k8s的StatefulSet基础教程
k8s官网教程:StatefulSet基础1. StatefulSet的使用场景当我们部署一个有状态服务时,一般使用Statefulset控制器,具有如下特性:稳定的持久化存储,即Pod重新调度之后还是能够访问同样的持久化数据,基于PV和PVC实现。稳定的网络标识符,即Pod重新调度之后其PodName和HostName不变。有序部署,有序扩展,基于init-containers实现。有序收缩。2
1. StatefulSet的使用场景
当我们部署一个有状态服务时,一般使用Statefulset控制器,具有如下特性:
- 稳定的持久化存储,即Pod重新调度之后还是能够访问同样的持久化数据,基于PV和PVC实现。
- 稳定的网络标识符,即Pod重新调度之后其
PodName
和HostName
不变。基于Service类型为Headless实现。 - 有序部署,有序扩展,基于
init-containers
实现。 - 有序收缩。
2. 教程目标
StatefulSets 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes 上管理有状态应用和分布式系统是一个宽泛而复杂的话题。为了演示 StatefulSet 的基本特性,并且不使前后的主题混淆,使用 StatefulSet 部署一个简单的 web 应用。
在阅读本教程后,将熟悉以下内容:
- 如何创建 StatefulSet
- 如何删除 StatefulSet
- StatefulSet 怎样管理它的 Pods
- 如何对 StatefulSet 进行扩容/缩容
- 如何更新一个 StatefulSet 的 Pods
3. 创建StatefulSet
3.1 前置工作:搭建NFS服务器
- 安装NFS服务器
在k8s-master01节点安装NFS服务器
yum install -y nfs-common nfs-utils rpcbind
mkdir /nfs && chmod 777 /nfs
chown nfsnobody /nfs
cat > /etc/exports << EOF
/nfs *(rw,no_root_squash,no_all_squash,sync)
EOF
#启动nfs
systemctl start rpcbind && systemctl start nfs-server
#设置开启自启
systemctl enable rpcbind && systemctl enable nfs-server
- 在k8s-node01、k8s-node02安装客户端工具
yum install -y nfs-utils
systemctl start nfs-utils
systemctl enable nfs-utils
3.2 使用helm部署nfs-client-provisioner动态创建PV
在日常学习测试kubernetes时,经常需要PersistentVolume把一些数据(例如:数据库、日志等)存储起来,不随着容器的删除而丢失;关于PV、PVC、StorageClass的关系参考PV/PVC/StorageClass;存储卷的实现有很多种,此处选择比较容易实现的NFS作为存储。
helm的安装和使用可以参考其他教程。
helm安装nfs-client-provisioner
可以访问 nfs-client-provisioner的Char里面有关于配置的详细说明。
#这里需要helm提前添加stable仓库
[root@k8s-master01 nfs]# helm repo add stable https://kubernetes-charts.storage.googleapis.com
"stable" has been added to your repositories
[root@k8s-master01 nfs]# helm repo update
Hang tight while we grab the latest from your chart repositories...
...Successfully got an update from the "incubator" chart repository
...Successfully got an update from the "bitnami" chart repository
...Successfully got an update from the "stable" chart repository
Update Complete. ⎈Happy Helming!⎈
[root@k8s-master01 nfs]# helm repo list
NAME URL
incubator https://kubernetes-charts-incubator.storage.googleapis.com
bitnami https://charts.bitnami.com/bitnami
stable https://kubernetes-charts.storage.googleapis.com
安装nfs-client-provisioner
helm install nfs-client-provisioner stable/nfs-client-provisioner --set nfs.server=x.x.x.x --set nfs.path=/xxxx --set storageClass.defaultClass=true
上面的命令创建一个release实例、配置nfs-server地址、挂载的路径、设置为默认的class
查看创建的nfs-client-provisioner
[root@k8s-master01 nfs-client-provisioner]# helm list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
nfs-client-provisioner default 1 2020-09-23 15:04:48.690645046 +0800 CST deployed nfs-client-provisioner-1.2.9 3.1.0
查看storageclass
[root@k8s-master01 nfs]# kubectl get sc
NAME PROVISIONER AGE
nfs-client (default) cluster.local/nfs-client-provisioner 63m
这里default的含义是当定义pvc时,不声明storageClassName时,就会使用默认的default。
查看Pod
[root@k8s-master01 nfs-client-provisioner]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6999f4847d-ck52d 1/1 Running 0 34m
创建一个pvc检测能否动态创建pv,并且绑定到pv中
[root@k8s-master01 nfs]# cat > test-pvc.yaml << EOF
> apiVersion: v1
> kind: PersistentVolumeClaim
> metadata:
> name: test-nfs-pvc
> spec:
> accessModes:
> - ReadWriteMany
> storageClassName: nfs-client
> resources:
> requests:
> storage: 1Gi
> EOF
storageClassName: 需要定义为刚刚创建的nfs-client。这里不写也可以,因为已经将default设置为了nfs-client。
创建pvc,查看能否动态创建pv,pvc能够绑定到对应的pv上。
[root@k8s-master01 nfs]# kubectl apply -f test-pvc.yaml
persistentvolumeclaim/test-nfs-pvc created
[root@k8s-master01 nfs]# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-f5207723-c172-4076-bedb-f603115230eb 1Gi RWX Delete Bound default/test-nfs-pvc nfs-client 6s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/test-nfs-pvc Bound pvc-f5207723-c172-4076-bedb-f603115230eb 1Gi RWX nfs-client 6s
查看/nfs文件夹下的内容。这里的/nfs为使用helm创建nfs-client的release实例时设置的nfs.path的值
[root@k8s-master01 nfs]# ls -l /nfs
总用量 0
drwxrwxrwx 2 root root 6 9月 23 16:03 default-test-nfs-pvc-pvc-f5207723-c172-4076-bedb-f603115230eb
文件夹的格式为<namespace-name>-<pvc-name>-<pv-name>-<uuid>
3.3 编写资源清单文件
定义1个StatefulSet
的Controller和1个Headless
的Service资源清单。
在定义Service的时候clusterIP为None
需要注意的是PV不属于任何namespace,属于全局资源,PVC属于某一个namesapce,在定义Statefulset的时候,各个Pod使用的是属于自己的PVC,有三个副本,就会创建3个PVC。
资源清单:web.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
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: wangyanglinux/myapp:v1
imagePullPolicy: IfNotPresent
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: "nfs"
resources:
requests:
storage: 1Gi
这时候打开另外一个shell终端,执行kubectl get pod -w -o wide -l app=nginx
命令监控pod的创建过程
3.3 创建pv、service、statefulset、pvc
创建service、statefulset、pvc等 kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
查看创建的Statefulset、Service、PV、PVC等
[root@k8s-master01 statefulset]# kubectl get sts
NAME READY AGE
web 2/2 49s
[root@k8s-master01 statefulset]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 44m
nginx ClusterIP None <none> 80/TCP 53s
[root@k8s-master01 statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Bound default/www-web-0 nfs 56s
nfs-pv2 1Gi RWO Retain Bound default/www-web-1 nfs 56s
nfs-pv3 1Gi RWO Retain Available nfs 56s
[root@k8s-master01 statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfs-pv1 1Gi RWO nfs 58s
www-web-1 Bound nfs-pv2 1Gi RWO nfs 56s
顺序创建Pod
对于一个拥有N个副本的Statefulset,Pod被部署的时候的顺序是按照{0…N-1}的序号创建的。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
web-0 0/1 Pending 0 0s <none> <none> <none> <none>
web-0 0/1 Pending 0 0s <none> <none> <none> <none>
web-0 0/1 Pending 0 1s <none> k8s-node01 <none> <none>
web-0 0/1 ContainerCreating 0 1s <none> k8s-node01 <none> <none>
web-0 1/1 Running 0 2s 10.244.1.20 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 2s <none> k8s-node02 <none> <none>
web-1 0/1 ContainerCreating 0 2s <none> k8s-node02 <none> <none>
web-1 1/1 Running 0 4s 10.244.2.18 k8s-node02 <none> <none>
请注意在web-0的Pod处于Running和Ready状态之后,web-1的Pod才会启动。这里做到了有序部署。
4. StatefulSet 中的 Pod
Statefulset中的每一个Pod拥有一个唯一的顺序索引和稳定的网络身份标识。
4.1 检查Pod的顺序索引
[root@k8s-master01 /]# kubectl get pod -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 17m
web-1 1/1 Running 0 17m
Pod 的名称的形式为<statefulset name>-<ordinal index>
。web这个StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0和web-1。
4.2 使用稳定的网络身份标识
每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用kubectl exec在每个 Pod 中执行hostname。
[root@k8s-master01 /]# for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1
一般而言主机名就是Pod的名称。
运行一个busybox容器用于测试使用nslookup
命令检查集群内部的DNS地址。
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
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.6
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.6
这里可以得出,使用<Pod-name-index>.<Service-name>
的方式访问Pod,这是headless service做到的。
如果我们此时删除StatefulSet中所有的 Pod,由于StatefulSet是一种的Controller,它的Pod是受控制器控制,
始终会将Pod的副本数维持在期望值。因此会重建Pod。
但是问题来了,如果重建Pod,Pod的ip很有可能发生变化,但是使用<Pod-name-index>.<Service-name>
的方式能不能访问呢?
在一个终端中查看 StatefulSet 的 Pod。
kubectl get pod -w -l app=nginx
在另一个终端中使用 kubectl delete 删除 StatefulSet 中所有的 Pod。
kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 45m 10.244.1.20 k8s-node01 <none> <none>
web-1 1/1 Running 0 45m 10.244.2.18 k8s-node02 <none> <none>
web-0 1/1 Terminating 0 45m 10.244.1.20 k8s-node01 <none> <none>
web-1 1/1 Terminating 0 45m 10.244.2.18 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 45m 10.244.1.20 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 45m 10.244.2.18 k8s-node02 <none> <none>
web-1 0/1 Terminating 0 45m 10.244.2.18 k8s-node02 <none> <none>
web-1 0/1 Terminating 0 45m 10.244.2.18 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 45m 10.244.1.20 k8s-node01 <none> <none>
web-0 0/1 Terminating 0 45m 10.244.1.20 k8s-node01 <none> <none>
web-0 0/1 Pending 0 0s <none> <none> <none> <none>
web-0 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-0 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-0 1/1 Running 0 2s 10.244.1.26 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> k8s-node02 <none> <none>
web-1 0/1 ContainerCreating 0 0s <none> k8s-node02 <none> <none>
web-1 1/1 Running 0 2s 10.244.2.19 k8s-node02 <none> <none
重复之前的检查步骤,使用 kubectl exec 和 kubectl run 查看 Pod 的主机名和集群内部的 DNS 表项。
for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; done
web-0
web-1
kubectl run -i --tty --image busybox:1.28 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.8
Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变。这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要。
如果在项目中使用StatefulSet类型的Pod。可以在initcontainers中检查StatefulSet是否成功。方式为使用busybox的initcontainers去轮询解析Statefulset的域名:nslookup <Pod-name-index>.<Service-name>
直到成功为止。
例如:command: ['sh', '-c', "until nslookup <Pod-name-index>.<Service-name>.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]
这个命令是获取Pod所属的名称空间 cat /var/run/secrets/kubernetes.io/serviceaccount/namespace
4.3 写入稳定的存储
获取 web-0 和 web-1 的 PersistentVolumeClaims。
[root@k8s-master01 statefulset]# kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfs-pv1 1Gi RWO nfs 61m
www-web-1 Bound nfs-pv2 1Gi RWO nfs 61m
StatefulSet 控制器创建了两个 PersistentVolumeClaims,绑定到两个 PersistentVolumes。
NGINX web 服务器默认会加载位于 /usr/share/nginx/html/index.html 的 index 文件。StatefulSets spec 中的 volumeMounts 字段保证了 /usr/share/nginx/html 文件夹由一个 PersistentVolume 支持。
将 Pod 的主机名写入它们的index.html文件并验证 NGINX web 服务器使用该主机名提供服务。
for i in 0 1; do kubectl exec web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done
[root@k8s-master01 statefulset]# kubectl get pod -o wide | grep web | awk '{print $6}' | xargs curl
web-0
web-1
重复之前的监控Pod和删除Pod操作。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 24m 10.244.1.26 k8s-node01 <none> <none>
web-1 1/1 Running 0 24m 10.244.2.19 k8s-node02 <none> <none>
web-0 1/1 Terminating 0 25m 10.244.1.26 k8s-node01 <none> <none>
web-1 1/1 Terminating 0 24m 10.244.2.19 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 25m 10.244.1.26 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 25m 10.244.2.19 k8s-node02 <none> <none>
web-1 0/1 Terminating 0 25m 10.244.2.19 k8s-node02 <none> <none>
web-1 0/1 Terminating 0 25m 10.244.2.19 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 25m 10.244.1.26 k8s-node01 <none> <none>
web-0 0/1 Terminating 0 25m 10.244.1.26 k8s-node01 <none> <none>
web-0 0/1 Pending 0 0s <none> <none> <none> <none>
web-0 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-0 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-0 1/1 Running 0 2s 10.244.1.28 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> k8s-node02 <none> <none>
web-1 0/1 ContainerCreating 0 0s <none> k8s-node02 <none> <none>
web-1 1/1 Running 0 2s 10.244.2.20 k8s-node02 <none> <none>
验证所有 web 服务器在继续使用它们的主机名提供服务。
[root@k8s-master01 statefulset]# kubectl get pod -o wide | grep web | awk '{print $6}' | xargs curl
web-0
web-1
虽然 web-0 和 web-1 被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 被重新挂载到了各自的 volumeMount 上。不管 web-0 和 web-1 被调度到了哪个节点上,它们的 PersistentVolumes 将会被挂载到合适的挂载点上。
5. 扩容/缩容 StatefulSet
扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新 replicas 字段完成。你可以使用kubectl scale
或者kubectl patch
来扩容/缩容一个 StatefulSet。
5.1 扩容
在一个终端窗口观察 StatefulSet 的 Pod。kubectl get pods -w -l app=nginx
在另一个终端窗口使用 kubectl scale 扩展副本数为 3。
kubectl scale sts web --replicas=3
statefulset.apps/web scaled
在第一个 终端中检查 kubectl get 命令的输出,等待增加的 3 个 Pod 的状态变为 Running 和 Ready。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 24s 10.244.2.21 k8s-node02 <none> <none>
web-1 1/1 Running 0 21s 10.244.1.30 k8s-node01 <none> <none>
web-2 0/1 Pending 0 0s <none> <none> <none> <none>
web-2 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-2 0/1 ContainerCreating 0 1s <none> k8s-node01 <none> <none>
web-2 1/1 Running 0 3s 10.244.1.31 k8s-node01 <none> <none>
StatefulSet 控制器扩展了副本的数量。如同创建 StatefulSet 所述,StatefulSet 按序号索引顺序的创建每个 Pod,并且会等待前一个 Pod 变为 Running 和 Ready 才会启动下一个 Pod。
5.2 缩容
在一个终端观察 StatefulSet 的 Pod。 kubectl get pods -w -l app=nginx
在另一个终端使用 kubectl patch 将 StatefulSet 缩容回三个副本。
kubectl patch sts web -p '{"spec":{"replicas":2}}'
statefulset.apps/web patched
等待web-2状态变为Terminating
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 2m43s 10.244.2.21 k8s-node02 <none> <none>
web-1 1/1 Running 0 2m40s 10.244.1.30 k8s-node01 <none> <none>
web-2 1/1 Running 0 2m16s 10.244.1.31 k8s-node01 <none> <none>
web-2 1/1 Terminating 0 2m29s 10.244.1.31 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 2m30s 10.244.1.31 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 2m36s 10.244.1.31 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 2m36s 10.244.1.31 k8s-node01 <none> <none>
5.3 PV和PVC
查看PV和PVC
[root@k8s-master01 statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
nfs-pv1 1Gi RWO Retain Terminating default/www-web-0 nfs 88m
nfs-pv2 1Gi RWO Retain Terminating default/www-web-1 nfs 88m
nfs-pv3 1Gi RWO Retain Terminating default/www-web-2 nfs 88m
[root@k8s-master01 statefulset]# kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound nfs-pv1 1Gi RWO nfs 88m
www-web-1 Bound nfs-pv2 1Gi RWO nfs 88m
www-web-2 Bound nfs-pv3 1Gi RWO nfs 9m9s
3个 PersistentVolumeClaims 和3个 PersistentVolumes 仍然存在。查看 Pod 的 稳定存储,我们发现当删除 StatefulSet 的 Pod 时,挂载到 StatefulSet 的 Pod 的 PersistentVolumes 不会被删除。当这种删除行为是由 StatefulSet 缩容引起时也是一样的。
6. 更新 StatefulSet
Kubernetes 1.7 版本的 StatefulSet 控制器支持自动更新。更新策略由 StatefulSet API Object 的spec.updateStrategy
字段决定。这个特性能够用来更新一个 StatefulSet 中的 Pod 的 container images,resource requests,以及 limits,labels 和 annotations。RollingUpdate滚动更新是 StatefulSets 默认策略。
6.1 Rolling Update 策略
RollingUpdate 更新策略会更新一个 StatefulSet 中所有的 Pod,采用与序号索引相反的顺序并遵循 StatefulSet 的保证。
StatefulSet的默认更新策略是RollingUpdate
更新image的版本
kubectl set image sts/web nginx=wangyanglinux/myapp:v2 --record=true
或者是
kubectl patch sts/web -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx","image":"wangyanglinux/myapp:v2"}]}}}}'
或者是
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"wangyanglinux/myapp:v2"}]'
在一个终端监控 StatefulSet 中的 Pod。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 12h 10.244.2.21 k8s-node02 <none> <none>
web-1 1/1 Running 0 12h 10.244.1.30 k8s-node01 <none> <none>
web-1 1/1 Terminating 0 12h 10.244.1.30 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 12h 10.244.1.30 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 12h 10.244.1.30 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 12h 10.244.1.30 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-1 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-1 1/1 Running 0 2s 10.244.1.32 k8s-node01 <none> <none>
web-0 1/1 Terminating 0 12h 10.244.2.21 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 12h 10.244.2.21 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 12h 10.244.2.21 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 12h 10.244.2.21 k8s-node02 <none> <none>
web-0 0/1 Pending 0 0s <none> <none> <none> <none>
web-0 0/1 Pending 0 0s <none> k8s-node02 <none> <none>
web-0 0/1 ContainerCreating 0 0s <none> k8s-node02 <none> <none>
web-0 1/1 Running 0 4s 10.244.2.22 k8s-node02 <none> <none>
StatefulSet 里的 Pod 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet 控制器终止每个 Pod 并等待它们变成 Running 和 Ready。请注意,虽然在顺序后继者变成 Running 和 Ready 之前 StatefulSet 控制器不会更新下一个 Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们当前的版本。已经接收到更新请求的 Pod 将会被恢复为更新的版本,没有收到请求的 Pod 则会被恢复为之前的版本。像这样,控制器尝试继续使应用保持健康并在出现间歇性故障时保持更新的一致性。
获取 Pod 来查看他们的容器镜像。
[root@k8s-master01 statefulset]# for p in 0 1; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
wangyanglinux/myapp:v2
wangyanglinux/myapp:v2
6.2 分段更新
你可以使用 RollingUpdate 更新策略的 partition 参数来分段更新一个 StatefulSet。分段的更新将会使 StatefulSet 中的其余所有 Pod 保持当前版本的同时仅允许改变 StatefulSet 的 .spec.template。
partition
字段的作用是当Pod的序号大于等于partition
字段的值时,Pod会进行RollingUpdate
。
Patch web StatefulSet 来对 updateStrategy 字段添加一个分区。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
修改容器的镜像版本
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"wangyanglinux/myapp:v3"}]'
[root@k8s-master01 statefulset]# for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
wangyanglinux/myapp:v2
wangyanglinux/myapp:v2
wangyanglinux/myapp:v2
发现所有的Pod还是之前的版本,容器并没有发生改变,这是因为partition的值为3,Pod的序号最大值为2。因此所有的Pod都没有发生改变。
这时候我们手动删除web-2这个Pod。kubectl delete pod web-2
同时打开另一个终端监视Pod的状况
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 14m 10.244.2.26 k8s-node02 <none> <none>
web-1 1/1 Running 0 14m 10.244.1.39 k8s-node01 <none> <none>
web-2 1/1 Running 0 14m 10.244.1.38 k8s-node01 <none> <none>
web-2 1/1 Terminating 0 14m 10.244.1.38 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 14m 10.244.1.38 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 14m 10.244.1.38 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 14m 10.244.1.38 k8s-node01 <none> <none>
web-2 0/1 Pending 0 0s <none> <none> <none> <none>
web-2 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-2 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-2 1/1 Running 0 2s 10.244.1.40 k8s-node01 <none> <none>
重新查询web-2这个Pod的容器镜像的版本
[root@k8s-master01 statefulset]# kubectl get po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
wangyanglinux/myapp:v2
可以看到版本并没有发生变化
请注意,虽然更新策略是 RollingUpdate
,StatefulSet 控制器还是会使用原始的容器恢复 Pod。这是因为 Pod 的序号比 updateStrategy
指定的 partition
更小。
6.3 灰度发布
通过设置partition的值来进行灰度发布,以此来测试程序的改动。
通过 patch 命令修改 StatefulSet 来减少分区。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
statefulset.apps/web patched
监控Pod的运行状况
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 20m 10.244.2.26 k8s-node02 <none> <none>
web-1 1/1 Running 0 20m 10.244.1.39 k8s-node01 <none> <none>
web-2 1/1 Running 0 6m3s 10.244.1.40 k8s-node01 <none> <none>
web-2 1/1 Terminating 0 6m14s 10.244.1.40 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 6m15s 10.244.1.40 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 6m16s 10.244.1.40 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 6m16s 10.244.1.40 k8s-node01 <none> <none>
web-2 0/1 Pending 0 0s <none> <none> <none> <none>
web-2 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-2 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-2 1/1 Running 0 2s 10.244.1.41 k8s-node01 <none> <none>
获取web-2Pod中容器的镜像
[root@k8s-master01 statefulset]# kubectl get po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
wangyanglinux/myapp:v3
可以看到web-2这个Pod的镜像版本变成了v3,这是因为Pod的序号大于等于partition的值。
删除web-1这个Pod
[root@k8s-master01 statefulset]# kubectl delete pod web-1
pod "web-1" deleted
监控Pod的状况
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 25m 10.244.2.26 k8s-node02 <none> <none>
web-1 1/1 Running 0 25m 10.244.1.39 k8s-node01 <none> <none>
web-2 1/1 Running 0 4m36s 10.244.1.41 k8s-node01 <none> <none>
web-1 1/1 Terminating 0 25m 10.244.1.39 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 25m 10.244.1.39 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 25m 10.244.1.39 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 25m 10.244.1.39 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-1 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-1 1/1 Running 0 2s 10.244.1.42 k8s-node01 <none> <none>
查看web-1这个Pod的镜像的版本
[root@k8s-master01 statefulset]# kubectl get po web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
wangyanglinux/myapp:v2
web-1 被按照原来的配置恢复,因为 Pod 的序号小于分区。当指定了分区时,如果更新了 StatefulSet 的 .spec.template,则所有序号大于或等于分区的 Pod 都将被更新。如果一个序号小于分区的 Pod 被删除或者终止,它将被按照原来的配置恢复。
6.4 分阶段的发布
使用类似灰度发布的方法执行一次分阶段的发布(例如一次线性的、等比的或者指数形式的发布)。要执行一次分阶段的发布,你需要设置 partition 为希望控制器暂停更新的序号。
例如现在运行了3个Pod,可以先将partition的值设置为2,更新镜像的版本,将一部分用户流量导入到web-2中,当Pod运行一段时间之后发现没有问题,将partition的值设置为0,更新剩余web-0、web-1镜像的版本,以达到阶段性发布的目的。
分区当前为2。请将分区设置为0。
[root@k8s-master01 statefulset]# kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched
等待 StatefulSet 中的所有 Pod 变成 Running 和 Ready。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 36m 10.244.2.26 k8s-node02 <none> <none>
web-1 1/1 Running 0 10m 10.244.1.42 k8s-node01 <none> <none>
web-2 1/1 Running 0 15m 10.244.1.41 k8s-node01 <none> <none>
web-1 1/1 Terminating 0 10m 10.244.1.42 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 10m 10.244.1.42 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 10m 10.244.1.42 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 10m 10.244.1.42 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-1 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-1 1/1 Running 0 2s 10.244.1.43 k8s-node01 <none> <none>
web-0 1/1 Terminating 0 36m 10.244.2.26 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 36m 10.244.2.26 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 36m 10.244.2.26 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 36m 10.244.2.26 k8s-node02 <none> <none>
web-0 0/1 Pending 0 0s <none> <none> <none> <none>
web-0 0/1 Pending 0 0s <none> k8s-node02 <none> <none>
web-0 0/1 ContainerCreating 0 0s <none> k8s-node02 <none> <none>
web-0 1/1 Running 0 3s 10.244.2.27 k8s-node02 <none> <none>
仅更新web-1、web-0,且先更新web-1,等待web-1变成Running和Ready之后,再更新web-0。
获取所有Pod容器镜像的版本
[root@k8s-master01 statefulset]# for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
wangyanglinux/myapp:v3
wangyanglinux/myapp:v3
wangyanglinux/myapp:v3
7. 删除 StatefulSet
StatefulSet 同时支持级联和非级联删除。使用非级联方式删除 StatefulSet 时,StatefulSet 的 Pod 不会被删除。使用级联删除时,StatefulSet 和它的 Pod 都会被删除。
7. 1 非级联删除
在一个终端窗口查看 StatefulSet 中的 Pod。kubectl get pods -w -l app=nginx
使用 kubectl delete
删除 StatefulSet。请确保提供了 --cascade=false
参数给命令。这个参数告诉 Kubernetes 只删除 StatefulSet 而不要删除它的任何 Pod。
[root@k8s-master01 statefulset]# kubectl delete statefulset web --cascade=false
statefulset.apps "web" deleted
获取 Pod 来检查他们的状态。
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 10m 10.244.2.27 k8s-node02 <none> <none>
web-1 1/1 Running 0 11m 10.244.1.43 k8s-node01 <none> <none>
web-2 1/1 Running 0 26m 10.244.1.41 k8s-node01 <none> <none>
web-0 1/1 Running 0 11m 10.244.2.27 k8s-node02 <none> <none>
web-2 1/1 Running 0 26m 10.244.1.41 k8s-node01 <none> <none>
web-1 1/1 Running 0 11m 10.244.1.43 k8s-node01 <none> <none>
虽然 web
已经被删除了,但所有 Pod 仍然处于 Running 和 Ready 状态。
删除 web-0
[root@k8s-master01 statefulset]# kubectl delete pod web-0
pod "web-0" deleted
获取 StatefulSet 的 Pod。
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-1 1/1 Running 0 13m 10.244.1.43 k8s-node01 <none> <none>
web-2 1/1 Running 0 29m 10.244.1.41 k8s-node01 <none> <none>
由于web的StatefulSet 已经被删除,web-0没有被重新启动。
在一个终端监控 StatefulSet 的 Pod。kubectl get pods -w -l app=nginx
在另一个终端里重新创建 StatefulSet。
[root@k8s-master01 statefulset]# kubectl apply -f web.yaml
persistentvolume/nfs-pv1 unchanged
persistentvolume/nfs-pv2 unchanged
persistentvolume/nfs-pv3 unchanged
service/nginx unchanged
statefulset.apps/web created
查看Pod的变化
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-1 1/1 Running 0 13m 10.244.1.43 k8s-node01 <none> <none>
web-2 1/1 Running 0 29m 10.244.1.41 k8s-node01 <none> <none>
web-1 1/1 Running 0 15m 10.244.1.43 k8s-node01 <none> <none>
web-2 1/1 Running 0 31m 10.244.1.41 k8s-node01 <none> <none>
web-0 0/1 Pending 0 1s <none> <none> <none> <none>
web-0 0/1 Pending 0 1s <none> k8s-node02 <none> <none>
web-0 0/1 ContainerCreating 0 1s <none> k8s-node02 <none> <none>
web-0 1/1 Running 0 4s 10.244.2.28 k8s-node02 <none> <none>
web-2 1/1 Terminating 0 31m 10.244.1.41 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 31m 10.244.1.41 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 31m 10.244.1.41 k8s-node01 <none> <none>
web-2 0/1 Terminating 0 31m 10.244.1.41 k8s-node01 <none> <none>
web-1 1/1 Terminating 0 15m 10.244.1.43 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 15m 10.244.1.43 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 15m 10.244.1.43 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 15m 10.244.1.43 k8s-node01 <none> <none>
web-1 0/1 Pending 0 0s <none> <none> <none> <none>
web-1 0/1 Pending 0 0s <none> k8s-node01 <none> <none>
web-1 0/1 ContainerCreating 0 0s <none> k8s-node01 <none> <none>
web-1 1/1 Running 0 2s 10.244.1.44 k8s-node01 <none> <none>
当重新创建 web StatefulSet 时,web-0被第一个重新启动。由于 web-1 已经处于 Running 和 Ready 状态,当 web-0 变成 Running 和 Ready 时,StatefulSet 会直接接收这个 Pod。由于你重新创建的 StatefulSet 的 replicas 等于 2,一旦 web-0 被重新创建并且 web-1 被认为已经处于 Running 和 Ready 状态时,web-2将会被终止。
这里为什么web-1被重启了呢?因为web-1仍然是之前的v3版本的镜像,当使用Statefulset的web被创建之后,镜像版本是v1版本,重建web-1。
让我们再看看被 Pod 的 web 服务器加载的 index.html 的内容。
[root@k8s-master01 statefulset]# kubectl get pod -o wide | grep web | awk '{print $6}' | xargs curl
web-0
web-1
尽管你同时删除了 StatefulSet 和 web-0 Pod,但它仍然使用最初写入 index.html 文件的主机名进行服务。这是因为 StatefulSet 永远不会删除和一个 Pod 相关联的 PersistentVolumes。当你重建这个 StatefulSet 并且重新启动了 web-0 时,它原本的 PersistentVolume 会被重新挂载。
7.2 级联删除
在一个终端窗口观察 StatefulSet 里的 Pod。kubectl get pods -w -l app=nginx
在另一个窗口中再次删除这个 StatefulSet。这次省略 --cascade=false 参数。
[root@k8s-master01 statefulset]# kubectl delete statefulset web
statefulset.apps "web" deleted
查看Pod的状态
[root@k8s-master01 /]# kubectl get pod -w -o wide -l app=nginx
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
web-0 1/1 Running 0 8m42s 10.244.2.28 k8s-node02 <none> <none>
web-1 1/1 Running 0 8m21s 10.244.1.44 k8s-node01 <none> <none>
web-0 1/1 Terminating 0 8m45s 10.244.2.28 k8s-node02 <none> <none>
web-1 1/1 Terminating 0 8m24s 10.244.1.44 k8s-node01 <none> <none>
web-0 0/1 Terminating 0 8m46s 10.244.2.28 k8s-node02 <none> <none>
web-1 0/1 Terminating 0 8m25s 10.244.1.44 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 8m26s 10.244.1.44 k8s-node01 <none> <none>
web-1 0/1 Terminating 0 8m26s 10.244.1.44 k8s-node01 <none> <none>
web-0 0/1 Terminating 0 8m58s 10.244.2.28 k8s-node02 <none> <none>
web-0 0/1 Terminating 0 8m58s 10.244.2.28 k8s-node02 <none> <none>
如同你在缩容一节看到的,Pod 按照和他们序号索引相反的顺序每次终止一个。在终止一个 Pod 前,StatefulSet 控制器会等待 Pod 后继者被完全终止。
请注意,虽然级联删除会删除 StatefulSet 和它的 Pod,但它并不会删除和 StatefulSet 关联的 Headless Service。
你必须手动删除nginx Service。
[root@k8s-master01 statefulset]# kubectl delete service nginx
service "nginx" deleted
再一次重新创建 StatefulSet 和 Headless Service。
[root@k8s-master01 statefulset]# kubectl apply -f web.yaml
persistentvolume/nfs-pv1 unchanged
persistentvolume/nfs-pv2 unchanged
persistentvolume/nfs-pv3 unchanged
service/nginx created
statefulset.apps/web created
当 StatefulSet 所有的 Pod 变成 Running 和 Ready 时,获取它们的 index.html 文件的内容。
[root@k8s-master01 statefulset]# kubectl get pod -o wide | grep web | awk '{print $6}' | xargs curl
web-0
web-1
即使你已经删除了 StatefulSet 和它的全部 Pod,这些 Pod 将会被重新创建并挂载它们的 PersistentVolumes,并且 web-0 和 web-1 将仍然使用它们的主机名提供服务。
更多推荐
所有评论(0)