Kubernetes 一文详解StatefulSet 控制器
无状态与有状态
Deployment控制器设计原则:管理的所有Pod一模一样,提供同一个服务,也不考虑在哪台Node运行,可随意扩容和缩容。这种应用称为“无状态”,例如Web服务
在实际的场景中,并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有依赖关系,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群
StatefulSet 控制器概述
StatefulSet控制器用于部署有状态应用,满足一些有状态应
用的需求:
• Pod有序的部署、扩容、删除和停止
• Pod分配一个稳定的且唯一的网络标识
• Pod分配一个独享的存储
Deployment数据卷是共享的,当创建3个pod都是用的同一个数据卷,对外提供统一的服务。
StatefulSet 控制器:主机名
稳定的网络标识:使用Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份,会为每个Pod分配一个数字编号并且按照编号顺序部署。还需要在StatefulSet添加serviceName: “nginx”字段指定StatefulSet控制器要使用这个Headless Service。
稳定主要体现在主机名和Pod A记录:
• 主机名:<statefulset名称>-<编号>
• Pod DNS A记录:<statefulset名称-编号>.<service-name> .<namespace>.svc.cluster.local
之前的service会自动的帮你填充cluster ip,这里将字段设置为none,不会分配,和deployment不同,deployment都是提供相同的统一的入口。有状态的都是提供独立的访问。
[root@k8s-master ~]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
name: headless-web
namespace: default
spec:
clusterIP: None
ports:
- port: 80
protocol: TCP
targetPort: 80
selector:
app: nginx
type: ClusterIP
[root@k8s-master ~]# kubectl apply -f service.yaml
service/headless-web created
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
headless-web ClusterIP None <none> 80/TCP 9s
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 76d
可以看到没有为其分配cluster ip
稳定的网络标识怎么体现的,为每个pod分配固定的主机名,并且会创建dns记录
[root@k8s-master ~]# cat statefulset-web.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "headless-web"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-d565bb89b-8d9hp 1/1 Running 4 26h
web-0 1/1 Running 0 99s
web-1 1/1 Running 0 39s
web-2 1/1 Running 0 22s
这个主机名是固定的,即使pod被删除掉了,重新启动也是这个名称上面体现了稳定的主机名
[root@k8s-master ~]# kubectl exec web-0 -- hostname
web-0
[root@k8s-master ~]# kubectl exec web-1 -- hostname
web-1
[root@k8s-master ~]# kubectl exec web-2 -- hostname
web-2
可见,对于一个拥有 N 个副本的 StatefulSet 来说,Pod 在部署时按照 {0 …… N-1} 的序号顺序创建的,而删除的时候按照逆序逐个删除,这便是我想说的第一个特性。StatefulSet 创建出来的 Pod 都具有固定的、且确切的主机名。
StatefulSet 控制器:网络标识
可以看到,每个 Pod 都有一个对应的 A 记录。
[root@k8s-master ~]# kubectl run -i -t dns-test --image busybox:1.28.4 /bin/sh
If you don't see a command prompt, try pressing enter.
/ # nslookup headless-web.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: headless-web.default.svc.cluster.local
Address 1: 10.244.169.147 web-2.headless-web.default.svc.cluster.local
Address 2: 10.244.169.146 web-1.headless-web.default.svc.cluster.local
Address 3: 10.244.36.81 web-0.headless-web.default.svc.cluster.local
/ # ping web-0.headless-web.default.svc.cluster.local
PING web-0.headless-web.default.svc.cluster.local (10.244.36.81): 56 data bytes
64 bytes from 10.244.36.81: seq=0 ttl=62 time=1.105 ms
64 bytes from 10.244.36.81: seq=1 ttl=62 time=2.990 ms
64 bytes from 10.244.36.81: seq=2 ttl=62 time=1.408 ms
^C
这里解析出来了三条,任意访问其中的一条可以访问具体的pod,即使pod重建ip发生变化,也能够通过这个记录解析到新的Ip上面
有头服务只会解析出一条记录
上面固定的dns名称和主机名就是网络标识的体现
删除一下这些 Pod,看看会有什么变化:
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
dns-test 1/1 Running 0 10m
nfs-client-provisioner-d565bb89b-8d9hp 1/1 Running 4 26h
web-0 1/1 Running 0 14m
web-1 1/1 Running 0 13m
web-2 1/1 Running 0 13m
[root@k8s-master ~]# kubectl delete pod web-0
pod "web-0" deleted
/ # nslookup headless-web.default.svc.cluster.local
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: headless-web.default.svc.cluster.local
Address 1: 10.244.36.82 web-0.headless-web.default.svc.cluster.local
Address 2: 10.244.169.147 web-2.headless-web.default.svc.cluster.local
Address 3: 10.244.169.146 web-1.headless-web.default.svc.cluster.local
可以看出,DNS 记录中 Pod 的域名没有发生变化,仅仅 IP 地址发生了更换。因此当 Pod 所在的节点发生故障导致 Pod 飘移到其他节点上,或者 Pod 因故障被删除重建,Pod 的 IP 都会发生变化,但是 Pod 的域名不会有任何变化,这也就意味着服务间可以通过不变的 Pod 域名来保障通信稳定,而不必依赖 Pod IP。
有了spec.serviceName这个字段,保证了 StatefulSet 关联的 Pod 可以有稳定的网络身份标识,即 Pod 的序号、主机名、DNS 记录名称等。
StatefulSet 控制器:独享存储
最后一个我想说的是,对于有状态的服务来说,每个副本都会用到持久化存储,且各自使用的数据是不一样的。
StatefulSet 通过 PersistentVolumeClaim(PVC)可以保证 Pod 的存储卷之间一一对应的绑定关系。同时,删除 StatefulSet 关联的 Pod 时,不会删除其关联的 PVC。
独享存储:StatefulSet的存储卷使用VolumeClaimTemplate创建,称为卷申请模板,当StatefulSet使用VolumeClaimTemplate创建一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC,每个PVC绑定对应的PV,从而保证每个Pod都拥有独立的存储。
[root@k8s-master ~]# cat statefulset-web.yml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "headless-web"
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
storageClassName: "managed-nfs-storage"
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 1Gi
[root@k8s-master ~]# kubectl apply -f statefulset-web.yml
statefulset.apps/web created
volumeClaimTemplates: 这个字段delploy是不支持的,只要state支持,这个是通过动态供给完成的,实际上还是申请pvc pv ,www是申请模板里面的名字
[root@k8s-master ~]# kubectl get pvc,pv
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-web-0 Bound pvc-7403fc64-998a-4d94-9bf7-00cde6f4e52d 1Gi RWO managed-nfs-storage 87s
persistentvolumeclaim/www-web-1 Bound pvc-9c10db54-3a82-4374-ad24-b14b59d4819e 1Gi RWO managed-nfs-storage 66s
persistentvolumeclaim/www-web-2 Bound pvc-d8ea2562-6137-404f-bef9-b0c9f700a040 1Gi RWO managed-nfs-storage 44s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-7403fc64-998a-4d94-9bf7-00cde6f4e52d 1Gi RWO Delete Bound default/www-web-0 managed-nfs-storage 86s
persistentvolume/pvc-9c10db54-3a82-4374-ad24-b14b59d4819e 1Gi RWO Delete Bound default/www-web-1 managed-nfs-storage 66s
persistentvolume/pvc-d8ea2562-6137-404f-bef9-b0c9f700a040 1Gi RWO Delete Bound default/www-web-2 managed-nfs-storage
Pvc和pv一一绑定的,可以看到每个独立的Pod都有独立的pv
[root@reg kubernetes]# pwd
/ifs/kubernetes
[root@reg kubernetes]# ls
default-www-web-0-pvc-7403fc64-998a-4d94-9bf7-00cde6f4e52d
default-www-web-1-pvc-9c10db54-3a82-4374-ad24-b14b59d4819e
default-www-web-2-pvc-d8ea2562-6137-404f-bef9-b0c9f700a040
当你删除pod的时候pv pvc不会被删除,并且删除重建之后还是可以读取之前的数据,当pod被重建之后,k8s还是根据pod编号找对应的pvc进行挂载
如何更新升级 StatefulSet
那么,如果想对一个 StatefulSet 进行升级,该怎么办呢?
在 StatefulSet 中,支持两种更新升级策略,即 RollingUpdate 和 OnDelete。
RollingUpdate策略是默认的更新策略。可以实现 Pod 的滚动升级,跟我们上一节课中 Deployment 介绍的RollingUpdate策略一样。比如我们这个时候做了镜像更新操作,那么整个的升级过程大致如下,先逆序删除所有的 Pod,然后依次用新镜像创建新的 Pod 出来。这里你可以通过kubectl get pod -n demo -w -l app=nginx来动手观察下。
总结
现在我们就总结下 StatefulSet 的特点:
- 具备固定的网络标记,比如主机名,域名等;
- 支持持久化存储,而且最好能够跟实例一一绑定;
- 可以按照顺序来部署和扩展;
- 可以按照顺序进行终止和删除操作;
- 在进行滚动升级的时候,也会按照一定顺序。
借助 StatefulSet 的这些能力,我们就可以去部署一些有状态服务,比如 MySQL、ZooKeeper、MongoDB 等。你可以跟着这个教程在 Kubernetes 中搭建一个 ZooKeeper 集群。
同时使用 RollingUpdate 更新策略还支持通过 partition 参数来分段更新一个 StatefulSet。所有序号大于或者等于 partition 的Pod 都将被更新。你这里也可以手动更新 StatefulSet 的配置来实验下。
当你把更新策略设置为 OnDelete 时,我们就必须手动先删除 Pod,才能触发新的 Pod 更新。
相关文章
- 基于kubeadm部署kubernetes
- 详解kubernetes的企业级监控(付文档)
- kubernetes | 存储
- Kubernetes DaemonSet控制器
- Kubernetes资源之服务发现service详解
- 关于 Kubernetes中secret、configmap的一些笔记
- kubernetes-pod详解(二)
- Kubernetes—Pod控制器详解
- 云计算运维kubernetes集群里集成Apollo配置中心
- Kubernetes集群部署之六Flannel网络部署详解架构师
- Kubernetes集群部署之四Master节点部署详解架构师
- Kubernetes集群部署之三ETCD集群部署详解架构师
- Kubernetes 1.9发布:Apps Workloads通用版本与生态系统扩展