k8s官网教程:StatefulSet基础

1. StatefulSet的使用场景

当我们部署一个有状态服务时,一般使用Statefulset控制器,具有如下特性:

  • 稳定的持久化存储,即Pod重新调度之后还是能够访问同样的持久化数据,基于PV和PVC实现。
  • 稳定的网络标识符,即Pod重新调度之后其PodNameHostName不变。基于Service类型为Headless实现。
  • 有序部署,有序扩展,基于init-containers实现。
  • 有序收缩。

2. 教程目标

StatefulSets 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes 上管理有状态应用和分布式系统是一个宽泛而复杂的话题。为了演示 StatefulSet 的基本特性,并且不使前后的主题混淆,使用 StatefulSet 部署一个简单的 web 应用。
在阅读本教程后,将熟悉以下内容:

  • 如何创建 StatefulSet
  • 如何删除 StatefulSet
  • StatefulSet 怎样管理它的 Pods
  • 如何对 StatefulSet 进行扩容/缩容
  • 如何更新一个 StatefulSet 的 Pods

3. 创建StatefulSet

3.1 前置工作:搭建NFS服务器

  1. 安装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
  1. 在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 将仍然使用它们的主机名提供服务。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐