K8S-StatefulSet
引入
Deployment不足以满足所有的应用编排问题,造成这个问题的根本原因,因为Deployment对应用做了一个简单的假设,一个应用的所有Pod是完全一样的,它们之间没有顺序,也无所谓到底运行在哪一台宿主机上,需要的时候Deployment可以根据Pod模板创建新的Pod,不需要的时候Deployment就可以杀掉任意一个Pod。
这样的话并不能满足我们的所有场景,多个实例之间可能存在依赖关系,比如主从关系、主备关系。还有一些自身实例中自己带有一些缓存数据的,实例一旦被杀掉虽然能够重建出来但是数据已经丢了。
这种实例之间有不对等关系,以及实例对外部数据有依赖的,称为有状态应用,Deployment适合用来部署无状态应用,容器技术对于有状态应用来说,困难程度直线上升,一开始有状态应用几乎成了容器技术圈子的忌讳,不过Kubernetes还是成为了第一个吃螃蟹的人,这个吃螃蟹的工具就是StatefulSet
。
介绍
StatefulSet的设计非常容易理解,将真实世界里的应用状态分为如下两种:
拓扑状态
拓扑状态意味着应用的多个实例之间不是完全的对等关系,这些应用的实例必须按照一定的顺序启动,比如主节点A和从节点B,主节点A必须先于从节点B启动,删除了重新创建的时候也必须满足这个顺序,并且新创建出来的Pod必须和原来的Pod标识一样,这样原先的访问者才能使用原先的方法,访问到新的Pod
存储状态
队友应用的多个实例来说,应用的多个实例绑定了不同的存储数据,对于这些应用实例来说,Pod A第一次读取到的数据,和隔了十分钟读取到的数据应该是同一份,哪怕Pod A在这个期间被重新创建过,典型例子比如数据库应用的多个存储实例
StatefulSet的核心功能就是通过某种方式记录Pod的这些状态,让Pod在重建时能够为新Pod恢复这些状态。
Service和Headless Service介绍
讲述StatefulSet工作原理之前,必须先讲解一下Headless Service,Service是Kubernetes中将Pod暴露访问的一种机制,定义好了Pod,只要能访问到这个Service就能访问到Pod,Service如何被访问的呢?
VIP
第一种方式就是Service的VIP(Virtual IP)方式,即虚拟IP的方式,比如我看到Service的IP地址是10.0.23.1,我访问这个10.0.23.1其实就是个虚拟IP,它会把请求转发到Service所代理的一个Pod上
DNS
Service的DNS方式,比如这时候只要我访问my-svc.my-namespace.svc.cluster.local
这条DNS记录,就可以访问电脑名为my-svc代理的Pod。
Service DNS方式下,分为两种处理方法:
第一种处理方法,是Normal Service。这种情况下访问my-svc.my-namespace.svc.cluster.local
解析到的就是my-svc的VIP,然后就是VIP的方式继续访问。
第二种处理方法,就是Headless Service,这种情况下你访问my-svc.my-namespace.svc.cluster.local
解析到的就是my-svc代理的某一个Pod的IP地址,这里的区别就在于,你不需要为这个Service分配虚拟IP,而是可以直接以DNS记录的方式解析出被代理的Pod的IP地址。
这么样设计有何作用,先从Headless Service的定义方式看看再解释
Headless Service定义
1 | apiVersion: v1 |
将上面这个yaml文件使用kubectl create -f xxx.yaml
创建,这里的字段大多和普通的Service一样,只是clusterIP字段的值是None,也就是这个Service没有一个VIP作为头,这样这个Service被创建后就没有VIP,而是会以DNS记录的方式暴露出它所代理的Pod,他所代理的Pod就是Pod上携带了app=nginx标签的Pod。
创建完这个Headless Service之后,他所代理的所有的Pod的IP地址,都被绑定一个这样的DNS记录:
1 | <pod-name>.<svc-name>.<namespace>.svc.cluster.local |
这个DNS记录,就是Kubernetes项目为Pod分配的唯一的可解析身份,有个这种访问方式,知道pod的名字和service的名字就能直接通过这条DNS记录访问到IP地址了。
StatefulSet定义
我们再来编写一个StatefulSet的YAML文件
1 | apiVersion: apps/v1 |
这个yaml文件的定义就是比Deployment一文中的nginx-deployment多了一个serviceName=nginx的属性,这个字段就是告诉StatefulSet控制器在执行控制循环的时候请使用nginx这个Headless Service来保证Pod的可解析身份。
创建这个StatefulSet
1 | [root@localhost home]# kubectl create -f statefulSet_test1.yml |
从上面pod观察可以发现,StatefulSet新建的Pod后面都有个编号,从0开始,Pod的创建也是按照顺序来创建的,比如web-0进入Running状态,并且READY之前,web-1都会处于Pending状态,两个Pod都进入了Running状态之后,可以查看到它们唯一的网络身份了。
现在来试试用之前说到的dns记录能不能访问到这些pod,使用如下命令创建一个一次性Pod,在里面使用nslookup
解析Pod对应的Headless Service
1 | 创建一个一次性的Pod,-rm命令意味着Pod退出后就会被删除掉(busybox的latest镜像一直解析dns失败,用老版本就可以) |
上面的命令执行成功之后会自动进入pod中
StatefulSet的Pod重启
我们先删除掉这两个Pod,因为存在StatefulSet的缘故,检测到这两个Pod下线会重新启动Pod起来
1 | [root@localhost ~]# kubectl delete pod -l app=nginx |
接下来我们使用如下命令来观测这个app=nginx
的Pod的状态,(执行完删除得快点执行这条命令不然看不到效果了)
1 | [root@localhost ~]# kubectl get pod -w -l app=nginx |
从上面我们可以发现,web-0和web-1被停掉以后,web-0
经过Pending
,ContainerCreating
,Running
三个阶段重新运行起来,接下来才是web-1
有开始经过这三个阶段,可以看出来,Kubernetes会按照原先编号的顺序,创建两个新的Pod,并且现在他们的网络身份依然是web-0.nginx
再去执行上面的创建busybox的pod
1 | [root@localhost home]# kubectl run -i --tty --image busybox:1.28.3 dns-test --restart=Never --rm /bin/sh |
可以看到结果如下,web-0.nginx
的解析的ip address已经有了修改
所以我们对于有状态应用的实例访问,应该使用DNS记录或者hostname,而不是Pod的IP地址。
总结如下:StatefulSet这个控制器在创建Pod的时候会对Pod进行编号,并且按照编号顺序逐一完成创建工作,而当StatefulSet的控制循环发现期望状态和实际状态不一致的时候,需要新建、删除Pod操作的时候,会按照Pod的顺序完成这些操作。