我们确保了StatefulSet如何保证应用实例的拓补状态
在Pod删除和再创建的过程中保持稳定
我们继续看StatefulSet对存储状态的管理,使用了一个叫做Persistent Volume Claim的功能
我们介绍Pod的时候,要在一个Pod里面声明Volume,只需要在Pod里面加上spec.volumes字段就可以了,接下来就可以定义一个具体类型的Volume了
可是,很多持久化的Volume的类型,是不容易记住和得知的
作为一个应用开发者,我也不知道我的容器可能部署在上面持久化存储项目中,或者说,我根本不知道公司的K8S使用的持久化存储是什么?
而且,对于运维来说,全权将Volume相关的信息暴露给了开发者,可能会有暴露公司基础设置的风险
比如下面的Ceph RBD类型的Volume
apiVersion: v1
kind: Pod metadata: name: rbd spec: containers: – image: kubernetes/pause name: rbd-rw volumeMounts: – name: rbdpd mountPath: /mnt/rbd volumes: – name: rbdpd rbd: monitors: – ‘10.16.154.78:6789’ – ‘10.16.154.82:6789’ – ‘10.16.154.83:6789’ pool: kube image: foo fsType: ext4 readOnly: true user: admin keyring: /etc/ceph/keyring imageformat: “2” imagefeatures: “layering” |
在这个Pod声明的volumes中,存储了Ceph RBD对应的存储服务的地址,用户名,授权文件的地址,这样都暴露给了全公司的开发人员,这是属于过度暴露的例子
这也是为何,再后来的过程中,Kubernetes项目引入了一组叫做Persistent Volume Clain (PVC)和Persistent Volume (PV)的API对象,大大降低了用户使用的门槛
使用的方式不难
我们首先声明自己想要的PVC长什么样
kind: PersistentVolumeClaim
apiVersion: v1 metadata: name: pv-claim spec: accessModes: – ReadWriteOnce resources: requests: storage: 1Gi |
我们声明了name:pv-claim
和spec中的读写方式ReadWriteOnce 只能挂在一个节点上而不是多个节点共享 ,以及大小storage为1GB
接下来是
apiVersion: v1
kind: Pod metadata: name: pv-pod spec: containers: – name: pv-container image: nginx ports: – containerPort: 80 name: “http-server” volumeMounts: – mountPath: “/usr/share/nginx/html” name: pv-storage volumes: – name: pv-storage persistentVolumeClaim: claimName: pv-claim |
我队友我们直接声明volumes的类型是persisentVolumeClaim,然后指定PVC的名字,不关心Volume本身的定义
只要我们创建PVC的对象,Kubernetes就会开始绑定对应的Volumes
当然,这个Volumes不会凭空出现
自然是,由一个运维人员维护的PV对象,我们看一下常见的PV对象的YAML文件
kind: PersistentVolume
apiVersion: v1 metadata: name: pv-volume labels: type: local spec: capacity: storage: 10Gi accessModes: – ReadWriteOnce rbd: monitors: # 使用 kubectl get pods -n rook-ceph 查看 rook-ceph-mon- 开头的 POD IP 即可得下面的列表 – ‘10.16.154.78:6789’ – ‘10.16.154.82:6789’ – ‘10.16.154.83:6789’ pool: kube image: foo fsType: ext4 readOnly: true user: admin keyring: /etc/ceph/keyring |
这个PV的spec.rbd字段,正是之前说的Ceph RBD Volume的详细定义,还生命了这个PV的容量是10GB,这样Kuberenetes就会进行自动的绑定
Kuberentes中PVC和PV的设计,类似接口和实现的思想,开发者只要知道接口就可以既PVC,运维人员给接口绑定具体的实现PV
这种解耦,避免了向开发者暴露过多的存储细节
避免出现事故时候的扯皮
PVC和PV的设计,使得StatefulSet对存储的状态的管理成为了可能
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 |
比如,我们创建的StatefulSet,绑定了对应的volumeClaimTempaltes字段,从名字看出来,和Deployment中的Pod模板作用类似,凡事和StatefulSet管理的Pod,都会声明一个对应的PVC,这个PVC的定义,来自volumeClaimTemplates
这个PVC的名字也会有一个和Pod一致的编号
创建的PVC,也会自动和PV进行绑定
不过PVC和PV的绑定的前提在于,运维人员已经提前创建好了符合条件的PV,或者Kubermetes集群运行在公有云中,这样Kubernetes可以用过Dynamic Provisioning的方式,自动创建匹配的PV
然后我们看一下对应的StatefulSet绑定的PVC
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,会使用对应的volumes
web-0的Pod,会使用名为www-web-0的PVC,挂载到这个PVC的PV
然后,我们尝试绑定如下的Volume分配
$ for i in 0 1; do kubectl exec web-$i — sh -c ‘echo hello $(hostname) > /usr/share/nginx/html/index.html’; done
给每一个pod的volume写入一个对应的index文件
web-0写入的就是hello web-0
如果是在Pod中访问对应的nginx的文件,就会返回/usr/share/nginx/html/index.html
如果利用kubectl delete删除这两个Pod,这些Volume中的文件会不会丢失呢?
kubectl delete pod -l app=nginx
我们前面介绍的,在被删除之后,这些Pod会按照编号的顺序被重新创建出来,这是,如果在进行容器访问nginx服务
得到的还是对应的hell信息
说明,新创建的Pod和之前创建的pod仍然绑定在了一起,对于Pod-web-1来说也是一样的
这个原理相当的简单,先把一个Pod删除,但是这个Pod对应的PVC和PV,并不会被删除,但是Volume中已经写入了数据,也会被保存在远程存储服务里
而且,在一个web-0的Pod消失了,控制会发现并重新创建一个web-0出来
纠正不一致
而且,还会绑定对应的PVC,www-web-0,这个PVC的定义,来自于PVC模板,这是StatefulSet创建Pod的标准流程
这样,挂载上了对应的Volume之后,还能获取到之前的volume中的数据
这样,就实现了存储状态的管理
总结一下
StatefulSet中管理的是Pod,而不是和Deployment那样管理RS,每一个Pod的hostname,名字是不一样的,其中带了编号的,而StatefulSet利用这些编号进行了区分
其次,Kubernetes通过Headless Service,为这些有编号的POD,在DNS服务器中生成带有同样编号的DNS记录,只要Pod中编号不变,那么DNS记录也不会变,那么这条记录解析出的Pod的IP地址,会随着Pod的删除和再次创建而更新
最后,StatefulSet还为每一个Pod分配了一个同样编号的PVC,这样,Kubernetes就可以利用PV机制,来给这个PVC绑定上PV,保证每一个Pod有一个唯一的独立的Volume
这样看来,即使Pod已经被删除,对应的PV和PVC也会被保留,方便重新创建的Pod进行绑定,从而找到之前保存在Volume中的数据
在本章文章中,讲解了StatefulSet处理存储状态的方法,然后以此为基础,梳理了StatefuleSet控制器的原理
StatefulSet其实本质上就是一种特殊的Deployement,特殊在于,其给每一个Pod都进行了编号,这个编号会体现在Pod的名字和hostname上,这代表了Pod的唯一身份
有了这个编号,StatefulSet就使用Kubernetes中两个标准的功能,Headless Service和PV/PVC实现了对拓补状态和存储状态的维护