StatefulSet Basics
本教程介绍了如何使用有状态集(StatefulSets)来管理应用程序。 它展示了如何创建、删除、扩缩容以及更新有状态集的 Pod。
Before you begin
在开始本教程之前,你应该熟悉以下 Kubernetes 概念:
- Pods
- Cluster DNS
- Headless Services
- PersistentVolumes
- PersistentVolume Provisioning
- The kubectl command line tool
在开始本教程之前,你需要拥有一个 Kubernetes 集群,并且必须配置好 kubectl 命令行工具,以便它能与你的集群进行通信。 建议在至少有两个并非用作控制平面主机的节点的集群上运行本教程。如果你还没有集群,你可以参考kubernetes-basics里面的create-cluster创建集群。
你应该配置 kubectl 以使用一个采用默认命名空间的上下文。如果你正在使用一个现有的集群,请确保使用该集群的默认命名空间进行练习是可行的。 理想情况下,应在一个不运行任何实际工作负载的集群中进行练习。
阅读有关有状态集(StatefulSets)的概念页面也会很有帮助。
注意: 本教程假定你的集群已配置为动态配置持久卷(PersistentVolumes)。你还需要有一个默认的存储类(StorageClass)。 如果你的集群未配置为动态配置存储,那么在开始本教程之前,你将不得不手动配置两个 1 GiB 的卷,并设置好你的集群, 以便这些持久卷能够映射到有状态集(StatefulSet)所定义的持久卷声明(PersistentVolumeClaim)模板上。
Objectives
有状态集(StatefulSets)旨在用于有状态应用程序和分布式系统。然而,在 Kubernetes 上管理有状态应用程序和分布式系统是一个广泛且复杂的话题。 为了展示有状态集的基本特性,同时避免将前面提到的有状态应用程序和分布式系统管理的复杂话题与有状态集的基本特性相混淆,你将使用有状态集来部署一个简单的 Web 应用程序。
- 创建StatefulSet
- 使用StatefulSet管理Pods
- 删除StatefulSet
- 扩容StatefulSet
- 升级StatefulSet's Pods
Creating a StatefulSet
首先,使用以下示例来创建一个有状态集(以及它所依赖的服务)。 该示例与有状态集概念文档中给出的示例类似。 它会创建一个无头服务(headless Service),名为 nginx,用于发布有状态集 web 中各 Pod 的 IP 地址。
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.21
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 命令来监控有状态集(StatefulSet)的 Pod 的创建过程。
# 在终端使用kubectl命令,并使用--watch执行
# 当被要求开始新的watch时,结束这次watch
kubectl get pods --watch -l app=nginx在第二个终端中,使用 kubectl apply 命令来创建无头服务(headless Service)和有状态集(StatefulSet):
kubectl apply -f https://k8s.io/examples/application/web/web.yamlservice/nginx created
statefulset.apps/web created上述命令会创建两个 Pod,每个 Pod 都运行着一个 NGINX Web 服务器。获取 nginx Service……
kubectl get service nginxNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 12s…… 然后获取 web 有状态集,以验证两者是否都已成功创建:
kubectl get statefulset webNAME READY AGE
web 2/2 37sOrdered Pod creation
有状态集(StatefulSet)默认以严格的顺序创建其 Pod。
对于一个具有 n 个副本的有状态集,在部署 Pod 时,它们会按顺序依次创建,顺序是从 NaN 进行编号。 检查第一个终端中 kubectl get 命令的输出。最终,输出结果将会类似于以下示例。
# 不必重新开始一个wath;
# 这个是上文中第一个终端中执行的命令,它会输出下面类似的结果
kubectl get pods --watch -l app=nginxNAME 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 19s
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 18s在本教程的后续部分,你将练习并行启动(有状态集的 Pod)。
注意: 若要配置分配给有状态集中每个 Pod 的整数序号,请参阅 “起始序号(Start ordinal)” 相关内容。
Pods in a StatefulSet
有状态集(StatefulSet)中的 Pod 具有唯一的序号索引和稳定的网络标识。
Examining the Pod's ordinal index
获取有状态集的 Pod:
kubectl get pods -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 1m
web-1 1/1 Running 0 1m正如有状态集(StatefulSet)概念中所提到的,有状态集中的 Pod 具有固定且唯一的标识。这个标识基于有状态集控制器分配给每个 Pod 的唯一序号索引。 Pod 的名称采用 “<有状态集名称>-< 序号索引 >” 的形式。由于 web 有状态集有两个副本,所以它会创建两个 Pod,即 web - 0 和 web - 1。
Using stable network identities
每个 Pod 都基于其序号索引拥有一个稳定的主机名。使用 kubectl exec 命令在每个 Pod 中执行 hostname 命令:
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; doneweb-0
web-1使用 kubectl run 来执行一个容器,该容器提供来自 dnsutils 软件包的 nslookup 命令。通过对 Pod 的主机名使用 nslookup,你可以查看它们在集群内的 DNS 地址:
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm这会启动一个新的 shell。在这个新的 shell 中,运行:
# Run this in the dns-test container shell
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(现在退出容器 shell:输入 exit )
无头服务的规范名称(CNAME)指向服务资源记录(SRV records,对于每个处于 “运行中且就绪” 状态的 Pod 都有一条对应的记录)。 这些服务资源记录又指向包含 Pod 的 IP 地址的地址记录(A record)条目。
在一个终端中,监控有状态集的 Pod:
# Start a new watch
# End this watch when you've seen that the delete is finished
kubectl get pod --watch -l app=nginx在第二个终端中,使用 kubectl delete 命令删除有状态集中的所有 Pod:
kubectl delete pod -l app=nginxpod "web-0" deleted
pod "web-1" deleted等待有状态集重新启动这些 Pod,并且等待两个 Pod 都转换为 “运行中(Running)” 且 “就绪(Ready)” 状态:
# This should already be running
kubectl get pod --watch -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
NAME READY STATUS RESTARTS AGE
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 34s使用 kubectl exec 和 kubectl run 命令来查看 Pod 的主机名以及集群内的 DNS 条目。首先,查看 Pod 的主机名:
for i in 0 1; do kubectl exec web-$i -- sh -c 'hostname'; doneweb-0
web-1然后执行:
kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm这会开启一个新的 shell 环境。在这个新的 shell 里,运行指定的命令。
# Run this in the dns-test container shell
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(现在退出容器 shell:输入 exit ) 这些 Pod 的序号、主机名、服务资源记录(SRV records)以及地址记录(A record)名称都没有改变,但与这些 Pod 相关联的 IP 地址可能已经发生了变化。 在本教程所使用的集群中,IP 地址确实发生了变化。这就是为什么不应该将其他应用程序配置为通过特定 Pod 的 IP 地址来连接有状态集中的 Pod(通过解析 Pod 的主机名来连接 Pod 是可行的), 这一点非常重要。
Discovery for specific Pods in a StatefulSet
如果你需要查找并连接到有状态集(StatefulSet)的活动成员,你应该查询无头服务(headless Service)的规范名称(CNAME)(例如 nginx.default.svc.cluster.local)。 与该 CNAME 相关联的服务资源记录(SRV records)将仅包含处于 “运行中(Running)” 且 “就绪(Ready)” 状态的有状态集中的 Pod。
如果你的应用程序已经实现了用于测试存活状态和就绪状态的连接逻辑,你可以使用 Pod 的 SRV 记录(例如 web-0.nginx.default.svc.cluster.local,web-1.nginx.default.svc.cluster.local), 因为它们是稳定的,并且当 Pod 转换到 “运行中” 且 “就绪” 状态时,你的应用程序将能够发现这些 Pod 的地址。
如果你的应用程序想要找到有状态集中的任意一个健康的 Pod,因此不需要跟踪每个特定的 Pod,你也可以连接到由该有状态集中的 Pod 作为后端支撑的 type: ClusterIP 类型的服务的 IP 地址。 你可以使用用于跟踪该有状态集的同一个服务(在有状态集的 serviceName 中指定),或者使用一个单独的、能够选择正确 Pod 集合的服务。
Writing to stable storage
要获取 web - 0 和 web - 1 这两个 Pod 对应的持久卷声明(PersistentVolumeClaims,PVC),可以使用以下 kubectl 命令:
kubectl get pvc -l app=nginx输出内容类似于:
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 48s
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 48s有状态集(StatefulSet)控制器创建了两个持久卷声明(PersistentVolumeClaims),它们分别绑定到了两个持久卷(PersistentVolumes)上。
由于本教程所使用的集群配置为动态供应持久卷,所以持久卷会自动创建并绑定。
默认情况下,NGINX Web 服务器会从 /usr/share/nginx/html/index.html 文件提供索引页面。有状态集规范中的 volumeMounts 字段确保了 /usr/share/nginx/html 目录由持久卷提供支持。
下面我们将 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'; donefor i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; doneweb-0
web-1注意:
如果在执行上述 curl 命令时看到 403 Forbidden 响应,这意味着你没有权限访问相关资源。这可能是因为使用 hostPath 卷时存在一个小问题, 你需要修复 volumeMounts 挂载目录的权限。可以通过运行以下命令来解决:
for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done在重新尝试上述 curl 命令之前(先执行修改权限的操作)。
在一个终端中监控有状态集(StatefulSet)的 Pod:
# End this watch when you've reached the end of the section.
# At the start of "Scaling a StatefulSet" you'll start a new watch.
kubectl get pod --watch -l app=nginx在第二个终端中,删除有状态集的所有 Pod:
kubectl delete pod -l app=nginxpod "web-0" deleted
pod "web-1" deleted检查第一个终端中 kubectl get 命令的输出内容,并等待所有的 Pod 都转变为 “运行中(Running)” 且 “就绪(Ready)” 状态。
# This should already be running
kubectl get pod --watch -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 0/1 ContainerCreating 0 0s
NAME READY STATUS RESTARTS AGE
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 34s验证 Web 服务器是否继续提供它们各自的主机名作为响应内容:
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; doneweb-0
web-1即使 web-0 和 web-1 被重新调度了,它们仍继续提供各自的主机名(作为网页内容), 这是因为与它们的持久卷声明(PersistentVolumeClaims)相关联的持久卷(PersistentVolumes)会被重新挂载到对应的卷挂载点(volumeMounts)上。 无论 web-0 和 web-1 被调度到哪个节点上,它们的持久卷都会被挂载到合适的挂载点。
Scaling a StatefulSet
有状态集(StatefulSet)的伸缩操作指的是增加或减少副本数量(即水平伸缩)。 这一操作可以通过更新 replicas 字段来实现。你可以使用 kubectl scale 或者 kubectl patch 命令来对有状态集进行伸缩操作。
Scaling up
扩容(向上扩展)意味着添加更多的副本。只要你的应用程序能够在有状态集(StatefulSet)的各个副本之间分配工作,那么新的、数量更多的一组 Pod 就能够处理更多的工作。
在一个终端窗口中,监控有状态集中的 Pod:
# If you already have a watch running, you can continue using that.
# Otherwise, start one.
# End this watch when there are 5 healthy Pods for the StatefulSet
kubectl get pods --watch -l app=nginx在另一个终端窗口中,使用 kubectl scale 命令将有状态集的副本数量扩展到 5 个:
kubectl scale sts web --replicas=5statefulset.apps/web scaled检查第一个终端中 kubectl get 命令的输出内容,并等待新增的三个 Pod 转变为 “运行中(Running)” 且 “就绪(Ready)” 状态。
# This should already be running
kubectl get pod --watch -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 2h
web-1 1/1 Running 0 2h
NAME READY STATUS RESTARTS AGE
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 19s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 ContainerCreating 0 0s
web-3 1/1 Running 0 18s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 ContainerCreating 0 0s
web-4 1/1 Running 0 19s有状态集控制器对副本数量进行了缩放操作。就像创建有状态集时一样,有状态集控制器会按照序号索引依次创建每个 Pod,并且在启动下一个 Pod 之前,它会等待前一个 Pod 进入 “运行中(Running)” 且 “就绪(Ready)” 状态。
Scaling down
缩容意味着减少副本的数量。例如,你可能会这样做,是因为对某个服务的访问流量已经下降了,并且在当前的规模下存在闲置资源。
在一个终端中,监控有状态集的 Pod:
# End this watch when there are only 3 Pods for the StatefulSet
kubectl get pod --watch -l app=nginx在另一个终端中,你可以使用 kubectl patch 命令将有状态集(StatefulSet)的副本数量缩减回三个:
kubectl patch sts web -p '{"spec":{"replicas":3}}'statefulset.apps/web patched等待 web-4 和 web-3 这两个 Pod 转变为 “终止中(Terminating)” 状态。
# This should already be running
kubectl get pods --watch -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3h
web-1 1/1 Running 0 3h
web-2 1/1 Running 0 55s
web-3 1/1 Running 0 36s
web-4 0/1 ContainerCreating 0 18s
NAME READY STATUS RESTARTS AGE
web-4 1/1 Running 0 19s
web-4 1/1 Terminating 0 24s
web-4 1/1 Terminating 0 24s
web-3 1/1 Terminating 0 42s
web-3 1/1 Terminating 0 42sOrdered Pod termination
控制平面会按照 Pod 序号的逆序,一次删除一个 Pod,并且会在删除下一个 Pod 之前,等待当前 Pod 完全关闭。
要获取有状态集的持久卷声明(PersistentVolumeClaims,PVC):
kubectl get pvc -l app=nginxNAME STATUS VOLUME CAPACITY ACCESSMODES AGE
www-web-0 Bound pvc-15c268c7-b507-11e6-932f-42010a800002 1Gi RWO 13h
www-web-1 Bound pvc-15c79307-b507-11e6-932f-42010a800002 1Gi RWO 13h
www-web-2 Bound pvc-e1125b27-b508-11e6-932f-42010a800002 1Gi RWO 13h
www-web-3 Bound pvc-e1176df6-b508-11e6-932f-42010a800002 1Gi RWO 13h
www-web-4 Bound pvc-e11bb5f8-b508-11e6-932f-42010a800002 1Gi RWO 13h仍然存在五个持久卷声明(PersistentVolumeClaims)和五个持久卷(PersistentVolumes)。
当你探究某个 Pod 的稳定存储时,你会发现,当有状态集(StatefulSet)的 Pod 被删除时,挂载到这些 Pod 上的持久卷并不会被删除。
当由于对有状态集进行缩容操作而导致 Pod 被删除时,这种情况依然如此(即挂载到被删除 Pod 上的持久卷不会被删除)。
Updating StatefulSets
有状态集(StatefulSet)控制器支持自动化更新。所采用的更新策略由有状态集 API 对象的 spec.updateStrategy 字段决定。这一特性可用于升级有状态集中 Pod 的容器镜像、资源请求和 / 或限制、标签以及注解等。
存在两种有效的更新策略:滚动更新(RollingUpdate,默认策略)和手动删除更新(OnDelete)。
RollingUpdate
滚动更新(RollingUpdate)策略会按照有状态集(StatefulSet)中 Pod 序号的逆序更新所有 Pod,同时保证有状态集的各项特性。
你可以通过指定 .spec.updateStrategy.rollingUpdate.partition 字段,将使用滚动更新策略的有状态集的更新操作进行分区,本教程后续会让你进行相关实践。
首先,我们来尝试一个简单的滚动更新操作。
在一个终端窗口中,使用 kubectl patch 命令来修改 web 有状态集,再次更改其容器镜像:
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.24"}]'statefulset.apps/web patched在另一个终端中监控有状态集里的 Pod,你可以继续使用 kubectl 命令来实现:
# End this watch when the rollout is complete
#
# If you're not sure, leave it running one more minute
kubectl get pod -l app=nginx --watch输出内容类似于:
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7m
web-1 1/1 Running 0 7m
web-2 1/1 Running 0 8m
web-2 1/1 Terminating 0 8m
web-2 1/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
web-2 0/1 Terminating 0 8m
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 19s
web-1 1/1 Terminating 0 8m
web-1 0/1 Terminating 0 8m
web-1 0/1 Terminating 0 8m
web-1 0/1 Terminating 0 8m
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 6s
web-0 1/1 Terminating 0 7m
web-0 1/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
web-0 0/1 Terminating 0 7m
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 10s有状态集中的 Pod 会按照序号逆序进行更新。有状态集控制器会逐个终止 Pod,并在更新下一个 Pod 之前,等待当前 Pod 转变为 “运行中(Running)” 且 “就绪(Ready)” 状态。需要注意的是,尽管有状态集控制器在序号较大的 Pod 未达到 “运行中(Running)” 且 “就绪(Ready)” 状态时不会继续更新下一个 Pod,但如果在更新过程中某个 Pod 失败,它会将该 Pod 恢复到其现有的版本。
已经完成更新的 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; doneregistry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24现在,有状态集中的所有 Pod 都在运行之前的容器镜像。
注意:
你也可以使用 kubectl rollout status sts/<name> 命令来查看有状态集(StatefulSet)滚动更新的状态。
Staging an update
你可以通过指定 .spec.updateStrategy.rollingUpdate.partition,将使用滚动更新(RollingUpdate)策略的有状态集(StatefulSet)的更新拆分为多个分区。
如需了解更多相关信息,你可以阅读有状态集概念页面中的 “分区滚动更新” 部分。
你可以利用 .spec.updateStrategy.rollingUpdate 中的分区字段来分阶段对有状态集进行更新。对于此次更新,在更改有状态集的 Pod 模板时,你可以保持有状态集中现有的 Pod 不变。然后,你(或者在实际场景中,由某些外部自动化工具)可以触发已准备好的更新。
首先,对 web 有状态集进行补丁操作,为 updateStrategy 字段添加一个分区:
# The value of "partition" determines which ordinals a change applies to
# Make sure to use a number bigger than the last ordinal for the
# StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'statefulset.apps/web patched再次对有状态集进行补丁操作,以更改该有状态集所使用的容器镜像:
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"registry.k8s.io/nginx-slim:0.21"}]'statefulset.apps/web patched删除有状态集中的一个 Pod:
kubectl delete pod web-2pod "web-2" deleted等待替换的 web-2 Pod 进入 “运行中(Running)” 且 “就绪(Ready)” 状态:
# End the watch when you see that web-2 is healthy
kubectl get pod -l app=nginx --watchNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4m
web-1 1/1 Running 0 4m
web-2 0/1 ContainerCreating 0 11s
web-2 1/1 Running 0 18s获取该 Pod 的容器镜像:
kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'registry.k8s.io/nginx-slim:0.24请注意,即使更新策略是 “滚动更新(RollingUpdate)”,有状态集(StatefulSet)还是使用了原始的容器镜像来恢复该 Pod。这是因为该 Pod 的序号小于由 updateStrategy 所指定的分区值。
Rolling out a canary
现在,你要尝试对已分阶段准备好的更改进行金丝雀发布。
你可以通过减小上面指定的分区值来进行金丝雀发布(以测试修改后的模板)。
对有状态集进行补丁操作以减小分区值:
# The value of "partition" should match the highest existing ordinal for
# the StatefulSet
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'statefulset.apps/web patched控制平面会触发对 web-2 的替换操作(具体是先进行优雅删除,一旦删除完成,就创建一个新的 Pod)。
等待新的 web-2 Pod 进入 “运行中(Running)” 且 “就绪(Ready)” 状态。
# This should already be running
kubectl get pod -l app=nginx --watchNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 4m
web-1 1/1 Running 0 4m
web-2 0/1 ContainerCreating 0 11s
web-2 1/1 Running 0 18s获取该 Pod 的容器:
kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'registry.k8s.io/nginx-slim:0.21当你更改分区时,有状态集控制器会自动更新 web - 2 Pod,因为该 Pod 的序号大于或等于分区值。
删除 web - 1 Pod:
kubectl delete pod web-1pod "web-1" deleted等待 web-1 Pod 进入 “运行中(Running)” 且 “就绪(Ready)” 状态。
# This should already be running
kubectl get pod -l app=nginx --watchThe output is similar to:
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 6m
web-1 0/1 Terminating 0 6m
web-2 1/1 Running 0 2m
web-1 0/1 Terminating 0 6m
web-1 0/1 Terminating 0 6m
web-1 0/1 Terminating 0 6m
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 18s获取 web-1 Pod 的容器镜像:
kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'registry.k8s.io/nginx-slim:0.24web - 1 被恢复到其原始配置,因为该 Pod 的序号小于分区值。
当指定了分区时,在更新有状态集的 .spec.template 时,所有序号大于或等于分区值的 Pod 都会被更新。如果一个序号小于分区值的 Pod 被删除或以其他方式终止,它将被恢复到其原始配置。
Phased roll outs
你可以采用与金丝雀发布类似的方式,利用分区滚动更新来进行分阶段发布(例如线性、几何或指数式发布)。若要进行分阶段发布,可将分区设置为你希望控制器暂停更新时对应的序号。
当前分区设置为 2。将分区设置为 0:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'statefulset.apps/web patched等待有状态集中的所有 Pod 都变为 “运行中(Running)” 且 “就绪(Ready)” 状态。
# This should already be running
kubectl get pod -l app=nginx --watch输出内容类似于:
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m
web-1 0/1 ContainerCreating 0 11s
web-2 1/1 Running 0 2m
web-1 1/1 Running 0 18s
web-0 1/1 Terminating 0 3m
web-0 1/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
web-0 0/1 Terminating 0 3m
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 3s获取有状态集中各 Pod 的容器镜像详细信息:
for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; doneregistry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21通过将分区值设置为 0,你使得有状态集能够继续进行更新流程。
OnDelete
你可以通过将 .spec.template.updateStrategy.type 设置为 OnDelete 来为有状态集选择这种更新策略。
对 web 有状态集进行补丁操作,使其使用 OnDelete 更新策略:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete", "rollingUpdate": null}}}'statefulset.apps/web patched当你选择这种更新策略时,若对有状态集的 .spec.template 字段进行了修改,有状态集控制器不会自动更新 Pod。你需要自行管理发布过程,可以手动操作,也可以使用独立的自动化工具。
Deleting StatefulSets
有状态集(StatefulSet)支持非级联删除和级联删除这两种方式。在非级联删除中,当有状态集被删除时,其所属的 Pod 不会被删除。而在级联删除中,有状态集及其所有 Pod 都会被删除。
请阅读《在集群中使用级联删除》一文,以全面了解有关级联删除的相关内容。
Non-cascading delete
在一个终端窗口中,监控有状态集中的 Pod。
# End this watch when there are no Pods for the StatefulSet
kubectl get pods --watch -l app=nginx你可以使用 kubectl delete 命令来删除有状态集(StatefulSet),同时需要确保为该命令提供 --cascade=orphan 参数。此参数会告知 Kubernetes 仅删除有状态集,而不删除其关联的任何 Pod。
kubectl delete statefulset web --cascade=orphanstatefulset.apps "web" deleted要获取 Pod 并检查它们的状态:
kubectl get pods -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 6m
web-1 1/1 Running 0 7m
web-2 1/1 Running 0 5m尽管 web 有状态集已被删除,但所有 Pod 仍处于 “运行中(Running)” 且 “就绪(Ready)” 状态。接下来要删除 web-0 Pod,可使用以下 kubectl 命令:
kubectl delete pod web-0pod "web-0" deleted要获取有状态集(StatefulSet)的 Pod:
kubectl get pods -l app=nginxNAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 7m由于 web 有状态集已经被删除,web - 0 不会被重新启动。现在我们要在一个终端里监控有状态集的 Pod。
# Leave this watch running until the next time you start a watch
kubectl get pods --watch -l app=nginx以下是在第二个终端中重新创建有状态集(StatefulSet)的详细步骤,同时会处理可能出现的服务(Service)已存在的错误情况。
kubectl apply -f https://k8s.io/examples/application/web/web.yamlstatefulset.apps/web created
service/nginx unchanged忽略该错误。它只是表明在尝试创建 nginx 无头服务时,该服务实际上已经存在。
现在查看在第一个终端中运行 kubectl get 命令的输出结果。
# This should already be running
kubectl get pods --watch -l app=nginxNAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 16m
web-2 1/1 Running 0 2m
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 18s
web-2 1/1 Terminating 0 3m
web-2 0/1 Terminating 0 3m
web-2 0/1 Terminating 0 3m
web-2 0/1 Terminating 0 3m当 web 有状态集被重新创建时,它首先重新启动了 web - 0。由于 web - 1 已经处于 “运行中(Running)” 且 “就绪(Ready)” 状态,当 web - 0 也转变为 “运行中(Running)” 且 “就绪(Ready)” 状态后,它会被有状态集接纳。因为你重新创建的有状态集的副本数设置为 2,所以一旦 web - 0 被重新创建,并且确认 web - 1 已经处于 “运行中(Running)” 且 “就绪(Ready)” 状态,web - 2 就会被终止。
现在,让我们再次查看这些 Pod 的 Web 服务器所提供的 index.html 文件内容。可以使用以下 kubectl 命令来实现:
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; doneweb-0
web-1即使你删除了有状态集(StatefulSet)和 web-0 Pod,它仍然提供最初输入到其 index.html 文件中的主机名。这是因为有状态集永远不会删除与 Pod 关联的持久卷(PersistentVolumes)。 当你重新创建有状态集并重新启动 web-0 时,它原来的持久卷会被重新挂载。
Cascading delete
在一个终端窗口中监控有状态集(StatefulSet)里的 Pod:
# Leave this running until the next page section
kubectl get pods --watch -l app=nginx在另一个终端中,再次删除有状态集(StatefulSet)。这次,省略 --cascade=orphan 参数。
kubectl delete statefulset webstatefulset.apps "web" deleted查看在第一个终端中运行的 kubectl get 命令的输出,并等待所有 Pod 转变为 “终止(Terminating)” 状态。
# This should already be running
kubectl get pods --watch -l app=nginxNAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 11m
web-1 1/1 Running 0 27m
NAME READY STATUS RESTARTS AGE
web-0 1/1 Terminating 0 12m
web-1 1/1 Terminating 0 29m
web-0 0/1 Terminating 0 12m
web-0 0/1 Terminating 0 12m
web-0 0/1 Terminating 0 12m
web-1 0/1 Terminating 0 29m
web-1 0/1 Terminating 0 29m
web-1 0/1 Terminating 0 29m正如你在 “缩容” 部分所看到的那样,Pod 会按照其序号索引的逆序一个一个地被终止。在终止一个 Pod 之前,有状态集控制器会等待该 Pod 的后继 Pod 被完全终止。
注意: 尽管级联删除会将有状态集及其 Pod 一并删除,但这种级联操作不会删除与该有状态集相关联的无头服务。你必须手动删除 nginx 服务。
kubectl delete service nginxservice "nginx" deleted重新创建有状态集(StatefulSet)和无头服务(Headless Service):
kubectl apply -f https://k8s.io/examples/application/web/web.yamlservice/nginx created
statefulset.apps/web created当有状态集的所有 Pod 都转变为 “运行中(Running)” 且 “就绪(Ready)” 状态后,我们可以使用 kubectl exec 命令来获取它们 index.html 文件的内容。
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; doneweb-0
web-1即使你完全删除了有状态集及其所有的 Pod,当这些 Pod 被重新创建时,它们的持久卷(PersistentVolumes)会被重新挂载,并且 web-0 和 web-1 会继续提供它们之前设置的主机名服务。
最后,删除 nginx 服务。你可以使用以下 kubectl 命令来删除 nginx 服务和web 有状态集:
kubectl delete service nginxservice "nginx" deletedkubectl delete statefulset webstatefulset "web" deletedPod management policy
对于某些分布式系统而言,有状态集(StatefulSet)所提供的顺序保证可能并非必要,甚至可能不受欢迎。这些系统仅仅需要 Pod 的唯一性和标识性。
你可以通过指定 Pod 管理策略来避免这种严格的顺序约束。Kubernetes 提供了两种 Pod 管理策略可供选择:
OrderedReady Pod management
“OrderedReady”(有序就绪)的 Pod 管理方式是有状态集(StatefulSets)的默认设置。它指示有状态集控制器遵循上述所展示的顺序保证。
当你的应用程序需要或期望诸如推出应用程序的新版本之类的更改,按照有状态集所提供的序号(Pod 编号)的严格顺序发生时,就可以使用这种管理方式。换句话说,如果你有 app-0、app-1 和 app-2 这些 Pod,Kubernetes 会首先更新 app-0 并对其进行检查。一旦检查通过,Kubernetes 就会更新 app-1,最后更新 app-2。
如果你再添加两个 Pod,Kubernetes 会先设置 app-3,并在部署 app-4 之前等待 app-3 运行状况良好。
由于这是默认设置,所以你实际上已经练习过使用它了。
Parallel Pod management
另一种选择是 “Parallel”(并行)Pod 管理方式,它指示有状态集控制器并行地启动或终止所有的 Pod,并且在启动或终止另一个 Pod 之前,无需等待当前 Pod 变为 “运行且就绪(Running and Ready)” 状态,或者无需等待其完全终止。
“Parallel” Pod 管理选项仅会影响伸缩操作的行为。更新操作不受此影响;Kubernetes 仍然会按顺序逐步推出更改。在本教程中,应用程序非常简单:这是一个 Web 服务器,它会告诉你它的主机名(因为这是一个有状态集,所以每个 Pod 的主机名是不同且可预测的)。
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.24
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi以下是在一个终端中监控有状态集中 Pod 的操作步骤。由于提到的清单文件除了 web 有状态集的 .spec.podManagementPolicy 设置为 Parallel 外,与之前下载的相同,我们可以按照以下方式监控 Pod:
# Leave this watch running until the end of the section
kubectl get pod -l app=nginx --watch以下是在另一个终端重新配置有状态集(StatefulSet)以使用并行(Parallel)Pod 管理策略的步骤:
kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yamlservice/nginx updated
statefulset.apps/web updated保持运行监控命令的那个终端处于打开状态。在另一个终端窗口中,对有状态集进行伸缩操作:
kubectl scale statefulset/web --replicas=5statefulset.apps/web scaled查看运行 kubectl get 命令的终端的输出内容。输出可能会类似于以下内容:
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 7s
web-3 0/1 ContainerCreating 0 7s
web-2 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-2 1/1 Running 0 8s
web-4 0/1 ContainerCreating 0 4s
web-3 1/1 Running 0 26s
web-4 1/1 Running 0 2s有状态集启动了三个新的 Pod,并且在启动第二个和第三个 Pod 之前,它没有等待第一个 Pod 变为 “运行且就绪” 状态。
如果您的工作负载具有有状态的元素,或者需要 Pod 能够通过可预测的命名相互识别,特别是当您有时需要快速提供更多的容量时,这种方法会很有用。如果本教程中的这个简单 Web 服务突然每分钟额外收到 100 万个请求,那么您会希望运行更多的 Pod,但您也不想等待每个新 Pod 逐个启动。并行启动额外的 Pod 可以缩短从请求额外容量到该容量可供使用之间的时间。
Cleaning up
你应该打开两个终端,以便随时运行 kubectl 命令来进行清理工作。
kubectl delete sts web
# sts is an abbreviation for statefulset你可以使用 kubectl get 命令进行监控,以便查看那些 Pod 被删除的过程。
# end the watch when you've seen what you need to
kubectl get pod -l app=nginx --watchweb-3 1/1 Terminating 0 9m
web-2 1/1 Terminating 0 9m
web-3 1/1 Terminating 0 9m
web-2 1/1 Terminating 0 9m
web-1 1/1 Terminating 0 44m
web-0 1/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-3 0/1 Terminating 0 9m
web-2 0/1 Terminating 0 9m
web-1 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-2 0/1 Terminating 0 9m
web-2 0/1 Terminating 0 9m
web-2 0/1 Terminating 0 9m
web-1 0/1 Terminating 0 44m
web-1 0/1 Terminating 0 44m
web-1 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-0 0/1 Terminating 0 44m
web-3 0/1 Terminating 0 9m
web-3 0/1 Terminating 0 9m
web-3 0/1 Terminating 0 9m在删除过程中,有状态集(StatefulSet)会同时移除所有 Pod;在删除某个 Pod 时,它不会等待该 Pod 序号后续的 Pod 终止。
关闭运行 kubectl get 命令的终端,然后删除 nginx 服务:
kubectl delete svc nginx删除本教程中使用的持久卷(PersistentVolumes)对应的持久存储介质。
kubectl get pvcNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO standard 25m
www-web-1 Bound pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO standard 24m
www-web-2 Bound pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO standard 15m
www-web-3 Bound pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO standard 15m
www-web-4 Bound pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO standard 14mkubectl get pvNAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-0c04d7f0-787a-4977-8da3-d9d3a6d8d752 1Gi RWO Delete Bound default/www-web-3 standard 15m
pvc-2bf00408-d366-4a12-bad0-1869c65d0bee 1Gi RWO Delete Bound default/www-web-0 standard 25m
pvc-b2c73489-e70b-4a4e-9ec1-9eab439aa43e 1Gi RWO Delete Bound default/www-web-4 standard 14m
pvc-ba3bfe9c-413e-4b95-a2c0-3ea8a54dbab4 1Gi RWO Delete Bound default/www-web-1 standard 24m
pvc-cba6cfa6-3a47-486b-a138-db5930207eaf 1Gi RWO Delete Bound default/www-web-2 standard 15mkubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deletedkubectl get pvcNo resources found in default namespace.注意: 你还需要删除本教程中所使用的持久卷(PersistentVolumes)的持久存储介质。 根据你的环境、存储配置以及供应方式,遵循必要的步骤,以确保所有存储都能被回收。