StatefulSet
RC、RS、Deployment、DS(DaemonSet)这些Pod控制器都是面向无状态的服务,它们所管理的Pod的IP、名字、启停顺序等都是随机的
这些Pod控制器都有一个相同点
template(模板):根据模板创建出来的Pod,它们的状态都是一摸一样的(除了名称、IP、域名之外)
可以理解为:任何一个Pod都可以被删除,然后用新生成的Pod进行替换
StatefulSet:
顾名思义:有状态的集合,管理所有有状态的服务,比如MySQL、MongoDB集群等
它之前的名字是:PetSet
Pet:宠物
把之前按无状态的服务比喻为牛、羊等牲畜。把有状态的服务比喻为:宠物
StatefulSet本质上是Deployment的一种变体,在v1.9版本中已成为GA版本,它为了解决有状态服务的问题,它所管理的Pod拥有固定的Pod名称,启停顺序,在StatefulSet中,Pod名字称为网络标识(hostname),还必须要用到共享存储
有状态的服务:后端生成的每一个Pod都具有自己的唯一性,不可随意被删除
需要记录前一次或者多次通信中的相关事件,以作为下一次通信的分类标准。比如:mysql等数据库服务。(Pod的名称不能随意变化,数据持久化的目录也是不一样的,每一个Pod都有自己独有的数据持久化存储目录)
一个小实例:
[root@master ~]# vim statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: headless-svc
labels:
app: headless-svc
spec:
ports:
- port: 80
selector:
app: headless-pod
clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test
spec:
serviceName: headless-svc
replicas: 3
selector:
matchLabels:
app: headless-pod
template:
metadata:
labels:
app: headless-pod
spec:
containers:
- name: myhttpd
image: httpd
ports:
- containerPort: 80
[root@master ~]# kubectl apply -f statefulset.yaml
service/headless-svc created
statefulset.apps/statefulset-test created
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless-svc ClusterIP None <none> 80/TCP 4m
Deployment控制的pod的名称由来:
Deployment+RS+随机字符串(Pod的名称),没有顺序的额,可以被随意替代
StategulSet的三个组成部分:
1、headless-svc:无头服务。因为没有IP地址,所以它不具备负载均衡的功能了
作用:为后端的每一个Pod去命名
因为statefulset要求Pod的名称是有顺序的,每一个Pod都不能被随意取代,也就是说即使Pod重建之后,名称依然不变
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
statefulset-test-0 1/1 Running 0 23m
statefulset-test-1 1/1 Running 0 22m
statefulset-test-2 1/1 Running 0 22m
[root@master ~]# kubectl delete pod statefulset-test-0
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
statefulset-test-0 0/1 ContainerCreating 0 6s
statefulset-test-1 1/1 Running 0 25m
statefulset-test-2 1/1 Running 0 25m
//Pod重建之后名称没有发生变化
2、statefulset:定义具体的应用
3、volumeClaimTeplates:自动创建PVC,为后端的Pod提供专有的存储
存储卷申请模板,创建PVC,指定pvc名称大小,将自动创建pvc,且pvc必须由存储类供应
为什么需要 headless service 无头服务?
在用Deployment时,每一个Pod名称是没有顺序的,是随机字符串,因此是Pod名称是无序的,但是在statefulset中要求必须是有序 ,每一个pod不能被随意取代,pod重建后pod名称还是一样的。而pod IP是变化的,所以是以Pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称 。
为什么需要volumeClaimTemplate?
对于有状态的副本集都会用到持久存储,对于分布式系统来讲,它的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自已的专用存储,但是如果在Deployment中的Pod template里定义的存储卷,是所有副本集共用一个存储卷,数据是相同的,因为是基于模板来的 ,而statefulset中每个Pod都要自已的专有存储卷,所以statefulset的存储卷就不能再用Pod模板来创建了,于是statefulSet使用volumeClaimTemplate,称为卷申请模板,它会为每个Pod生成不同的pvc,并绑定pv, 从而实现各pod有专用存储。这就是为什么要用volumeClaimTemplate的原因
每一个pod—>对应一个pvc—->每一个pvc对应一个pv
storageclass:自动创建PV
需要解决:自动创建PVC—–>volumeClaimTeplates
一、创建StorageClass资源对象
1、基于NFS服务,创建NFS服务
[root@master ~]# showmount -e
Export list for master:
/nfsdata *
2、创建rbac权限
[root@master ~]# vim rbac-rolebind.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: nfs-provisioner-runner
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["watch", "create", "update", "patch"]
- apiGroups: [""]
resources: ["services", "endpoints"]
verbs: ["get","create","list", "watch","update"]
- apiGroups: ["extensions"]
resources: ["podsecuritypolicies"]
resourceNames: ["nfs-provisioner"]
verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-provisioner
subjects:
- kind: ServiceAccount
name: nfs-provisioner
namespace: default //这个字段必须要写,不然会报错
roleRef:
kind: ClusterRole
name: nfs-provisioner-runner
apiGroup: rbac.authorization.k8s.io
[root@master ~]# kubectl apply -f rbac-rolebind.yaml
serviceaccount/nfs-provisioner unchanged
clusterrole.rbac.authorization.k8s.io/nfs-provisioner-runner unchanged
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-provisioner created
3、创建Deployment资源对象,用Pod代替真正的NFS服务
[root@master ~]# vim nfs-deployment.yaml
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nfs-client-provisioner
spec:
replicas: 1
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccount: nfs-provisioner
containers:
- name: nfs-client-provisioner
image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: bdqn
- name: NFS_SERVER
value: 192.168.1.70
- name: NFS_PATH
value: /nfsdata
volumes:
- name: nfs-client-root
nfs:
server: 192.168.1.70
path: /nfsdata
[root@master ~]# kubectl apply -f nfs-deployment.yaml
deployment.extensions/nfs-client-provisioner created
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-f6cb6688b-5zn8d 1/1 Running 0 7s
4、创建storaclass
[root@master ~]# vim test-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: sc-nfs
provisioner: bdqn
reclaimPolicy: Retain
[root@master ~]# kubectl apply -f test-storageclass.yaml
storageclass.storage.k8s.io/sc-nfs created
[root@master ~]# kubectl get sc
NAME PROVISIONER AGE
sc-nfs bdqn 10s
二、解决自动创建PVC
[root@master ~]# vim statefulset.yaml
apiVersion: v1
kind: Service
metadata:
name: headless-svc
labels:
app: headless-svc
spec:
ports:
- port: 80
name: myweb
selector:
app: headless-pod
clusterIP: None
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: statefulset-test
spec:
serviceName: headless-svc
replicas: 3
selector:
matchLabels:
app: headless-pod
template:
metadata:
labels:
app: headless-pod
spec:
containers:
- image: httpd
name: myhttpd
ports:
- containerPort: 80
name: httpd
volumeMounts:
- mountPath: /mnt
name: test
volumeClaimTemplates: //自动的创建PVC
- metadata:
name: test
annotations: //这是指定storageclass
volume.beta.kubernetes.io/storage-class: sc-nfs
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
[root@master ~]# kubectl apply -f statefulset.yaml
service/headless-svc created
statefulset.apps/statefulset-test created
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-f6cb6688b-5zn8d 1/1 Running 0 13m
statefulset-test-0 1/1 Running 0 23s
statefulset-test-1 1/1 Running 0 16s
statefulset-test-2 1/1 Running 0 9s
注意:
如果生成的Pod,第一个出现了问题,后面的都不会生成
根据volumeClaimTemplates自动创建的PVC:
[root@master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-statefulset-test-0 Bound pvc-3a105a9d-5892-4080-a993-20fd2540cd3e 100Mi RWO sc-nfs 46m
test-statefulset-test-1 Bound pvc-123bf53d-a72b-4bfa-a901-bb98efcda056 100Mi RWO sc-nfs 45m
test-statefulset-test-2 Bound pvc-8529c668-5b38-4024-93af-b18fa238b0ba 100Mi RWO sc-nfs 45m
如果集群中没有StorageClass的动态供应PVC的机制,也可以提前手动创建多个PV、PVC,手动创建的PVC名称必须符合之后创建的StatefulSet命名规则:**(volumeClaimTemplates.name)-(pod_name)**
Statefulset名称为statefulset-test
三个Pod副本: statefulset-test-0,statefulset-test-1,statefulset-test-2
volumeClaimTemplates名称为:test
那么自动创建出来的PVC名称为test-statefulset-test-[0-2],为每个Pod创建一个PVC
规律总结:
- 匹配Pod name(网络标识)的模式为:**$(statefulset名称)-$(序号)**,比如上面的示例:statefulset-test-0,statefulset-test-1,statefulset-test-2。
- StatefulSet为每个Pod副本创建了一个DNS域名,这个域名的格式为: **$(podname).(headless server name)**,也就意味着服务间是通过Pod域名来通信而非Pod IP,因为当Pod所在Node发生故障时,Pod会被飘移到其它Node上,Pod IP会发生变化,但是Pod域名不会有变化。
- StatefulSet使用Headless服务来控制Pod的域名,这个域名的FQDN为:**$(service name).$(namespace).svc.cluster.local**,其中,“cluster.local”指的是集群的域名。
- 根据volumeClaimTemplates,为每个Pod创建一个pvc,pvc的命名规则匹配模式:**(volumeClaimTemplates.name)-(pod_name)**,比如上面的volumeMounts.name=test, Pod name=statefulset-test-[0-2],因此创建出来的PVC是test-statefulset-test-0,test-statefulset-test-1,test-statefulset-test-2
- 删除Pod不会删除其pvc,手动删除pvc将自动释放pv。
关于Cluster Domain、headless service名称、StatefulSet 名称如何影响StatefulSet的Pod的
StatefulSet的启停顺序:
- 有序部署:部署StatefulSet时,如果有多个Pod副本,它们会被顺序地创建(从0到N-1)并且,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态。
- 有序删除:当Pod被删除时,它们被终止的顺序是从N-1到0。
- 有序扩展:当对Pod执行扩展操作时,与部署一样,它前面的Pod必须都处于Running和Ready状态
StatefulSet Pod管理策略:
在v1.7以后,通过允许修改Pod排序策略,同时通过.spec.podManagementPolicy字段确保其身份的唯一性。
- OrderedReady:上述的启停顺序,默认设置。
- Parallel:告诉StatefulSet控制器并行启动或终止所有Pod,并且在启动或终止另一个Pod之前不等待前一个Pod变为Running and Ready或完全终止
StatefulSet使用场景:
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
- 有序收缩,有序删除(即从N-1到0)
更新策略:
在Kubernetes 1.7及更高版本中,通过.spec.updateStrategy字段允许配置或禁用Pod、labels、source request/limits、annotations自动滚动更新功能。
OnDelete:通过.spec.updateStrategy.type 字段设置为OnDelete,StatefulSet控制器不会自动更新StatefulSet中的Pod。用户必须手动删除Pod,以使控制器创建新的Pod。
RollingUpdate:通过.spec.updateStrategy.type 字段设置为RollingUpdate,实现了Pod的自动滚动更新,如果.spec.updateStrategy未指定,则此为默认策略。
StatefulSet控制器将删除并重新创建StatefulSet中的每个Pod。它将以Pod终止(从最大序数到最小序数)的顺序进行,一次更新每个Pod。在更新下一个Pod之前,必须等待这个Pod Running and Ready。Partitions:通过指定 .spec.updateStrategy.rollingUpdate.partition 来对 RollingUpdate 更新策略进行分区,如果指定了分区,则当 StatefulSet 的 .spec.template 更新时,具有大于或等于分区序数的所有 Pod 将被更新。
具有小于分区的序数的所有 Pod 将不会被更新,即使删除它们也将被重新创建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition 大于其 .spec.replicas,则其 .spec.template 的更新将不会传播到 Pod。在大多数情况下,不需要使用分区
StatefulSet注意事项:
- 还在beta状态,需要kubernetes v1.5版本以上才支持
- 所有Pod的Volume必须使用PersistentVolume或者是管理员事先创建好
- 为了保证数据安全,删除StatefulSet时不会删除Volume
- StatefulSet需要一个Headless Service来定义DNS domain,需要在StatefulSet之前创建好
- 目前StatefulSet还没有feature complete,比如更新操作还需要手动patch
更多可以参考:https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/
进入容器,验证持久化是否成功
[root@master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-f6cb6688b-5zn8d 1/1 Running 0 30m
statefulset-test-0 1/1 Running 0 17m
statefulset-test-1 1/1 Running 0 16m
statefulset-test-2 1/1 Running 0 16m
[root@master ~]# kubectl exec -it statefulset-test-0 /bin/sh
# cd /mnt
# touch testfile
# exit
[root@master ~]# ls /nfsdata/default-test-statefulset-test-0-pvc-3a105a9d-5892-4080-a993-20fd2540cd3e/
testfile
小结
Deployment、RC、RS、DS:
这些Pod控制器都是面向无状态的服务,他们管理的Pod的IP、名字、启停顺序等都是随机的
这些根据模板创建出来的Pod,特们的状态都是一摸一样的(除了名称、IP、域名之外)
任何一个Pod都可以被删除,然后用因生成的Pod进项替换
StatefulSet:
顾名思义:有状态的集合,管理所有的有状态服务,比如MySQL集群等
后端生成的每一个Pod都具有自己的唯一性,不可被随意删除
需要记录前一次或者多次通信中的相关事件,以作为下一次通信的分类标准
Pod的名称不能随意变化,数据持久化的目录也是不一样的,每一个Pod又都自己独有的数据持久化存储目录
扩容、缩容:在此过程中,Pod的生成或删除操作也是有顺序性的
[root@master ~]# kubectl get pod -n zhb
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-f6cb6688b-x2zls 1/1 Running 1 42h
statefulset-test-0 1/1 Running 1 42h
statefulset-test-1 1/1 Running 1 42h
statefulset-test-2 1/1 Running 1 42h
statefulset-test-3 1/1 Running 0 76s
statefulset-test-4 1/1 Running 0 66s
升级操作:
[root@master ~]# kubectl explain sts.spec.updateStrategy.rollingUpdate.partition
partition:如果partition后面的值等于N,N+的都会更新,默认值为0(所有都会更新)
如果N等于2,那么它会从statefulset-test-2开始更新,以此类推
statefulset-test-0
statefulset-test-1
statefulset-test-2
statefulset-test-3
statefulset-test-4