StatefulSet 基础知识
本教程介绍了如何使用StatefulSets管理应用程序。它演示了如何创建、删除、扩缩和更新 StatefulSet 的 Pod。
准备工作
开始本教程之前,你应该熟悉以下 Kubernetes 概念:
你需要有一个 Kubernetes 集群,并且 kubectl 命令行工具已被配置为与你的集群通信。建议你在至少有两个节点(不充当控制平面主机)的集群上运行本教程。如果你还没有集群,可以使用 minikube 创建一个,或者使用以下 Kubernetes 实验环境之一:
你应该将 kubectl
配置为使用 default
命名空间的上下文。如果你使用现有集群,请确保可以使用该集群的默认命名空间进行练习。理想情况下,在不运行任何实际工作负载的集群中进行练习。
阅读关于StatefulSets的概念页面也很有帮助。
说明
本教程假设你的集群配置为动态制备 PersistentVolumes。你还需要有一个默认 StorageClass。如果你的集群未配置为动态制备存储,你必须在本教程开始之前手动制备两个 1 GiB 卷,并设置你的集群以便这些 PersistentVolumes 映射到 StatefulSet 定义的 PersistentVolumeClaim 模板。目标
StatefulSets 旨在用于有状态应用程序和分布式系统。然而,在 Kubernetes 上管理有状态应用程序和分布式系统是一个广泛而复杂的话题。为了演示 StatefulSet 的基本特性,而不是将前一个话题与后一个话题混淆,你将使用 StatefulSet 部署一个简单的 Web 应用程序。
完成本教程后,你将熟悉以下内容:
- 如何创建一个 StatefulSet
- StatefulSet 如何管理其 Pod
- 如何删除一个 StatefulSet
- 如何扩缩一个 StatefulSet
- 如何更新 StatefulSet 的 Pod
创建 StatefulSet
首先使用以下示例创建一个 StatefulSet(及其依赖的 Service)。它与 StatefulSets 概念中给出的示例相似。它创建一个无头 Service,名为 nginx
,用于发布 StatefulSet 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 的创建过程。
# use this terminal to run commands that specify --watch
# end this watch when you are asked to start a new watch
kubectl get pods --watch -l app=nginx
在第二个终端中,使用 kubectl apply
创建无头 Service 和 StatefulSet:
kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created
上述命令创建了两个 Pod,每个 Pod 运行一个 NGINX Web 服务器。获取 nginx
Service:
kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None <none> 80/TCP 12s
...然后获取 web
StatefulSet,以验证两者都已成功创建:
kubectl get statefulset web
NAME READY AGE
web 2/2 37s
有序的 Pod 创建
StatefulSet 默认按严格的顺序创建其 Pod。
对于一个副本数为 n 的 StatefulSet,部署 Pod 时,它们会按 {0..n-1} 的顺序依次创建。检查第一个终端中 kubectl get
命令的输出。最终,输出将如下例所示。
# Do not start a new watch;
# this should already be running
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 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
请注意,web-1
Pod 直到 web-0
Pod 处于 Running 状态(请参阅Pod Phase)并且是 Ready(请参阅Pod Conditions 中的 type
字段)之后才会启动。
在本教程稍后,你将练习并行启动。
说明
要配置分配给 StatefulSet 中每个 Pod 的整数序号,请参阅Start ordinal。StatefulSet 中的 Pod
StatefulSet 中的 Pod 具有唯一的序号索引和稳定的网络标识。
检查 Pod 的序号索引
获取 StatefulSet 的 Pod:
kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 1m
web-1 1/1 Running 0 1m
如StatefulSets概念中所述,StatefulSet 中的 Pod 具有粘性(sticky)且唯一的身份。此身份基于由 StatefulSet 控制器分配给每个 Pod 的唯一序号索引。
Pod 的名称形式为 <StatefulSet name>-<序号索引>
。由于 web
StatefulSet 有两个副本,它会创建两个 Pod,web-0
和 web-1
。
使用稳定的网络身份
每个 Pod 都根据其序号索引拥有一个稳定的主机名。使用 kubectl exec
在每个 Pod 中执行 hostname
命令:
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-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 记录(每个处于 Running 和 Ready 状态的 Pod 各一个)。SRV 记录指向包含 Pod IP 地址的 A 记录条目。
在一个终端中,监听 StatefulSet 的 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
删除 StatefulSet 中的所有 Pod:
kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
等待 StatefulSet 重启它们,并等待两个 Pod 都转换到 Running 和 Ready 状态。
# This should already be running
kubectl get pod --watch -l app=nginx
NAME 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'; done
web-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 记录和 A 记录名称都没有改变,但与 Pod 相关联的 IP 地址可能已经改变。在本教程使用的集群中,它们确实改变了。这就是为什么不应将其他应用程序配置为通过特定 Pod 的 IP 地址连接到 StatefulSet 中的 Pod(可以通过解析其主机名来连接 Pod)是很重要的。
StatefulSet 中特定 Pod 的发现
如果你需要查找和连接到 StatefulSet 的活动成员,你应该查询无头 Service 的 CNAME(nginx.default.svc.cluster.local
)。与 CNAME 相关联的 SRV 记录将只包含 StatefulSet 中处于 Running 和 Ready 状态的 Pod。
如果你的应用程序已经实现了用于测试 liveness 和 readiness 的连接逻辑,你可以使用 Pod 的 SRV 记录(web-0.nginx.default.svc.cluster.local
,web-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-0
和 web-1
的 PersistentVolumeClaims:
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。
由于本教程使用的集群被配置为动态制备 PersistentVolumes,PersistentVolumes 已自动创建并绑定。
NGINX Web 服务器默认从 /usr/share/nginx/html/index.html
提供索引文件。StatefulSet 的 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
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl https:///; done
web-0
web-1
说明
如果你看到上述 curl 命令的 403 Forbidden 响应,你需要修复由 volumeMounts
挂载的目录的权限(由于使用 hostPath 卷时的一个 bug),通过运行:
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
在第二个终端中,删除 StatefulSet 的所有 Pod:
kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
检查第一个终端中 kubectl get
命令的输出,等待所有 Pod 转换到 Running 和 Ready 状态。
# This should already be running
kubectl get pod --watch -l app=nginx
NAME 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 https:///; done
web-0
web-1
尽管 web-0
和 web-1
被重新调度,它们仍然提供它们的主机名,因为与它们的 PersistentVolumeClaims 关联的 PersistentVolumes 被重新挂载到它们的 volumeMounts
。无论 web-0
和 web-1
调度到哪个节点上,它们的 PersistentVolumes 都将挂载到适当的挂载点。
扩缩 StatefulSet
扩缩 StatefulSet 是指增加或减少副本数量(水平扩缩)。这通过更新 replicas
字段来实现。你可以使用 kubectl scale
或 kubectl patch
来扩缩 StatefulSet。
扩容
扩容意味着增加更多副本。前提是你的应用程序能够在 StatefulSet 中分发工作负载,新的较大 Pod 集合可以执行更多工作。
在一个终端窗口中,监听 StatefulSet 中的 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=5
statefulset.apps/web scaled
检查第一个终端中 kubectl get
命令的输出,等待另外三个 Pod 转换到 Running 和 Ready 状态。
# This should already be running
kubectl get pod --watch -l app=nginx
NAME 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
StatefulSet 控制器扩缩了副本数量。与StatefulSet 创建一样,StatefulSet 控制器按照序号索引依次创建每个 Pod,并在启动后续 Pod 之前等待每个 Pod 的前驱 Pod 达到 Running 和 Ready 状态。
缩容
缩容意味着减少副本数量。例如,你可能因为服务的流量减少而这样做,当前规模下存在空闲资源。
在一个终端中,监听 StatefulSet 的 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
转换为 Terminating 状态。
# This should already be running
kubectl get pods --watch -l app=nginx
NAME 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 42s
有序的 Pod 终止
控制平面一次删除一个 Pod,按照序号索引的逆序进行,并在删除下一个 Pod 之前等待每个 Pod 完全关闭。
获取 StatefulSet 的 PersistentVolumeClaims:
kubectl get pvc -l app=nginx
NAME 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 关联的 PersistentVolumes 在 StatefulSet 的 Pod 被删除时并不会被删除。当 Pod 删除是由 StatefulSet 缩容引起的时,情况仍然如此。
更新 StatefulSets
StatefulSet 控制器支持自动化更新。使用的策略由 StatefulSet API 对象的 spec.updateStrategy
字段决定。此功能可用于升级 StatefulSet 中 Pod 的容器镜像、资源请求和/或限制、标签和注解。
有两种有效的更新策略:RollingUpdate
(默认)和 OnDelete
。
滚动更新 (RollingUpdate)
RollingUpdate
更新策略将按照序号逆序更新 StatefulSet 中的所有 Pod,同时遵守 StatefulSet 的保证。
通过指定 .spec.updateStrategy.rollingUpdate.partition
,你可以将使用 RollingUpdate
策略的 StatefulSet 更新分割为“分区”。你将在本教程稍后练习此操作。
首先,尝试一个简单的滚动更新。
在一个终端窗口中,再次修补 web
StatefulSet 以更改容器镜像:
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
在另一个终端中,监听 StatefulSet 中的 Pod:
# 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
StatefulSet 中的 Pod 按照序号逆序更新。StatefulSet 控制器终止每个 Pod,并等待其转换到 Running 和 Ready 状态后才更新下一个 Pod。请注意,即使 StatefulSet 控制器不会在序号后继 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; done
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24
registry.k8s.io/nginx-slim:0.24
StatefulSet 中的所有 Pod 现在都运行着先前的容器镜像。
说明
你还可以使用kubectl rollout status sts/<name>
查看 StatefulSet 滚动更新的状态:暂存更新
通过在 .spec.updateStrategy.rollingUpdate
中指定 partition
字段,你可以将使用 RollingUpdate
策略的 StatefulSet 更新分割为“分区”。对于此更新,你将保持 StatefulSet 中现有的 Pod 不变,同时更改 StatefulSet 的 Pod 模板。然后你(或者在教程之外,一些外部自动化)可以触发该已准备好的更新。
有关更多背景信息,你可以阅读 StatefulSet 概念页面中的分区滚动更新。
你可以通过使用 .spec.updateStrategy.rollingUpdate
中的 partition
字段来暂存 StatefulSet 的更新。对于此次更新,在更改 StatefulSet 的 Pod 模板时,将保持 StatefulSet 中现有 Pod 不变。然后你(或在教程之外,某些外部自动化)可以触发该已准备好的更新。
首先,修补 web
StatefulSet,向 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
再次修补 StatefulSet,更改此 StatefulSet 使用的容器镜像:
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
删除 StatefulSet 中的一个 Pod:
kubectl delete pod web-2
pod "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 --watch
NAME 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
指定的 partition
。
金丝雀发布
现在你将尝试对暂存的变更进行金丝雀发布。
你可以通过递减你上面指定的 partition
来进行金丝雀发布(以测试修改后的模板)。
修补 StatefulSet 以递减分区:
# 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 --watch
NAME 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
当你更改 partition
时,StatefulSet 控制器自动更新了 web-2
Pod,因为该 Pod 的序号大于或等于 partition
。
删除 web-1
Pod:
kubectl delete pod web-1
pod "web-1" deleted
等待 web-1
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 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.24
web-1
被恢复到其原始配置,因为该 Pod 的序号小于分区。指定分区时,所有序号大于或等于分区的 Pod 在 StatefulSet 的 .spec.template
更新时都会更新。如果一个序号小于分区的 Pod 被删除或以其他方式终止,它将恢复到其原始配置。
分阶段发布
你可以使用分区滚动更新以类似于金丝雀发布的方式执行分阶段发布(例如线性、几何或指数发布)。要执行分阶段发布,将 partition
设置为你希望控制器暂停更新的序号。
分区当前设置为 2
。将分区设置为 0
:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
statefulset.apps/web patched
等待 StatefulSet 中的所有 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
获取 StatefulSet 中 Pod 的容器镜像详情:
for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21
registry.k8s.io/nginx-slim:0.21
通过将 partition
移至 0
,你允许 StatefulSet 继续更新过程。
OnDelete 策略
通过将 .spec.template.updateStrategy.type
设置为 OnDelete
,可以为 StatefulSet 选择此更新策略。
修补 web
StatefulSet 以使用 OnDelete
更新策略:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete", "rollingUpdate": null}}}'
statefulset.apps/web patched
选择此更新策略时,当对 StatefulSet 的 .spec.template
字段进行修改时,StatefulSet 控制器不会自动更新 Pod。你需要自己管理发布过程——无论是手动还是使用单独的自动化工具。
删除 StatefulSets
StatefulSet 支持**非级联**删除和**级联**删除。在非级联**删除**中,StatefulSet 的 Pod 在 StatefulSet 被删除时不会被删除。在级联**删除**中,StatefulSet 及其 Pod 都会被删除。
阅读在集群中使用级联删除以了解有关级联删除的一般信息。
非级联删除
在一个终端窗口中,监听 StatefulSet 中的 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 仅删除 StatefulSet,**不**删除其任何 Pod。
kubectl delete statefulset web --cascade=orphan
statefulset.apps "web" deleted
获取 Pod,查看它们的状态:
kubectl get pods -l app=nginx
NAME 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
:
kubectl delete pod web-0
pod "web-0" deleted
获取 StatefulSet 的 Pod:
kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 7m
由于 web
StatefulSet 已被删除,web-0
没有被重新启动。
在一个终端中,监听 StatefulSet 的 Pod。
# Leave this watch running until the next time you start a watch
kubectl get pods --watch -l app=nginx
在第二个终端中,重新创建 StatefulSet。请注意,除非你删除了 nginx
Service(你不应该删除),否则你会看到一个错误,指示 Service 已经存在。
kubectl apply -f https://k8s.io/examples/application/web/web.yaml
statefulset.apps/web created
service/nginx unchanged
忽略该错误。它仅表示尝试创建 nginx 无头 Service,即使该 Service 已经存在。
检查在第一个终端中运行的 kubectl get
命令的输出。
# This should already be running
kubectl get pods --watch -l app=nginx
NAME 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
StatefulSet 被重新创建时,它首先重新启动了 web-0
。由于 web-1
已经处于 Running 和 Ready 状态,当 web-0
转换到 Running 和 Ready 状态时,它就收养了这个 Pod。由于你重新创建 StatefulSet 时 replicas
等于 2,一旦 web-0
被重新创建,并且一旦确定 web-1
已经 Running 和 Ready,web-2
就被终止了。
现在再次查看由 Pod Web 服务器提供的 index.html
文件内容:
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl https:///; done
web-0
web-1
即使你删除了 StatefulSet 和 web-0
Pod,它仍然提供最初写入其 index.html
文件的主机名。这是因为 StatefulSet 从不删除与 Pod 关联的 PersistentVolumes。当你重新创建 StatefulSet 并重新启动 web-0
时,其原始 PersistentVolume 被重新挂载了。
级联删除
在一个终端窗口中,监听 StatefulSet 中的 Pod。
# Leave this running until the next page section
kubectl get pods --watch -l app=nginx
在另一个终端中,再次删除 StatefulSet。这次,省略 --cascade=orphan
参数。
kubectl delete statefulset web
statefulset.apps "web" deleted
检查在第一个终端中运行的 kubectl get
命令的输出,等待所有 Pod 转换到 Terminating 状态。
# This should already be running
kubectl get pods --watch -l app=nginx
NAME 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 之前,StatefulSet 控制器会等待其后续 Pod 完全终止。
说明
虽然级联删除会删除 StatefulSet 及其 Pod,但级联**不会**删除与 StatefulSet 关联的无头 Service。你必须手动删除nginx
Service。kubectl delete service nginx
service "nginx" deleted
再次重新创建 StatefulSet 和无头 Service:
kubectl apply -f https://k8s.io/examples/application/web/web.yaml
service/nginx created
statefulset.apps/web created
当所有 StatefulSet 的 Pod 都转换到 Running 和 Ready 状态时,获取其 index.html
文件的内容:
for i in 0 1; do kubectl exec -i -t "web-$i" -- curl https:///; done
web-0
web-1
即使你完全删除了 StatefulSet 及其所有 Pod,Pod 仍会重新创建并挂载其 PersistentVolumes,并且 web-0
和 web-1
继续提供其主机名。
最后,删除 nginx
Service...:
kubectl delete service nginx
service "nginx" deleted
...以及 web
StatefulSet:
kubectl delete statefulset web
statefulset "web" deleted
Pod 管理策略
对于某些分布式系统,StatefulSet 的有序保证是不必要的或不期望的。这些系统仅需要唯一性和身份。
你可以指定一个Pod 管理策略来避免这种严格的排序;可以是 OrderedReady
(默认)或 Parallel
。
有序就绪 (OrderedReady) Pod 管理
OrderedReady
Pod 管理是 StatefulSet 的默认设置。它告诉 StatefulSet 控制器遵循上面展示的有序保证。
当你的应用程序要求或期望变更(例如滚动推出应用程序的新版本)按照 StatefulSet 提供的序号(Pod 编号)严格有序地发生时,请使用此策略。换句话说,如果你有 Pod app-0
、app-1
和 app-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 仍然按顺序推出更改。在本教程中,应用程序非常简单:一个告诉你其主机名的 Web 服务器(因为这是一个 StatefulSet,每个 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
此清单与您上面下载的清单相同,不同之处在于 web
StatefulSet 的 .spec.podManagementPolicy
设置为 Parallel
。
在一个终端中,观察 StatefulSet 中的 Pod。
# Leave this watch running until the end of the section
kubectl get pod -l app=nginx --watch
在另一个终端中,为 Parallel
Pod 管理重新配置 StatefulSet
kubectl apply -f https://k8s.io/examples/application/web/web-parallel.yaml
service/nginx updated
statefulset.apps/web updated
保持正在运行 watch 命令的终端打开。在另一个终端窗口中,扩缩 StatefulSet
kubectl scale statefulset/web --replicas=5
statefulset.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
StatefulSet 启动了三个新的 Pod,并且它没有等待第一个 Pod 变成 Running 和 Ready 就启动了第二个和第三个 Pod。
如果您的工作负载包含状态元素,或者需要 Pod 能够通过可预测的命名相互识别,特别是如果您有时需要快速提供更多容量,这种方法会很有用。如果本教程中的简单 Web 服务突然每分钟收到额外 1,000,000 个请求,那么您会希望运行更多 Pod - 但您也不想等待每个新 Pod 启动。并行启动额外的 Pod 可以缩短从请求额外容量到可用容量之间的时间。
清理
您应该打开两个终端,准备好作为清理的一部分运行 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 --watch
web-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。
关闭运行 kubectl get
命令的终端,并删除 nginx
Service
kubectl delete svc nginx
删除本教程中使用的 PersistentVolumes 的持久化存储介质。
kubectl get pvc
NAME 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 14m
kubectl get pv
NAME 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 15m
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.