Kubernetes的有状态应用:基础

文章目录

  • 环境
  • 创建StatefulSet
    • 顺序创建pod
  • StatefulSet中的pod
    • 检查pod的顺序索引
    • 使用稳定的网络身份标识
      • 发现StatefulSet中特定的pod
    • 写入稳定的存储
  • 伸缩StatefulSet
    • 扩容
    • 缩容
    • 顺序终止pod
  • 更新StatefulSet
    • 滚动更新
      • 分段更新
      • 金丝雀发布
      • 分阶段发布
    • OnDelete
  • 删除 StatefulSet
    • 非级联删除
    • 级联删除
  • Pod 管理策略
    • Parallel pod管理策略
  • 清理
  • 参考

环境

  • RHEL 9.3
  • Docker Community 24.0.7
  • minikube v1.32.0

创建StatefulSet

在一个终端窗口中,持续监视pod:

kubectl get pods --watch -l app=nginx

创建文件 application/web/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: registry.k8s.io/nginx-slim:0.8
        image: kaiding1/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

注:因为访问不了 registry.k8s.io ,所以事先把image pull下来,并push到了可访问的位置。

$ kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
$ kubectl get service nginx
NAME    TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx   ClusterIP   None                 80/TCP    2m30s
$ kubectl get statefulset web
NAME   READY   AGE
web    2/2     3m10s

顺序创建pod

如果StatefulSet有 n 个副本,则pod在部署时是按 {0..n-1} 的顺序创建的。

回到监视窗口,如下:

$ kubectl get pods --watch -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          0s
web-0   0/1     ContainerCreating   0          0s
web-0   1/1     Running             0          2s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   1/1     Running             0          1s

注意:pod web-0 处于 Running 状态并 READY 后,pod web-1 才会启动。

StatefulSet中的pod

StatefulSet中的每个pod都有一个唯一的顺序索引和稳定的网络身份标识。

检查pod的顺序索引

$ kubectl get pods -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          7m28s
web-1   1/1     Running   0          7m26s

StatefulSet中的pod拥有一个粘性的、唯一的身份标志。该标志基于StatefulSet控制器分配给每个pod 的唯一顺序索引。Pod的命名格式为 - 。本例中, web StatefulSet 拥有两个副本,所以创建了两个pod:web-0web-1

使用稳定的网络身份标识

每个pod都有一个基于顺序索引的稳定的hostname:

$ for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1

使用 kubectl run 运行一个提供 nslookup 命令的容器,该命令来自于 dnsutils 包。通过 nslookup pod的hostname,可以检查它们在集群内部的DNS地址:

$ kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
If you don't see a command prompt, try pressing enter.
/ # 
/ # nslookup web-0.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.0.144 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.0.145 web-1.nginx.default.svc.cluster.local

输入 exit 退出。

Headless service的CNAME指向SRV记录(每个 RunningReady 的pod对应一条记录)。SRV记录指向一个包含pod IP地址的记录条目。

删除StatefulSet中的pod:

$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

回到监视窗口,如下:

$ kubectl get pods --watch -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          0s
web-0   0/1     ContainerCreating   0          0s
web-0   1/1     Running             0          2s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   1/1     Running             0          1s
web-0   1/1     Terminating         0          33m
web-1   1/1     Terminating         0          33m
web-0   0/1     Terminating         0          33m
web-1   0/1     Terminating         0          33m
web-0   0/1     Terminating         0          33m
web-0   0/1     Terminating         0          33m
web-0   0/1     Terminating         0          33m
web-1   0/1     Terminating         0          33m
web-0   0/1     Pending             0          0s
web-0   0/1     Pending             0          0s
web-1   0/1     Terminating         0          33m
web-1   0/1     Terminating         0          33m
web-0   0/1     ContainerCreating   0          0s
web-0   1/1     Running             0          1s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   1/1     Running             0          1s
$ kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
If you don't see a command prompt, try pressing enter.
/ # 
/ # nslookup web-0.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.0.147 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.0.148 web-1.nginx.default.svc.cluster.local

输入 exit 退出。

Pod 的序号、主机名、SRV记录和记录名称没有改变,但pod关联的IP地址可能发生了改变。所以在其它应用中,不要通过IP地址来连接StatefulSet中的pod。

发现StatefulSet中特定的pod

如果要查找并连接StatefulSet的活动成员,需要查询headless service的CNAME( nginx.default.svc.cluster.local )。和CNAME相关联的SRV记录只包含StatefulSet中处于 RunningReady 状态的pod。

如果应用已经实现了用于测试是否已存活(liveness)和就绪(readiness)的连接逻辑,你可以使用pod的SRV记录( web-0.nginx.default.svc.cluster.localweb-1.nginx.default.svc.cluster.local )。因为它们是稳定的,当pod状态变为 RunningReady 时,应用就能够发现其地址。

写入稳定的存储

获取 web-0web-1 的PVC(PersistentVolumeClaims):

$ kubectl get pvc -l app=nginx
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-71418599-e0d4-4c45-9f3b-f56ebd992509   1Gi        RWO            standard       75m
www-web-1   Bound    pvc-c6741fad-942a-434a-b2a7-05efe540ca1e   1Gi        RWO            standard       68m

StatefulSet 控制器创建了两个绑定到PV(PersistentVolume)的PVC。

本例中使用的集群配置为动态provision PV,因此PV都是自动创建和绑定的。

NginX web服务器默认会加载 /usr/share/nginx/html/index.html 。StatefulSet spec 中的 volumeMounts 字段会确保 /usr/share/nginx/html 目录由一个PV支持。

将pod的hostname写入它们的 index.html 文件,并验证 NginX web服务器使用该hostname提供服务:

for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

注:如果遇到了 403 Forbidden

for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done

在监视窗口,重新监视:

kubectl get pod --watch -l app=nginx

删除pod:

$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted

回到监视窗口,如下:

$ kubectl get pods --watch -l app=nginx
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          33m
web-1   1/1     Running   0          33m
web-0   1/1     Terminating   0          33m
web-1   1/1     Terminating   0          33m
web-0   0/1     Terminating   0          33m
web-1   0/1     Terminating   0          33m
web-0   0/1     Terminating   0          33m
web-0   0/1     Terminating   0          33m
web-0   0/1     Terminating   0          33m
web-1   0/1     Terminating   0          33m
web-0   0/1     Pending       0          0s
web-0   0/1     Pending       0          0s
web-1   0/1     Terminating   0          33m
web-1   0/1     Terminating   0          33m
web-0   0/1     ContainerCreating   0          0s
web-0   1/1     Running             0          1s
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   1/1     Running             0          1s

验证web服务器会继续通过hostname提供服务:

$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

虽然 web-0web-1 被重新调度了,但它们仍然继续监听hostname,因为和PVC相关联的PV被重新mount到 volumeMount 。无论 web-0web-1 被调度到了哪个node,它们的PV将会被mount到合适的mount point。

伸缩StatefulSet

可通过 kubectl scale 或者 kubectl patch 来伸缩StatefulSet。

(注:原文写的是“scale up/down”(纵向伸缩),我觉得其实应该是“scale out/in”(横向伸缩)。)

扩容

在监视窗口,重新监视:

kubectl get pods --watch -l app=nginx
kubectl scale sts web --replicas=5

回到监视窗口,如下:

$ kubectl get pods --watch -l app=nginx
NAME    READY   STATUS    RESTARTS       AGE
web-0   1/1     Running   1 (110s ago)   153m
web-1   1/1     Running   1 (111s ago)   153m
web-2   0/1     Pending   0              0s
web-2   0/1     Pending   0              0s
web-2   0/1     Pending   0              23s
web-2   0/1     ContainerCreating   0              23s
web-2   1/1     Running             0              24s
web-3   0/1     Pending             0              0s
web-3   0/1     Pending             0              0s
web-3   0/1     Pending             0              2s
web-3   0/1     ContainerCreating   0              2s
web-3   1/1     Running             0              3s
web-4   0/1     Pending             0              0s
web-4   0/1     Pending             0              0s
web-4   0/1     Pending             0              2s
web-4   0/1     ContainerCreating   0              2s
web-4   1/1     Running             0              3s

StatefulSet按顺序索引串行创建pod,前一个pod变为 RunningReady 后才会启动下一个pod。

缩容

在监视窗口,重新监视:

kubectl get pods --watch -l app=nginx
kubectl patch sts web -p '{"spec":{"replicas":3}}'

回到监视窗口,如下:

$ kubectl get pods --watch -l app=nginx
NAME    READY   STATUS    RESTARTS        AGE
web-0   1/1     Running   1 (8m25s ago)   160m
web-1   1/1     Running   1 (8m26s ago)   160m
web-2   1/1     Running   0               6m23s
web-3   1/1     Running   0               5m59s
web-4   1/1     Running   0               5m56s
web-4   1/1     Terminating   0               6m2s
web-4   0/1     Terminating   0               6m2s
web-4   0/1     Terminating   0               6m3s
web-4   0/1     Terminating   0               6m3s
web-4   0/1     Terminating   0               6m3s
web-3   1/1     Terminating   0               6m6s
web-3   0/1     Terminating   0               6m6s
web-3   0/1     Terminating   0               6m7s
web-3   0/1     Terminating   0               6m7s
web-3   0/1     Terminating   0               6m7s

顺序终止pod

控制器会按照pod顺序索引的相反顺序,依次删除pod。在完全删除一个pod之后,才会删除下一个pod。

获取StatefulSet的PVC:

$ kubectl get pvc -l app=nginx
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-71418599-e0d4-4c45-9f3b-f56ebd992509   1Gi        RWO            standard       4h9m
www-web-1   Bound    pvc-c6741fad-942a-434a-b2a7-05efe540ca1e   1Gi        RWO            standard       4h2m
www-web-2   Bound    pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643   1Gi        RWO            standard       10m
www-web-3   Bound    pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64   1Gi        RWO            standard       10m
www-web-4   Bound    pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee   1Gi        RWO            standard       10m

可见,仍然有五个PVC和五个PV。当删除StatefulSet的pod时,mount的PV不会被删除。当StatefulSet缩容导致pod被删除时,也是一样。

更新StatefulSet

StatefulSet控制器支持自动更新。其策略由StatefulSet API对象的 spec.updateStrategy 字段决定。该特性可用来更新容器image、资源请求和/或限制、label和注解。

有两种更新策略:

  • RollingUpdate (默认)
  • OnDelete

滚动更新

RollingUpdate 更新策略会更新StatefulSet中的所有pod,采用与顺序索引相反的顺序,同时遵循对StatefulSet的保证(guarantee)。

对 web StatefulSet 应用 Patch 操作来应用 RollingUpdate 更新策略:

在监视窗口,重新监视:

kubectl get pods --watch -l app=nginx
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"docker.io/kaiding1/nginx-slim:0.7"}]'

注:原文是 gcr.io/google_containers/nginx-slim:0.8 ,这里有两个问题:

  • gcr.io 无法访问,所以事先要把image pull下来,并push到可访问的位置。
  • gcr.io/google_containers/nginx-slim:0.8registry.k8s.io/nginx-slim:0.8 是相同的,所以并不会触发patch,为了有差异,改为 0.7

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          3m59s
web-1   1/1     Running   0          4m1s
web-2   1/1     Running   0          4m3s
web-2   1/1     Terminating   0          15m
web-2   0/1     Terminating   0          15m
web-2   0/1     Terminating   0          15m
web-2   0/1     Terminating   0          15m
web-2   0/1     Pending       0          0s
web-2   0/1     Pending       0          0s
web-2   0/1     ContainerCreating   0          0s
web-2   0/1     ErrImagePull        0          4s
web-2   0/1     ImagePullBackOff    0          18s
web-2   0/1     ErrImagePull        0          34s
web-2   0/1     ImagePullBackOff    0          45s
web-2   0/1     ErrImagePull        0          61s
web-2   0/1     ImagePullBackOff    0          72s
web-2   0/1     ErrImagePull        0          113s
web-2   0/1     ImagePullBackOff    0          2m4s
web-2   0/1     ErrImagePull        0          3m29s
web-2   0/1     ImagePullBackOff    0          3m43s
web-2   0/1     ErrImagePull        0          6m23s
web-2   0/1     ImagePullBackOff    0          6m35s
web-2   0/1     ErrImagePull        0          11m
web-2   0/1     ImagePullBackOff    0          11m
web-2   1/1     Running             0          16m
web-1   1/1     Terminating         0          31m
web-1   0/1     Terminating         0          31m
web-1   0/1     Terminating         0          31m
web-1   0/1     Terminating         0          31m
web-1   0/1     Terminating         0          31m
web-1   0/1     Pending             0          0s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          0s
web-1   1/1     Running             0          1s
web-0   1/1     Terminating         0          31m
web-0   0/1     Terminating         0          31m
web-0   0/1     Terminating         0          31m
web-0   0/1     Terminating         0          31m
web-0   0/1     Terminating         0          31m
web-0   0/1     Pending             0          0s
web-0   0/1     Pending             0          0s
web-0   0/1     ContainerCreating   0          0s
web-0   1/1     Running             0          1s

可见,StatefulSet里的pod采用和序号相反的顺序更新。StatefulSet控制器终止每个pod,并等待它们变成 RunningReady ,然后再更新下一个pod。注意,StatefulSet控制器仍然会恢复在更新过程中发生故障的pod,恢复为当前版本。

注:从监视的输出结果,可见出问题时,watch的输出间隔大概是上一次的1.5倍到2倍时间。

接收到更新请求的pod将会被恢复为更新的版本,而没有收到更新请求的pod会被恢复为之前的版本。这样,即使出现间歇故障,控制器会尝试继续使应用保持健康以及更新的一致性。

查看pod:

$ for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
docker.io/kaiding1/nginx-slim:0.7
docker.io/kaiding1/nginx-slim:0.7
docker.io/kaiding1/nginx-slim:0.7

查看更新:

$ kubectl rollout status sts/web
partitioned roll out complete: 3 new pods have been updated...

分段更新

对于使用 RollingUpdate 策略的StatefulSet,可通过 .spec.updateStrategy.rollingUpdate.partition 把更新分隔为多个 partition 。具体参见 https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions

可以使用 .spec.updateStrategy.rollingUpdatepartition 字段来分段更新StatefulSet。 这样,使得StatefulSet中的pod不变的同时,改变StatefulSet的pod模板。然后,就可以触发准备好的升级。

首先,patch web StatefulSet,为 updateStrategy 字段添加partition:

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":"docker.io/kaiding1/nginx-slim:0.8"}]'
kubectl delete pod web-2

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS        AGE
web-0   1/1     Running   1 (4m53s ago)   5h19m
web-1   1/1     Running   1 (4m53s ago)   5h19m
web-2   1/1     Running   1 (4m53s ago)   5h36m
web-2   1/1     Terminating   1 (6m28s ago)   5h38m
web-2   0/1     Terminating   1 (6m28s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Pending       0               0s
web-2   0/1     Pending       0               0s
web-2   0/1     ContainerCreating   0               0s
web-2   1/1     Running             0               1s
$ kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.7

注意:虽然更新策略是 RollingUpdate ,StatefulSet还是会使用原先的容器恢复pod。这是因为pod序号比 updateStrategy 指定的 partition 小。

金丝雀发布

注:关于金丝雀部署,参见 https://glossary.cncf.io/canary-deployment

金丝雀部署是一种部署策略,开始时有两个环境:一个有实时流量,另一个包含没有实时流量的更新代码。 流量逐渐从应用程序的原始版本转移到更新版本。 它可以从移动 1% 的实时流量开始,然后是 10%,25%,以此类推,直到所有流量都通过更新的版本运行。 企业可以在生产中测试新版本的软件,获得反馈,诊断错误,并在必要时快速回滚到稳定版本。

“金丝雀” 一词是指 “煤矿中的金丝雀” 的做法,即把金丝雀带入煤矿以保证矿工的安全。 如果出现无味的有害气体,鸟就会死亡,而矿工们知道他们必须迅速撤离。 同样,如果更新后的代码出了问题,现场交通就会被 “疏散” 回原来的版本。

可以通过减少 partition 来进行金丝雀发布,以测试修改的模板。

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'

Control plane会触发 web-2 的替换(先delete再create)。

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS        AGE
web-0   1/1     Running   1 (4m53s ago)   5h19m
web-1   1/1     Running   1 (4m53s ago)   5h19m
web-2   1/1     Running   1 (4m53s ago)   5h36m
web-2   1/1     Terminating   1 (6m28s ago)   5h38m
web-2   0/1     Terminating   1 (6m28s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Pending       0               0s
web-2   0/1     Pending       0               0s
web-2   0/1     ContainerCreating   0               0s
web-2   1/1     Running             0               1s
web-2   1/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Pending             0               0s
web-2   0/1     Pending             0               0s
web-2   0/1     ContainerCreating   0               0s
web-2   1/1     Running             0               1s
$ kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.8

当改变 partition 时,StatefulSet会自动更新 web-2 pod,这是因为pod的序号大于等于 partition

$ kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.7

删除 web-1 pod:

kubectl delete pod web-1

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS        AGE
web-0   1/1     Running   1 (4m53s ago)   5h19m
web-1   1/1     Running   1 (4m53s ago)   5h19m
......
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Pending             0               0s
web-1   0/1     Pending             0               0s
web-1   0/1     ContainerCreating   0               0s
web-1   1/1     Running             0               1s
$ kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.7

web-1 被恢复为初始配置,因为pod序号小于分区。当指定了分区时,如果更新了StatefulSet的 .spec.template ,则所有序号大于等于分区的pod都将被更新。如果序号小于分区的pod被删除或者终止,它将被恢复为初始配置。

分阶段发布

与金丝雀发布的方法类似,可以执行分阶段发布(例如:线性的、几何的、或者指数的发布)。 要执行分阶段发布,要把 partition 设置为希望控制器暂停更新的序号。

kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS        AGE
web-0   1/1     Running   1 (4m53s ago)   5h19m
web-1   1/1     Running   1 (4m53s ago)   5h19m
web-2   1/1     Running   1 (4m53s ago)   5h36m
web-2   1/1     Terminating   1 (6m28s ago)   5h38m
web-2   0/1     Terminating   1 (6m28s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Terminating   1 (6m29s ago)   5h38m
web-2   0/1     Pending       0               0s
web-2   0/1     Pending       0               0s
web-2   0/1     ContainerCreating   0               0s
web-2   1/1     Running             0               1s
web-2   1/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Terminating         0               14m
web-2   0/1     Pending             0               0s
web-2   0/1     Pending             0               0s
web-2   0/1     ContainerCreating   0               0s
web-2   1/1     Running             0               1s
web-1   1/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Terminating         1 (28m ago)     5h42m
web-1   0/1     Pending             0               0s
web-1   0/1     Pending             0               0s
web-1   0/1     ContainerCreating   0               0s
web-1   1/1     Running             0               1s
web-1   1/1     Terminating         0               10m
web-1   0/1     Terminating         0               10m
web-1   0/1     Terminating         0               10m
web-1   0/1     Terminating         0               10m
web-1   0/1     Terminating         0               10m
web-1   0/1     Pending             0               0s
web-1   0/1     Pending             0               0s
web-1   0/1     ContainerCreating   0               0s
web-1   1/1     Running             0               1s
web-0   1/1     Terminating         1 (38m ago)     5h53m
web-0   0/1     Terminating         1 (38m ago)     5h53m
web-0   0/1     Terminating         1 (38m ago)     5h53m
web-0   0/1     Terminating         1 (38m ago)     5h53m
web-0   0/1     Terminating         1 (38m ago)     5h53m
web-0   0/1     Pending             0               0s
web-0   0/1     Pending             0               0s
web-0   0/1     ContainerCreating   0               0s
web-0   1/1     Running             0               1s
$ for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
docker.io/kaiding1/nginx-slim:0.8
docker.io/kaiding1/nginx-slim:0.8
docker.io/kaiding1/nginx-slim:0.8

partition 设置为 0 ,则允许StatefulSet继续更新流程。

OnDelete

$ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete"}}}'
The StatefulSet "web" is invalid: spec.updateStrategy.rollingUpdate: Invalid value: apps.RollingUpdateStatefulSetStrategy{Partition:0, MaxUnavailable:(*intstr.IntOrString)(nil)}: only allowed for updateStrategy 'RollingUpdate'

报错了,应该是哪里有冲突。

那就直接 kubectl edit 好了:

kubectl edit statefulset web

找到 updateStrategy ,修改如下:

  updateStrategy:
    type: OnDelete

对于 OnDelete 更新策略,当StatefulSet的 .spec.template 字段有修改时,StatefulSet控制器不会自动更新pod。需要自己处理更新——要么采取手工方式,要么使用其它自动化手段。

删除 StatefulSet

StatefulSet支持级联和非级联删除。对于非级联删除,当StatefulSet被删除时,其pod不会被删除。对于级联删除,StatefulSet和它的pod都会被删除。

非级联删除

在监视窗口,重新监视:

kubectl get pods --watch -l app=nginx
kubectl delete statefulset web --cascade=orphan

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS      AGE
web-0   1/1     Running   1 (95s ago)   11h
web-1   1/1     Running   1 (95s ago)   11h
web-2   1/1     Running   1 (95s ago)   12h
web-2   1/1     Running   1 (12m ago)   12h
web-1   1/1     Running   1 (12m ago)   12h
web-0   1/1     Running   1 (12m ago)   12h

可见, web 虽然被删除了,其pod仍然处于 RunningReady 状态。

删除 web-0

kubectl delete pod web-0

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS      AGE
web-0   1/1     Running   1 (95s ago)   11h
web-1   1/1     Running   1 (95s ago)   11h
web-2   1/1     Running   1 (95s ago)   12h
web-2   1/1     Running   1 (12m ago)   12h
web-1   1/1     Running   1 (12m ago)   12h
web-0   1/1     Running   1 (12m ago)   12h
web-0   1/1     Terminating   1 (14m ago)   12h
web-0   0/1     Terminating   1 (14m ago)   12h
web-0   0/1     Terminating   1 (14m ago)   12h
web-0   0/1     Terminating   1 (14m ago)   12h
web-0   0/1     Terminating   1 (14m ago)   12h

可见, web-0 不会再被重建。

在监视窗口,重新监视:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS      AGE
web-1   1/1     Running   1 (16m ago)   12h
web-2   1/1     Running   1 (16m ago)   12h

重新创建 web

$ kubectl apply -f web.yaml
service/nginx unchanged
statefulset.apps/web created

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS      AGE
web-1   1/1     Running   1 (16m ago)   12h
web-2   1/1     Running   1 (16m ago)   12h
web-1   1/1     Running   1 (18m ago)   12h
web-2   1/1     Running   1 (18m ago)   12h
web-0   0/1     Pending   0             0s
web-0   0/1     Pending   0             1s
web-0   0/1     ContainerCreating   0             1s
web-0   1/1     Running             0             1s
web-2   1/1     Terminating         1 (18m ago)   12h
web-2   0/1     Terminating         1 (18m ago)   12h
web-2   0/1     Terminating         1 (18m ago)   12h
web-2   0/1     Terminating         1 (18m ago)   12h
web-2   0/1     Terminating         1 (18m ago)   12h
web-1   1/1     Terminating         1 (18m ago)   12h
web-1   0/1     Terminating         1 (18m ago)   12h
web-1   0/1     Terminating         1 (18m ago)   12h
web-1   0/1     Terminating         1 (18m ago)   12h
web-1   0/1     Terminating         1 (18m ago)   12h
web-1   0/1     Pending             0             0s
web-1   0/1     Pending             0             0s
web-1   0/1     ContainerCreating   0             0s
web-1   1/1     Running             0             1s

当重新创建 web StatefulSet时,首先重新启动 web-0 。当 web-0 变成 RunningReady 时,由于 web-1 已经处于 RunningReady 状态,StatefulSet会接收这个pod。由于重新创建的StatefulSet的 replicas 等于 2 ,一旦 web-0 被重新创建,且 web-1 被认为已经处于 RunningReady 状态,则 web-2 将会被终止。

$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

尽管删除了StatefulSet和 web-0 pod,但它仍然使用最初写入 index.html 文件的hostname进行服务。这是因为StatefulSet永远不会删除和pod相关联的PV。当重建StatefulSet,并且重新启动了 web-0 时,它原本的PV会被重新mount。

级联删除

在监视窗口,重新监视:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          13m
web-1   1/1     Running   0          13m
kubectl delete statefulset web

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS   AGE
web-0   1/1     Running   0          13m
web-1   1/1     Running   0          13m
web-1   1/1     Terminating   0          13m
web-0   1/1     Terminating   0          13m
web-0   0/1     Terminating   0          13m
web-1   0/1     Terminating   0          13m
web-1   0/1     Terminating   0          13m
web-1   0/1     Terminating   0          13m
web-1   0/1     Terminating   0          13m
web-0   0/1     Terminating   0          13m
web-0   0/1     Terminating   0          13m
web-0   0/1     Terminating   0          13m

可见,pod按照序号索引相反的顺序依次终止。StatefulSet控制器会等待pod后继者(注:指序号加1的pod)完全终止,才会终止前一个pod。

注意:尽管级联删除会删除StatefulSet及其pod,但不会删除与StatefulSet关联的headless service。必须手动删除 nginx service。

kubectl delete service nginx

重新创建StatefulSet和headless service:

$ kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1

即使你已经完全删除了StatefulSet及其pod,重新创建pod时,并mount它们的PV,并且 web-0web-1 将继续使用hostname提供服务。

最后,删除 nginx service 和 web StatefulSet:

kubectl delete service nginx

kubectl delete statefulset web

Pod 管理策略

对于某些分布式系统来说,确保StatefulSet的顺序性是不必要和/或不应该的。这些系统仅仅要求唯一性和身份标志。

为了避免这种严格顺序性,可通过 OrderedReady (默认)或 Parallel 来指定pod管理策略。

Parallel pod管理策略

Parallel pod管理策略告诉StatefulSet控制器并行启动或终止所有pod,不必等待pod变成 RunningReady 状态或者完全终止状态,就可以启动或终止另一个pod。该选项只影响缩放行为,不影响更新。

创建文件 web-parallel.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"
  podManagementPolicy: "Parallel"
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        # image: registry.k8s.io/nginx-slim:0.8
        image: docker.io/kaiding1/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      resources:
        requests:
          storage: 1Gi

在监视窗口,重新监视:

$ kubectl get pod -l app=nginx --watch
$ kubectl apply -f web-parallel.yaml
service/nginx created
statefulset.apps/web created

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          0s
web-1   0/1     Pending   0          0s
web-0   0/1     ContainerCreating   0          1s
web-1   0/1     Pending             0          1s
web-1   0/1     ContainerCreating   0          1s
web-1   1/1     Running             0          2s
web-0   1/1     Running             0          2s

可见,StatefulSet控制器几乎同时启动了 web-0web-1

kubectl scale statefulset/web --replicas=4

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          0s
web-1   0/1     Pending   0          0s
web-0   0/1     ContainerCreating   0          1s
web-1   0/1     Pending             0          1s
web-1   0/1     ContainerCreating   0          1s
web-1   1/1     Running             0          2s
web-0   1/1     Running             0          2s
web-2   0/1     Pending             0          0s
web-3   0/1     Pending             0          0s
web-2   0/1     Pending             0          0s
web-3   0/1     Pending             0          0s
web-2   0/1     ContainerCreating   0          0s
web-3   0/1     ContainerCreating   0          0s
web-2   1/1     Running             0          1s
web-3   1/1     Running             0          2s

StatefulSet启动了两个新的pod,而且在启动第二个之前并没有等待第一个变成 RunningReady 状态。

清理

kubectl delete sts web

回到监视窗口,如下:

$ kubectl get pod -l app=nginx --watch
NAME    READY   STATUS    RESTARTS   AGE
web-0   0/1     Pending   0          0s
web-0   0/1     Pending   0          0s
web-1   0/1     Pending   0          0s
web-0   0/1     ContainerCreating   0          1s
web-1   0/1     Pending             0          1s
web-1   0/1     ContainerCreating   0          1s
web-1   1/1     Running             0          2s
web-0   1/1     Running             0          2s
web-2   0/1     Pending             0          0s
web-3   0/1     Pending             0          0s
web-2   0/1     Pending             0          0s
web-3   0/1     Pending             0          0s
web-2   0/1     ContainerCreating   0          0s
web-3   0/1     ContainerCreating   0          0s
web-2   1/1     Running             0          1s
web-3   1/1     Running             0          2s
web-3   1/1     Terminating         0          2m12s
web-1   1/1     Terminating         0          4m25s
web-0   1/1     Terminating         0          4m25s
web-2   1/1     Terminating         0          2m12s
web-3   0/1     Terminating         0          2m12s
web-0   0/1     Terminating         0          4m25s
web-2   0/1     Terminating         0          2m12s
web-1   0/1     Terminating         0          4m25s
web-0   0/1     Terminating         0          4m26s
web-0   0/1     Terminating         0          4m26s
web-0   0/1     Terminating         0          4m26s
web-2   0/1     Terminating         0          2m13s
web-2   0/1     Terminating         0          2m13s
web-2   0/1     Terminating         0          2m13s
web-1   0/1     Terminating         0          4m26s
web-1   0/1     Terminating         0          4m26s
web-1   0/1     Terminating         0          4m26s
web-3   0/1     Terminating         0          2m13s
web-3   0/1     Terminating         0          2m13s
web-3   0/1     Terminating         0          2m13s

可见,StatefulSet并发的删除所有pod。

kubectl delete svc nginx
$ kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-71418599-e0d4-4c45-9f3b-f56ebd992509   1Gi        RWO            standard       23h
www-web-1   Bound    pvc-c6741fad-942a-434a-b2a7-05efe540ca1e   1Gi        RWO            standard       23h
www-web-2   Bound    pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643   1Gi        RWO            standard       19h
www-web-3   Bound    pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64   1Gi        RWO            standard       19h
www-web-4   Bound    pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee   1Gi        RWO            standard       19h
$ kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM               STORAGECLASS   REASON   AGE
pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643   1Gi        RWO            Delete           Bound    default/www-web-2   standard                19h
pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64   1Gi        RWO            Delete           Bound    default/www-web-3   standard                19h
pvc-71418599-e0d4-4c45-9f3b-f56ebd992509   1Gi        RWO            Delete           Bound    default/www-web-0   standard                23h
pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee   1Gi        RWO            Delete           Bound    default/www-web-4   standard                19h
pvc-c6741fad-942a-434a-b2a7-05efe540ca1e   1Gi        RWO            Delete           Bound    default/www-web-1   standard                23h
$ kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deleted
$ kubectl get pvc
No resources found in default namespace.
$ kubectl delete pv pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643 pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64 pvc-71418599-e0d4-4c45-9f3b-f56ebd992509 pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee pvc-c6741fad-942a-434a-b2a7-05efe540ca1e
persistentvolume "pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643" deleted
persistentvolume "pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64" deleted
persistentvolume "pvc-71418599-e0d4-4c45-9f3b-f56ebd992509" deleted
persistentvolume "pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee" deleted
persistentvolume "pvc-c6741fad-942a-434a-b2a7-05efe540ca1e" deleted
$ kubectl get pv
No resources found

参考

  • https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set

你可能感兴趣的:(Kubernetes,kubernetes,statefulset)