有状态应用
对于在ReplicaSet作业副本与水平扩展中使用deployment控制器,其功能并不足以覆盖所有容器编排的场景。因为对于deployment来说一个应用的所有 Pod,是完全一样的。所以,它们互相之间没有顺序,也无所谓运行在哪台宿主机上。 但是实际的应用,特别是分布式应用,它的多个实例之间,是有依赖关系的,比如:主从关系,主备关系。
这种实例之间有不对等关系,以及实例对外部数据有依赖关系的应用,就被称为“有状态应用”。基于控制器模式的设计思想,对有状态应用的编排有了初步的支持,即StatefulSet。
这里的状态抽象成了两种:
- 拓扑状态。就是应用启动的拓扑关系图,应用的多个实例因为不是对等关系,所以需要按照一定的顺序进行启动。
- 存储状态。应用的多个实例保存了不同的存储数据,对于一个实例来说,其生命周期读取的数据应该是一致的。
StatefulSet的核心功能就是:通过某种方式记录这些状态,然后在Pod被重新创建时,能够为这些新Pod恢复状态。
拓扑状态
在介绍StatefulSet工作原理之前,需要先了解一下Service对外访问中的Headless Serivce的内容。因为StatefulSet是通过Headless Service的DNS记录来维持Pod的拓扑状态的。
要了解其工作机制,以一个StatefulSet的YAML文件进行说明:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
ports:
- containerPort: 80
name: web这里的StatefulSet其定义与deployment的不同就多了一个serviceName=nginx字段。这个字段就是告诉StatefulSet控制器,在执行循环控制(Control Loop)的时候,使用名为nginx的这个Headless Service来保证Pod的可解析身份。
在上面的StatefulSet的声明当中,创建了副本为2的标签为app=nginx的Pod,然后使用名为nginx的Service来代理这两个Pod。
创建在Service对外访问中的Service和上面的StatefulSet。
$ kubectl create -f svc.yaml
$ kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 10s
$ kubectl create -f statefulset.yaml
$ kubectl get statefulset web
NAME DESIRED CURRENT AGE
web 2 1 19s查看Pod,可以发现StatefulSet给它所管理的Pod名字进行了编号,为<statefulset name>-<index>,索引从0开始:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4m43s
web-1 1/1 Running 0 3m49sNote
StatefulSet所管理的Pod的创建,是严格按照编号顺序进行的。在web-0进入到Running状态,并且细分状态为Ready之前,web-1会一直处于Pending状态。
进入容器查看hostname可以发现,创建的Pod的hostname与Pod的名字是一致的。
$ kubectl exec web-0 -- sh -c 'hostname'
web-0
$ kubectl exec web-1 -- sh -c 'hostname'
web-1StatefulSet通过给其管理的Pod进行编号,来固定拓扑关系。
因为Pod的不对等性,所以使用过程中需要访问特定的一个Pod,而不是任意一个POD,于是StatefulSet还利用Headeless Service的DNS记录来为每一个Pod提供固定且唯一的访问入口。
比如:web-0是一个主节点,web-1是一个从节点,编号保证了web-0要咸鱼web-1启动,并且为它们分配的dns,保证了可以想要访问主节点和从节点的需求。
接下来便试着以dns的方式访问这个Headless Service。启动一个Pod,然后在这个Pod的容器里,解析一下StatefulSet创建的两个Pod的dns。
$ kubectl run -i --tty --image busybox:1.28.4 dns-test --restart=Never --rm /bin/sh
$ nslookup web-0.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.1.7
$ nslookup web-1.nginx
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.2.7因为使用Service对外提供统一的入口,就算这两个Pod重启,ip发生了变化,照样能够通过Service提供的固定地址访问。
删除这两个Pod,让StatefulSet自动重建
$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted重建之后再一次启动一个Pod,然后解析dns就会发现,就算新的Pod的IP发生了变化,仍然能够通过dns访问。
总结
StatefulSet控制器的主要作用之一,就是在利用Pod模板创建Pod的时候,对它们进行了编号,然后按照编号顺序完成创建工作。在控制循环发现状态不一致需要进行调谐的时候,也会严格按照顺序来完成。此外,还通过Headless Service的方式,来为每一个Pod的实例提供了唯一且稳定的DNS记录作为访问入口。
存储状态
StatefulSet对存储状态的管理,使用了名为Persistent Volume Claim的机制。
在前面的StatefulSet的YAML配置的基础上,添加对应的字段:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.9.1
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi这里使用了volumeClaimTemplates字段,与Pod模板作用类似,被这个StatefulSet管理的Pod都会声明一个对应的PVC,这个PVC的名字会分配一个与这个Pod完全一致的编号。
自动创建的PVC与PV绑定之后,就会进入Bound状态,Pod可以挂载并使用这个PV了。绑定的前提是,运维人员已经在系统中创建好了符合条件的PVC;或者,k8s集群运行在公有云上,这样k8s就会通过Dynamic Provisioning的方式,自动创建与PVC配的PV。
根据新的YAML文件创建StatefulSet,就可以看到k8s集群里的两个PVC了
$ kubectl create -f statefulset.yaml
$ kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s这些PVC的命令方式为<PVC名字>-<StatefulSet名字>-<编号>的方式命名,并且处于Bound状态。
向Pod挂载的目录中写入文件,就是记录到其对应的PV中去。
就算Pod被删除重建,仍然能够从重建后的Pod中读取到先前存放的文件。这是因为Pod被删除,但是Pod对应的PVC和PV并不会被删除,新的Pod命名方式不会变,其查找的PVC的名称也不会变,所以能够找到旧的Pod留下的同名的PVC,进而找到PVC绑定的PV。
这样StatefulSet就实现了对应用存储状态的管理。
总结
StatefulSet是为了管理有状态应用的,就是应用之间是存在不对等关系的,比如主从,主备等。 StatefulSet是一个控制器,不过与Deployment不同的是:
- StatefulSet的控制器直接管理的是Pod。原因还是在于应用之间是不对等的,不能像ReplicaSet一样将所有应用看成一样的。StatefulSet直接管理Pod,对所有Pod进行编号。
- 通过Headless Service为这些有编号的Pod,在DNS服务器中生成带有同样编号的DNS记录。因为应用的不对等关系,所以需要具有能够访问特定一个应用的能力。
- 为每一个Pod分配并创建一个同样编号的PVC。这样就可以通过PVC与PV机制为每一个Pod都分配一个独立的Volume,存储状态。
再简单点说,StatefulSet就是特殊的Deployment,为其管理的Pod引入编号来保存拓扑结构。使用Headless Service来为每一个Pod提供唯一且不变的访问DNS,使用PVC/PV机制来保存每一个Pod的状态。这就是其管理有状态应用的方式。