StatefulSet 基础

本教程介绍了如何使用 StatefulSet 来管理应用。 演示了如何创建、删除、扩容/缩容和更新 StatefulSet 的 Pod。

准备开始

在开始本教程之前,你应该熟悉以下 Kubernetes 的概念:

你必须拥有一个 Kubernetes 的集群,且必须配置 kubectl 命令行工具让其与你的集群通信。 建议运行本教程的集群至少有两个节点,且这两个节点不能作为控制平面主机。 如果你还没有集群,你可以通过 Minikube 构建一个你自己的集群,或者你可以使用下面的 Kubernetes 练习环境之一:

你应该配置 kubectl 的上下文使用 default 命名空间。 如果你使用的是现有集群,请确保可以使用该集群的 default 命名空间进行练习。 理想情况下,在没有运行任何实际工作负载的集群中进行练习。

阅读有关 StatefulSet 的概念页面也很有用。

说明:

本教程假设你的集群被配置为动态制备 PersistentVolume 卷, 且有一个默认 StorageClass。 如果没有这样配置,在开始本教程之前,你需要手动准备 2 个 1 GiB 的存储卷, 以便这些 PersistentVolume 可以映射到 StatefulSet 定义的 PersistentVolumeClaim 模板。

教程目标

StatefulSet 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes 上管理有状态应用和分布式系统是一个宽泛而复杂的话题。 为了演示 StatefulSet 的基本特性,并且不使前后的主题混淆,你将会使用 StatefulSet 部署一个简单的 Web 应用。

在阅读本教程后,你将熟悉以下内容:

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

创建 StatefulSet

作为开始,使用如下示例创建一个 StatefulSet(以及它所依赖的 Service)。它和 StatefulSet 概念中的示例相似。 它创建了一个 Headless Service nginx 用来发布 StatefulSet web 中的 Pod 的 IP 地址。

  1. application/web/web.yaml
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx
  5. labels:
  6. app: nginx
  7. spec:
  8. ports:
  9. - port: 80
  10. name: web
  11. clusterIP: None
  12. selector:
  13. app: nginx
  14. ---
  15. apiVersion: apps/v1
  16. kind: StatefulSet
  17. metadata:
  18. name: web
  19. spec:
  20. serviceName: "nginx"
  21. replicas: 2
  22. selector:
  23. matchLabels:
  24. app: nginx
  25. template:
  26. metadata:
  27. labels:
  28. app: nginx
  29. spec:
  30. containers:
  31. - name: nginx
  32. image: registry.k8s.io/nginx-slim:0.8
  33. ports:
  34. - containerPort: 80
  35. name: web
  36. volumeMounts:
  37. - name: www
  38. mountPath: /usr/share/nginx/html
  39. volumeClaimTemplates:
  40. - metadata:
  41. name: www
  42. spec:
  43. accessModes: [ "ReadWriteOnce" ]
  44. resources:
  45. requests:
  46. storage: 1Gi

你需要使用至少两个终端窗口。在第一个终端中,使用 kubectl get 来监视 StatefulSet 的 Pod 的创建情况。

  1. # 使用此终端运行指定 --watch 的命令
  2. # 当你被要求开始一个新的 watch 时结束这个 watch
  3. kubectl get pods --watch -l app=nginx

在另一个终端中,使用 kubectl apply 来创建 Headless Service 和 StatefulSet。

  1. kubectl apply -f https://k8s.io/examples/application/web/web.yaml
  1. service/nginx created
  2. statefulset.apps/web created

上面的命令创建了两个 Pod,每个都运行了一个 NginX Web 服务器。 获取 nginx Service:

  1. kubectl get service nginx
  1. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  2. nginx ClusterIP None <none> 80/TCP 12s

然后获取 web StatefulSet,以验证两者均已成功创建:

  1. kubectl get statefulset web
  1. NAME READY AGE
  2. web 2/2 37s

顺序创建 Pod

StatefulSet 默认以严格的顺序创建其 Pod。

对于一个拥有 n 个副本的 StatefulSet,Pod 被部署时是按照 {0..n-1} 的序号顺序创建的。 在第一个终端中使用 kubectl get 检查输出。这个输出最终将看起来像下面的样子。

  1. # 不要开始一个新的 watch
  2. # 这应该已经处于 Running 状态
  3. kubectl get pods --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 0/1 Pending 0 0s
  3. web-0 0/1 Pending 0 0s
  4. web-0 0/1 ContainerCreating 0 0s
  5. web-0 1/1 Running 0 19s
  6. web-1 0/1 Pending 0 0s
  7. web-1 0/1 Pending 0 0s
  8. web-1 0/1 ContainerCreating 0 0s
  9. web-1 1/1 Running 0 18s

请注意,直到 web-0 Pod 处于 Running(请参阅 Pod 阶段) 并 Ready(请参阅 Pod 状况中的 type)状态后,web-1 Pod 才会被启动。

在本教程的后面部分,你将练习并行启动

说明:

要配置分配给 StatefulSet 中每个 Pod 的整数序号, 请参阅起始序号

StatefulSet 中的 Pod

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

检查 Pod 的顺序索引

获取 StatefulSet 的 Pod:

  1. kubectl get pods -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 1m
  3. web-1 1/1 Running 0 1m

如同 StatefulSet 概念中所提到的, StatefulSet 中的每个 Pod 拥有一个具有黏性的、独一无二的身份标志。 这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。 Pod 名称的格式为 <statefulset 名称>-<序号索引>web StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0web-1

使用稳定的网络身份标识

每个 Pod 都拥有一个基于其顺序索引的稳定的主机名。使用 kubectl exec 在每个 Pod 中执行 hostname

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

使用 kubectl run 运行一个提供 nslookup 命令的容器,该命令来自于 dnsutils 包。 通过对 Pod 的主机名执行 nslookup,你可以检查这些主机名在集群内部的 DNS 地址:

  1. kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

这将启动一个新的 Shell。在新 Shell 中运行:

  1. # 在 dns-test 容器 Shell 中运行以下命令
  2. nslookup web-0.nginx

输出类似于:

  1. Server: 10.0.0.10
  2. Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
  3. Name: web-0.nginx
  4. Address 1: 10.244.1.6
  5. nslookup web-1.nginx
  6. Server: 10.0.0.10
  7. Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
  8. Name: web-1.nginx
  9. Address 1: 10.244.2.6

(现在可以退出容器 Shell:exit

Headless service 的 CNAME 指向 SRV 记录(记录每个 Running 和 Ready 状态的 Pod)。 SRV 记录指向一个包含 Pod IP 地址的记录表项。

在一个终端中监视 StatefulSet 的 Pod:

  1. # 启动一个新的 watch
  2. # 当你看到删除完成后结束这个 watch
  3. kubectl get pod --watch -l app=nginx

在另一个终端中使用 kubectl delete 删除 StatefulSet 中所有的 Pod:

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

等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态:

  1. # 这应该已经处于 Running 状态
  2. kubectl get pod --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 0/1 ContainerCreating 0 0s
  3. NAME READY STATUS RESTARTS AGE
  4. web-0 1/1 Running 0 2s
  5. web-1 0/1 Pending 0 0s
  6. web-1 0/1 Pending 0 0s
  7. web-1 0/1 ContainerCreating 0 0s
  8. web-1 1/1 Running 0 34s

使用 kubectl execkubectl run 查看 Pod 的主机名和集群内部的 DNS 表项。 首先,查看 Pod 的主机名:

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

然后,运行:

  1. kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm

这将启动一个新的 Shell。在新 Shell 中,运行:

  1. # 在 dns-test 容器 Shell 中运行以下命令
  2. nslookup web-0.nginx

输出类似于:

  1. Server: 10.0.0.10
  2. Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
  3. Name: web-0.nginx
  4. Address 1: 10.244.1.7
  5. nslookup web-1.nginx
  6. Server: 10.0.0.10
  7. Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
  8. Name: web-1.nginx
  9. Address 1: 10.244.2.8

(现在可以退出容器 Shell:exit

Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变。 在本教程中使用的集群中它们就改变了。这就是为什么不要在其他应用中使用 StatefulSet 中特定 Pod 的 IP 地址进行连接,这点很重要 (可以通过解析 Pod 的主机名来连接到 Pod)。

发现 StatefulSet 中特定的 Pod

如果你需要查找并连接一个 StatefulSet 的活动成员,你应该查询 Headless Service 的 CNAME。 和 CNAME 相关联的 SRV 记录只会包含 StatefulSet 中处于 Running 和 Ready 状态的 Pod。

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

如果你的应用程序想要在 StatefulSet 中找到任一健康的 Pod, 且不需要跟踪每个特定的 Pod,你还可以连接到由该 StatefulSet 中的 Pod 关联的 type: ClusterIP Service 的 IP 地址。 你可以使用跟踪 StatefulSet 的同一 Service (StatefulSet 中 serviceName 所指定的)或选择正确的 Pod 集的单独 Service。

写入稳定的存储

获取 web-0web-1 的 PersistentVolumeClaims:

  1. kubectl get pvc -l app=nginx

输出类似于:

  1. NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
  2. www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
  3. www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s

StatefulSet 控制器创建了两个 PersistentVolumeClaims, 绑定到两个 PersistentVolumes

由于本教程使用的集群配置为动态制备 PersistentVolume 卷,所有的 PersistentVolume 卷都是自动创建和绑定的。

NginX Web 服务器默认会加载位于 /usr/share/nginx/html/index.html 的 index 文件。 StatefulSet spec 中的 volumeMounts 字段保证了 /usr/share/nginx/html 文件夹由一个 PersistentVolume 卷支持。

将 Pod 的主机名写入它们的 index.html 文件并验证 NginX Web 服务器使用该主机名提供服务:

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

说明:

请注意,如果你看见上面的 curl 命令返回了 403 Forbidden 的响应,你需要像这样修复使用 volumeMounts (原因归咎于使用 hostPath 卷时存在的缺陷) 挂载的目录的权限,先运行:

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

再重新尝试上面的 curl 命令。

在一个终端监视 StatefulSet 的 Pod:

  1. kubectl get pod -w -l app=nginx

在另一个终端删除 StatefulSet 所有的 Pod:

  1. # 当你到达该部分的末尾时结束此 watch
  2. # 在开始“扩展 StatefulSet” 时,你将启动一个新的 watch。
  3. kubectl get pod --watch -l app=nginx
  1. pod "web-0" deleted
  2. pod "web-1" deleted

在第一个终端里检查 kubectl get 命令的输出,等待所有 Pod 变成 Running 和 Ready 状态。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pod --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 0/1 ContainerCreating 0 0s
  3. NAME READY STATUS RESTARTS AGE
  4. web-0 1/1 Running 0 2s
  5. web-1 0/1 Pending 0 0s
  6. web-1 0/1 Pending 0 0s
  7. web-1 0/1 ContainerCreating 0 0s
  8. web-1 1/1 Running 0 34s

验证所有 Web 服务器在继续使用它们的主机名提供服务:

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

虽然 web-0web-1 被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 卷被重新挂载到了各自的 volumeMount 上。 不管 web-0web-1 被调度到了哪个节点上,它们的 PersistentVolume 卷将会被挂载到合适的挂载点上。

扩容/缩容 StatefulSet

扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新 replicas 字段完成(水平缩放)。 你可以使用 kubectl scale 或者 kubectl patch 来扩容/缩容一个 StatefulSet。

扩容

扩容意味着添加更多副本。 如果你的应用程序能够在整个 StatefulSet 范围内分派工作,则新的更大的 Pod 集可以执行更多的工作。

在一个终端窗口监视 StatefulSet 的 Pod:

  1. # 如果你已经有一个正在运行的 wach,你可以继续使用它。
  2. # 否则,就启动一个。
  3. # 当 StatefulSet 有 5 个健康的 Pod 时结束此 watch
  4. kubectl get pods --watch -l app=nginx

在另一个终端窗口使用 kubectl scale 扩展副本数为 5:

  1. kubectl scale sts web --replicas=5
  1. statefulset.apps/web scaled

在第一个 终端中检查 kubectl get 命令的输出,等待增加的 3 个 Pod 的状态变为 Running 和 Ready。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pod --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 2h
  3. web-1 1/1 Running 0 2h
  4. NAME READY STATUS RESTARTS AGE
  5. web-2 0/1 Pending 0 0s
  6. web-2 0/1 Pending 0 0s
  7. web-2 0/1 ContainerCreating 0 0s
  8. web-2 1/1 Running 0 19s
  9. web-3 0/1 Pending 0 0s
  10. web-3 0/1 Pending 0 0s
  11. web-3 0/1 ContainerCreating 0 0s
  12. web-3 1/1 Running 0 18s
  13. web-4 0/1 Pending 0 0s
  14. web-4 0/1 Pending 0 0s
  15. web-4 0/1 ContainerCreating 0 0s
  16. web-4 1/1 Running 0 19s

StatefulSet 控制器扩展了副本的数量。 如同创建 StatefulSet 所述,StatefulSet 按序号索引顺序创建各个 Pod,并且会等待前一个 Pod 变为 Running 和 Ready 才会启动下一个 Pod。

缩容

缩容意味着减少副本数量。 例如,你可能因为服务的流量水平已降低并且在当前规模下存在空闲资源的原因执行缩容操作。

在一个终端监视 StatefulSet 的 Pod:

  1. kubectl get pods -w -l app=nginx
  2. # 当 StatefulSet 只有 3 个 Pod 时结束此 watch
  3. kubectl get pod --watch -l app=nginx

在另一个终端使用 kubectl patch 将 StatefulSet 缩容回三个副本:

  1. kubectl patch sts web -p '{"spec":{"replicas":3}}'
  1. statefulset.apps/web patched

等待 web-4web-3 状态变为 Terminating。

  1. kubectl get pods -w -l app=nginx
  2. # 这应该已经处于 Running 状态
  3. kubectl get pods --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 3h
  3. web-1 1/1 Running 0 3h
  4. web-2 1/1 Running 0 55s
  5. web-3 1/1 Running 0 36s
  6. web-4 0/1 ContainerCreating 0 18s
  7. NAME READY STATUS RESTARTS AGE
  8. web-4 1/1 Running 0 19s
  9. web-4 1/1 Terminating 0 24s
  10. web-4 1/1 Terminating 0 24s
  11. web-3 1/1 Terminating 0 42s
  12. web-3 1/1 Terminating 0 42s

顺序终止 Pod

控制器会按照与 Pod 序号索引相反的顺序每次删除一个 Pod。在删除下一个 Pod 前会等待上一个被完全关闭。

获取 StatefulSet 的 PersistentVolumeClaims:

  1. kubectl get pvc -l app=nginx
  1. NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
  2. www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 13h
  3. www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 13h
  4. www-web-2 Bound pvc-e1125b27-b508-11e6-932f-42010a800002 1Gi RWO 13h
  5. www-web-3 Bound pvc-e1176df6-b508-11e6-932f-42010a800002 1Gi RWO 13h
  6. www-web-4 Bound pvc-e11bb5f8-b508-11e6-932f-42010a800002 1Gi RWO 13h

五个 PersistentVolumeClaims 和五个 PersistentVolume 卷仍然存在。 查看 Pod 的稳定存储,你会发现当删除 StatefulSet 的 Pod 时,挂载到 StatefulSet 的 Pod 的 PersistentVolume 卷不会被删除。 当这种删除行为是由 StatefulSet 缩容引起时也是一样的。

更新 StatefulSet

StatefulSet 控制器支持自动更新。 更新策略由 StatefulSet API 对象的 spec.updateStrategy 字段决定。这个特性能够用来更新一个 StatefulSet 中 Pod 的容器镜像、资源请求和限制、标签和注解。

有两个有效的更新策略:RollingUpdate(默认)和 OnDelete

滚动更新

RollingUpdate 更新策略会更新一个 StatefulSet 中的所有 Pod,采用与序号索引相反的顺序并遵循 StatefulSet 的保证。

你可以通过指定 .spec.updateStrategy.rollingUpdate.partition 将使用 RollingUpdate 策略的 StatefulSet 的更新拆分为多个分区 。你将在本教程中稍后练习此操作。

首先,尝试一个简单的滚动更新。

在一个终端窗口中对 web StatefulSet 执行 patch 操作来再次改变容器镜像:

  1. kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.24"}]'
  1. statefulset.apps/web patched

在另一个终端监控 StatefulSet 中的 Pod:

  1. # 滚动完成后结束此 watch
  2. #
  3. # 如果你不确定,请让它再运行一分钟
  4. kubectl get pod -l app=nginx --watch

输出类似于:

  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 7m
  3. web-1 1/1 Running 0 7m
  4. web-2 1/1 Running 0 8m
  5. web-2 1/1 Terminating 0 8m
  6. web-2 1/1 Terminating 0 8m
  7. web-2 0/1 Terminating 0 8m
  8. web-2 0/1 Terminating 0 8m
  9. web-2 0/1 Terminating 0 8m
  10. web-2 0/1 Terminating 0 8m
  11. web-2 0/1 Pending 0 0s
  12. web-2 0/1 Pending 0 0s
  13. web-2 0/1 ContainerCreating 0 0s
  14. web-2 1/1 Running 0 19s
  15. web-1 1/1 Terminating 0 8m
  16. web-1 0/1 Terminating 0 8m
  17. web-1 0/1 Terminating 0 8m
  18. web-1 0/1 Terminating 0 8m
  19. web-1 0/1 Pending 0 0s
  20. web-1 0/1 Pending 0 0s
  21. web-1 0/1 ContainerCreating 0 0s
  22. web-1 1/1 Running 0 6s
  23. web-0 1/1 Terminating 0 7m
  24. web-0 1/1 Terminating 0 7m
  25. web-0 0/1 Terminating 0 7m
  26. web-0 0/1 Terminating 0 7m
  27. web-0 0/1 Terminating 0 7m
  28. web-0 0/1 Terminating 0 7m
  29. web-0 0/1 Pending 0 0s
  30. web-0 0/1 Pending 0 0s
  31. web-0 0/1 ContainerCreating 0 0s
  32. web-0 1/1 Running 0 10s

StatefulSet 里的 Pod 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet 控制器终止每个 Pod 并等待它们变成 Running 和 Ready。 请注意,虽然在顺序后继者变成 Running 和 Ready 之前 StatefulSet 控制器不会更新下一个 Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们现有的版本。

已经接收到更新请求的 Pod 将会被恢复为更新的版本,没有收到请求的 Pod 则会被恢复为之前的版本。 像这样,控制器尝试继续使应用保持健康并在出现间歇性故障时保持更新的一致性。

获取 Pod 来查看它们的容器镜像:

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

StatefulSet 中的所有 Pod 现在都在运行之前的容器镜像。

说明:

你还可以使用 kubectl rollout status sts/<名称> 来查看 StatefulSet 的滚动更新状态。

分段更新

你可以通过指定 .spec.updateStrategy.rollingUpdate.partition 将使用 RollingUpdate 策略的 StatefulSet 的更新拆分为多个分区

有关更多上下文,你可以阅读 StatefulSet 概念页面中的分区滚动更新

你可以使用 .spec.updateStrategy.rollingUpdate 中的 partition 字段对 StatefulSet 执行更新的分段操作。 对于此更新,你将保持 StatefulSet 中现有 Pod 不变,同时更改 StatefulSet 的 Pod 模板。 然后,你(或通过教程之外的一些外部自动化工具)可以触发准备好的更新。

web StatefulSet 执行 Patch 操作,为 updateStrategy 字段添加一个分区:

  1. # "partition" 的值决定更改适用于哪些序号
  2. # 确保使用比 StatefulSet 的最后一个序号更大的数字
  3. kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
  1. statefulset.apps/web patched

再次 Patch StatefulSet 来改变此 StatefulSet 使用的容器镜像:

  1. kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.21"}]'
  1. statefulset.apps/web patched

删除 StatefulSet 中的 Pod:

  1. kubectl delete pod web-2
  1. pod "web-2" deleted

等待替代的 Pod 变成 Running 和 Ready。

  1. # 当你看到 web-2 运行正常时结束 watch
  2. kubectl get pod -l app=nginx --watch
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 4m
  3. web-1 1/1 Running 0 4m
  4. web-2 0/1 ContainerCreating 0 11s
  5. web-2 1/1 Running 0 18s

获取 Pod 的容器镜像:

  1. kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
  1. registry.k8s.io/nginx-slim:0.24

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

金丝雀发布

现在,你将尝试对分段的变更进行金丝雀发布

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

通过 patch 命令修改 StatefulSet 来减少分区:

  1. # “partition” 的值应与 StatefulSet 现有的最高序号相匹配
  2. kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
  1. statefulset.apps/web patched

控制平面会触发 web-2 的替换(先优雅地 删除 现有 Pod,然后在删除完成后创建一个新的 Pod)。 等待新的 web-2 Pod 变成 Running 和 Ready。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pod -l app=nginx --watch
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 4m
  3. web-1 1/1 Running 0 4m
  4. web-2 0/1 ContainerCreating 0 11s
  5. web-2 1/1 Running 0 18s

获取 Pod 的容器:

  1. kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
  1. registry.k8s.io/nginx-slim:0.21

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

删除 web-1 Pod:

  1. kubectl delete pod web-1
  1. pod "web-1" deleted

等待 web-1 变成 Running 和 Ready。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pod -l app=nginx --watch

输出类似于:

  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 6m
  3. web-1 0/1 Terminating 0 6m
  4. web-2 1/1 Running 0 2m
  5. web-1 0/1 Terminating 0 6m
  6. web-1 0/1 Terminating 0 6m
  7. web-1 0/1 Terminating 0 6m
  8. web-1 0/1 Pending 0 0s
  9. web-1 0/1 Pending 0 0s
  10. web-1 0/1 ContainerCreating 0 0s
  11. web-1 1/1 Running 0 18s

获取 web-1 Pod 的容器镜像:

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

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

分阶段的发布

你可以使用类似金丝雀发布的方法执行一次分阶段的发布 (例如一次线性的、等比的或者指数形式的发布)。 要执行一次分阶段的发布,你需要设置 partition 为希望控制器暂停更新的序号。

分区当前为 2,请将其设置为 0

  1. kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
  1. statefulset.apps/web patched

等待 StatefulSet 中的所有 Pod 变成 Running 和 Ready。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pod -l app=nginx --watch

输出类似于:

  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 3m
  3. web-1 0/1 ContainerCreating 0 11s
  4. web-2 1/1 Running 0 2m
  5. web-1 1/1 Running 0 18s
  6. web-0 1/1 Terminating 0 3m
  7. web-0 1/1 Terminating 0 3m
  8. web-0 0/1 Terminating 0 3m
  9. web-0 0/1 Terminating 0 3m
  10. web-0 0/1 Terminating 0 3m
  11. web-0 0/1 Terminating 0 3m
  12. web-0 0/1 Pending 0 0s
  13. web-0 0/1 Pending 0 0s
  14. web-0 0/1 ContainerCreating 0 0s
  15. web-0 1/1 Running 0 3s

获取 StatefulSet 中 Pod 的容器镜像详细信息:

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

partition 改变为 0 以允许 StatefulSet 继续更新过程。

OnDelete 策略

通过将 .spec.template.updateStrategy.type 设置为 OnDelete,你可以为 StatefulSet 选择此更新策略。

web StatefulSet 执行 patch 操作,以使用 OnDelete 更新策略:

  1. kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete"}}}'
  1. statefulset.apps/web patched

当你选择这个更新策略并修改 StatefulSet 的 .spec.template 字段时,StatefulSet 控制器将不会自动更新 Pod。 你需要自己手动管理发布,或使用单独的自动化工具来管理发布。

删除 StatefulSet

StatefulSet 同时支持非级联级联删除。使用非级联方式删除 StatefulSet 时,StatefulSet 的 Pod 不会被删除。使用级联删除时,StatefulSet 和它的 Pod 都会被删除。

阅读在集群中使用级联删除, 以了解通用的级联删除。

非级联删除

在一个终端窗口监视 StatefulSet 中的 Pod。

  1. # 当 StatefulSet 没有 Pod 时结束此 watch
  2. kubectl get pods --watch -l app=nginx

使用 kubectl delete 删除 StatefulSet。请确保提供了 --cascade=orphan 参数给命令。这个参数告诉 Kubernetes 只删除 StatefulSet 而不要删除它的任何 Pod。

  1. kubectl delete statefulset web --cascade=orphan
  1. statefulset.apps "web" deleted

获取 Pod 来检查它们的状态:

  1. kubectl get pods -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 6m
  3. web-1 1/1 Running 0 7m
  4. web-2 1/1 Running 0 5m

虽然 web 已经被删除了,但所有 Pod 仍然处于 Running 和 Ready 状态。 删除 web-0

  1. kubectl delete pod web-0
  1. pod "web-0" deleted

获取 StatefulSet 的 Pod:

  1. kubectl get pods -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-1 1/1 Running 0 10m
  3. web-2 1/1 Running 0 7m

由于 web StatefulSet 已经被删除,web-0 没有被重新启动。

在一个终端监控 StatefulSet 的 Pod。

  1. # 让 watch 一直运行到你下次启动 watch 为止
  2. kubectl get pods --watch -l app=nginx

在另一个终端里重新创建 StatefulSet。请注意,除非你删除了 nginx Service(你不应该这样做),你将会看到一个错误,提示 Service 已经存在。

  1. kubectl apply -f https://k8s.io/examples/application/web/web.yaml
  1. statefulset.apps/web created
  2. service/nginx unchanged

请忽略这个错误。它仅表示 kubernetes 进行了一次创建 nginx Headless Service 的尝试,尽管那个 Service 已经存在。

在第一个终端中运行并检查 kubectl get 命令的输出。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pods --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-1 1/1 Running 0 16m
  3. web-2 1/1 Running 0 2m
  4. NAME READY STATUS RESTARTS AGE
  5. web-0 0/1 Pending 0 0s
  6. web-0 0/1 Pending 0 0s
  7. web-0 0/1 ContainerCreating 0 0s
  8. web-0 1/1 Running 0 18s
  9. web-2 1/1 Terminating 0 3m
  10. web-2 0/1 Terminating 0 3m
  11. web-2 0/1 Terminating 0 3m
  12. web-2 0/1 Terminating 0 3m

当重新创建 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 将会被终止。

现在再看看被 Pod 的 Web 服务器加载的 index.html 的内容:

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

尽管你同时删除了 StatefulSet 和 web-0 Pod,但它仍然使用最初写入 index.html 文件的主机名进行服务。 这是因为 StatefulSet 永远不会删除和一个 Pod 相关联的 PersistentVolume 卷。 当你重建这个 StatefulSet 并且重新启动了 web-0 时,它原本的 PersistentVolume 卷会被重新挂载。

级联删除

在一个终端窗口监视 StatefulSet 里的 Pod。

  1. # 让它运行直到下一页部分
  2. kubectl get pods --watch -l app=nginx

在另一个窗口中再次删除这个 StatefulSet,这次省略 --cascade=orphan 参数。

  1. kubectl delete statefulset web
  1. statefulset.apps "web" deleted

在第一个终端检查 kubectl get 命令的输出,并等待所有的 Pod 变成 Terminating 状态。

  1. # 这应该已经处于 Running 状态
  2. kubectl get pods --watch -l app=nginx
  1. NAME READY STATUS RESTARTS AGE
  2. web-0 1/1 Running 0 11m
  3. web-1 1/1 Running 0 27m
  4. NAME READY STATUS RESTARTS AGE
  5. web-0 1/1 Terminating 0 12m
  6. web-1 1/1 Terminating 0 29m
  7. web-0 0/1 Terminating 0 12m
  8. web-0 0/1 Terminating 0 12m
  9. web-0 0/1 Terminating 0 12m
  10. web-1 0/1 Terminating 0 29m
  11. web-1 0/1 Terminating 0 29m
  12. web-1 0/1 Terminating 0 29m

如同你在缩容章节看到的,这些 Pod 按照与其序号索引相反的顺序每次终止一个。 在终止一个 Pod 前,StatefulSet 控制器会等待 Pod 后继者被完全终止。

说明:

尽管级联删除会删除 StatefulSet 及其 Pod,但级联不会删除与 StatefulSet 关联的 Headless Service。你必须手动删除 nginx Service。

  1. kubectl delete service nginx
  1. service "nginx" deleted

再一次重新创建 StatefulSet 和 Headless Service:

  1. kubectl apply -f https://k8s.io/examples/application/web/web.yaml
  1. service/nginx created
  2. statefulset.apps/web created

当 StatefulSet 所有的 Pod 变成 Running 和 Ready 时,获取它们的 index.html 文件的内容:

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

即使你已经删除了 StatefulSet 和它的全部 Pod,这些 Pod 将会被重新创建并挂载它们的 PersistentVolume 卷,并且 web-0web-1 将继续使用它的主机名提供服务。

最后删除 nginx Service:

  1. kubectl delete service nginx
  1. service "nginx" deleted

并且删除 web StatefulSet:

  1. kubectl delete statefulset web
  1. statefulset "web" deleted

Pod 管理策略

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

你可以指定 Pod 管理策略 以避免这个严格的顺序; 你可以选择 OrderedReady(默认)或 Parallel

OrderedReady Pod 管理策略

OrderedReady Pod 管理策略是 StatefulSet 的默认选项。它告诉 StatefulSet 控制器遵循上文展示的顺序性保证。

当你的应用程序需要或期望变更(例如推出应用程序的新版本)按照 StatefulSet 提供的序号(Pod 编号)的严格顺序发生时,请使用此选项。 换句话说,如果你已经有了 Pod app-0app-1app-2,Kubernetes 将首先更新 app-0 并检查它。 一旦检查良好,Kubernetes 就会更新 app-1,最后更新 app-2

如果你再添加两个 Pod,Kubernetes 将设置 app-3 并等待其正常运行,然后再部署 app-4

因为这是默认设置,所以你已经在练习使用它,本教程不会让你再次执行类似的步骤。

Parallel Pod 管理策略

另一种选择,Parallel Pod 管理策略告诉 StatefulSet 控制器并行的终止所有 Pod, 在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。

Parallel Pod 管理选项仅影响扩缩容操作的行为。 变更操作不受其影响;Kubernetes 仍然按顺序推出变更。 对于本教程,应用本身非常简单:它是一个告诉你其主机名的网络服务器(因为这是一个 StatefulSet,每个 Pod 的主机名都是不同的且可预测的)。

  1. application/web/web-parallel.yaml
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx
  5. labels:
  6. app: nginx
  7. spec:
  8. ports:
  9. - port: 80
  10. name: web
  11. clusterIP: None
  12. selector:
  13. app: nginx
  14. ---
  15. apiVersion: apps/v1
  16. kind: StatefulSet
  17. metadata:
  18. name: web
  19. spec:
  20. serviceName: "nginx"
  21. podManagementPolicy: "Parallel"
  22. replicas: 2
  23. selector:
  24. matchLabels:
  25. app: nginx
  26. template:
  27. metadata:
  28. labels:
  29. app: nginx
  30. spec:
  31. containers:
  32. - name: nginx
  33. image: registry.k8s.io/nginx-slim:0.8
  34. ports:
  35. - containerPort: 80
  36. name: web
  37. volumeMounts:
  38. - name: www
  39. mountPath: /usr/share/nginx/html
  40. volumeClaimTemplates:
  41. - metadata:
  42. name: www
  43. spec:
  44. accessModes: [ "ReadWriteOnce" ]
  45. resources:
  46. requests:
  47. storage: 1Gi

这份清单和你在上文下载的完全一样,只是 web StatefulSet 的 .spec.podManagementPolicy 设置成了 Parallel

在一个终端窗口监视 StatefulSet 中的 Pod。

  1. # 让 watch 一直运行直到本节结束
  2. kubectl get pod -l app=nginx --watch

在另一个终端中,重新配置 StatefulSet 以进行 Parallel Pod 管理:

  1. kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yaml
  1. service/nginx updated
  2. statefulset.apps/web updated

保持你运行监视进程的终端为打开状态,并在另一个终端窗口中扩容 StatefulSet:

  1. kubectl scale statefulset/web --replicas=5
  1. statefulset.apps/web scaled

kubectl get 命令运行的终端里检查它的输出。它可能看起来像:

  1. web-3 0/1 Pending 0 0s
  2. web-3 0/1 Pending 0 0s
  3. web-3 0/1 Pending 0 7s
  4. web-3 0/1 ContainerCreating 0 7s
  5. web-2 0/1 Pending 0 0s
  6. web-4 0/1 Pending 0 0s
  7. web-2 1/1 Running 0 8s
  8. web-4 0/1 ContainerCreating 0 4s
  9. web-3 1/1 Running 0 26s
  10. web-4 1/1 Running 0 2s

StatefulSet 启动了三个新的 Pod,而且在启动第二和第三个之前并没有等待第一个变成 Running 和 Ready 状态。

如果你的工作负载具有有状态元素,或者需要 Pod 能够通过可预测的命名来相互识别, 特别是当你有时需要快速提供更多容量时,此方法非常有用。 如果本教程的这个简单 Web 服务突然每分钟收到额外 1,000,000 个请求, 那么你可能会想要运行更多 Pod,但你也不想等待每个新 Pod 启动。 并行启动额外的 Pod 可以缩短请求额外容量和使其可供使用之间的时间。

清理现场

你应该打开两个终端,准备在清理过程中运行 kubectl 命令。

  1. kubectl delete sts web
  2. # sts is an abbreviation for statefulset

你可以监视 kubectl get 来查看那些 Pod 被删除:

  1. # 当你看到需要的内容后结束 watch
  2. kubectl get pod -l app=nginx --watch
  1. web-3 1/1 Terminating 0 9m
  2. web-2 1/1 Terminating 0 9m
  3. web-3 1/1 Terminating 0 9m
  4. web-2 1/1 Terminating 0 9m
  5. web-1 1/1 Terminating 0 44m
  6. web-0 1/1 Terminating 0 44m
  7. web-0 0/1 Terminating 0 44m
  8. web-3 0/1 Terminating 0 9m
  9. web-2 0/1 Terminating 0 9m
  10. web-1 0/1 Terminating 0 44m
  11. web-0 0/1 Terminating 0 44m
  12. web-2 0/1 Terminating 0 9m
  13. web-2 0/1 Terminating 0 9m
  14. web-2 0/1 Terminating 0 9m
  15. web-1 0/1 Terminating 0 44m
  16. web-1 0/1 Terminating 0 44m
  17. web-1 0/1 Terminating 0 44m
  18. web-0 0/1 Terminating 0 44m
  19. web-0 0/1 Terminating 0 44m
  20. web-0 0/1 Terminating 0 44m
  21. web-3 0/1 Terminating 0 9m
  22. web-3 0/1 Terminating 0 9m
  23. web-3 0/1 Terminating 0 9m

在删除过程中,StatefulSet 将并发的删除所有 Pod,在删除一个 Pod 前不会等待它的顺序后继者终止。

关闭 kubectl get 命令运行的终端并删除 nginx Service:

  1. kubectl delete svc nginx

删除本教程中用到的 PersistentVolume 卷的持久化存储介质:

  1. kubectl get pvc
  1. NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
  2. www-web-0 Bound pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO standard 25m
  3. www-web-1 Bound pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO standard 24m
  4. www-web-2 Bound pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO standard 15m
  5. www-web-3 Bound pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO standard 15m
  6. www-web-4 Bound pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO standard 14m
  1. kubectl get pv
  1. NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
  2. pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO Delete Bound default/www-web-3 standard 15m
  3. pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO Delete Bound default/www-web-0 standard 25m
  4. pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO Delete Bound default/www-web-4 standard 14m
  5. pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO Delete Bound default/www-web-1 standard 24m
  6. pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO Delete Bound default/www-web-2 standard 15m
  1. kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4
  1. persistentvolumeclaim "www-web-0" deleted
  2. persistentvolumeclaim "www-web-1" deleted
  3. persistentvolumeclaim "www-web-2" deleted
  4. persistentvolumeclaim "www-web-3" deleted
  5. persistentvolumeclaim "www-web-4" deleted
  1. kubectl get pvc
  1. No resources found in default namespace.

说明:

你需要删除本教程中用到的 PersistentVolume 卷的持久化存储介质。

基于你的环境、存储配置和制备方式,按照必需的步骤保证回收所有的存储。