参考:https://kubernetes.io/zh/docs/concepts/workloads/controllers/statefulset/
https://www.kubernetes.org.cn/deployment
StatefulSet
StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括
- 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现
- 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有Cluster IP的Service)来实现
- 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现
- 有序收缩,有序删除(即从N-1到0)
从上面的应用场景可以发现,StatefulSet由以下几个部分组成:
- 用于定义网络标志(DNS domain)的Headless Service
- 用于创建PersistentVolumes的volumeClaimTemplates
- 定义具体应用的StatefulSet
StatefulSet中每个Pod的DNS格式为statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local
,其中
serviceName
为Headless Service的名字0..N-1
为Pod所在的序号,从0开始到N-1statefulSetName
为StatefulSet的名字namespace
为服务所在的namespace,Headless Servic和StatefulSet必须在相同的namespace.cluster.local
为Cluster Domain 默认域就是cluster.local
限制
- 给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的
storage class
来提供,由管理员预先提供。 - 删除或者收缩 StatefulSet 并不会删除它关联的存储卷。这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
- StatefulSet 当前需要 headless 服务 来负责 Pod 的网络标识。您需要负责创建此服务。
- 当删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。为了实现 StatefulSet 中的 Pod 可以有序和优雅的终止,可以在删除之前将 StatefulSet 缩放为 0。
- 在默认 Pod 管理策略(
OrderedReady
) 时使用 滚动更新,可能进入需要 人工干预 才能修复的损坏状态。
组件
下面的示例演示了 StatefulSet 的组件。
# cat 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: selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels serviceName: "nginx" replicas: 3 # by default is 1 template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx #image: k8s.gcr.io/nginx-slim:0.8 image: lowyard/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "managed-nfs-storage" resources: requests: storage: 1Gi
- 名为
nginx
的 Headless Service 用来控制网络域名。 - 名为web的StatefulSet .spec.replicas表明独立的3个Pod副本启动nginx容器
volumeClaimTemplates
将通过 PersistentVolumes 驱动提供的 PersistentVolumes 来提供稳定的存储。需要由管理员事先创建,本次创建的是NFS存储创建参考:https://www.cnblogs.com/minseo/p/12456987.html
Pod选择器
必须设置StatefulSet的.spec.selector字段,使之匹配其在.spec.template.metadata.labels
中设置的标签。在 Kubernetes 1.8 版本之前,被忽略 .spec.selector
字段会获得默认设置值。在 1.8 和以后的版本中,未指定匹配的 Pod 选择器将在创建 StatefulSet 期间导致验证错误。
Pod标识
StatefulSet Pod 具有唯一的标识,该标识包括顺序标识、稳定的网络标识和稳定的存储。该标识和 Pod 是绑定的,不管它被调度在哪个节点上。
有序索引
对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 将被分配一个整数序号,从 0 到 N-1,该序号在 StatefulSet 上是唯一的。
稳定的网络 ID
StatefulSet 中的每个 Pod 根据 StatefulSet 的名称和 Pod 的序号派生出它的主机名。组合主机名的格式为$(StatefulSet 名称)-$(序号)
。上例将会创建三个名称分别为 web-0、web-1、web-2
的 Pod
StatefulSet 可以使用 headless 服务 控制它的 Pod 的网络域。管理域的这个服务的格式为: $(服务名称).$(命名空间).svc.cluster.local
,其中 cluster.local
是集群域。 一旦每个 Pod 创建成功,就会得到一个匹配的 DNS 子域,格式为:$(pod 名称).$(所属服务的 DNS 域名)
,其中所属服务由 StatefulSet 的 serviceName
域来设定。
下面给出一些选择集群域、服务名、StatefulSet 名、及其怎样影响 StatefulSet 的 Pod 上的 DNS 名称的示例:
Cluster Domain | Service (ns/name) | StatefulSet (ns/name) | StatefulSet Domain | Pod DNS | Pod Hostname |
---|---|---|---|---|---|
cluster.local | default/nginx | default/web | nginx.default.svc.cluster.local | web-{0..N-1}.nginx.default.svc.cluster.local | web-{0..N-1} |
cluster.local | foo/nginx | foo/web | nginx.foo.svc.cluster.local | web-{0..N-1}.nginx.foo.svc.cluster.local | web-{0..N-1} |
kube.local | foo/nginx | foo/web | nginx.foo.svc.kube.local | web-{0..N-1}.nginx.foo.svc.kube.local | web-{0..N-1} |
注意: 集群域会被设置为 cluster.local,除非有其他配置。
稳定的存储
Kubernetes 为每个 VolumeClaimTemplate 创建一个 PersistentVolume。在上面的 nginx 示例中,每个 Pod 将会得到基于 StorageClass my-storage-class
提供的 1 Gib 的 PersistentVolume。如果没有声明 StorageClass,就会使用默认的 StorageClass。当一个 Pod 被调度(重新调度)到节点上时,它的 volumeMounts
会挂载与其 PersistentVolumeClaims 相关联的 PersistentVolume。请注意,当 Pod 或者 StatefulSet 被删除时,与 PersistentVolumeClaims 相关联的 PersistentVolume 并不会被删除。要删除它必须通过手动方式来完成。
Pod名称标签
当 StatefulSet 控制器 创建 Pod 时,它会添加一个标签 statefulset.kubernetes.io/pod-name
,该标签设置为 Pod 名称。这个标签允许您给 StatefulSet 中的特定 Pod 绑定一个 Service。
查看该标签
# kubectl get pod --show-labels NAME READY STATUS RESTARTS AGE LABELS db-0 0/1 CrashLoopBackOff 13 68m controller-revision-hash=db-86458bcd44,project=java-demo,run=mysql,statefulset.kubernetes.io/pod-name=db-0 nfs-client-provisioner-7db87779-jdp99 1/1 Running 0 71m app=nfs-client-provisioner,pod-template-hash=7db87779 web-0 1/1 Running 0 56m app=nginx,controller-revision-hash=web-5d84f996d,statefulset.kubernetes.io/pod-name=web-0 web-1 1/1 Running 0 56m app=nginx,controller-revision-hash=web-5d84f996d,statefulset.kubernetes.io/pod-name=web-1 web-2 1/1 Running 0 56m app=nginx,controller-revision-hash=web-5d84f996d,statefulset.kubernetes.io/pod-name=web-2
部署和扩缩保证
- 对于包含 N 个 副本的 StatefulSet,当部署 Pod 时,它们是依次创建的,顺序为
0..N-1
。 - 当删除 Pod 时,它们是逆序终止的,顺序为
N-1..0
。 - 在将缩放操作应用到 Pod 之前,它前面的所有 Pod 必须是 Running 和 Ready 状态。
- 在 Pod 终止之前,所有的继任者必须完全关闭。
StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds
设置为 0。这种做法是不安全的,要强烈阻止。
在上面的 nginx 示例被创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。在 web-0 进入 Running 和 Ready 状态前不会部署 web-1。在 web-1 进入 Running 和 Ready 状态前不会部署 web-2。如果 web-1 已经处于 Running 和 Ready 状态,而 web-2 尚未部署,在此期间发生了 web-0 运行失败,那么 web-2 将不会被部署,要等到 web-0 部署完成并进入 Running 和 Ready 状态后,才会部署 web-2。
如果用户想将示例中的 StatefulSet 收缩为 replicas=1
,首先被终止的是 web-2。在 web-2 没有被完全停止和删除前,web-1 不会被终止。当 web-2 已被终止和删除、web-1 尚未被终止,如果在此期间发生 web-0 运行失败,那么就不会终止 web-1,必须等到 web-0 进入 Running 和 Ready 状态后才会终止 web-1。
Pod管理策略
在 Kubernetes 1.7 及以后的版本中,StatefulSet 允许您不要求其排序保证,同时通过它的 .spec.podManagementPolicy
域保持其唯一性和身份保证。 在 Kubernetes 1.7 及以后的版本中,StatefulSet 允许您放宽其排序保证,同时通过它的 .spec.podManagementPolicy
域保持其唯一性和身份保证。
OrderedReady Pod 管理
OrderedReady
Pod 管理是 StatefulSet 的默认设置。它实现了上面描述的功能。
Parallel Pod 管理
Parallel
Pod 管理让 StatefulSet 控制器并行的启动或终止所有的 Pod,启动或者终止其他 Pod 前,无需等待 Pod 进入 Running 和 ready 或者完全停止状态。
更新策略
在 Kubernetes 1.7 及以后的版本中,StatefulSet 的 .spec.updateStrategy
字段让您可以配置和禁用掉自动滚动更新 Pod 的容器、标签、资源请求或限制、以及注解。
关于删除策略
OnDelete
更新策略实现了 1.6 及以前版本的历史遗留行为。当 StatefulSet 的 .spec.updateStrategy.type
设置为 OnDelete
时,它的控制器将不会自动更新 StatefulSet 中的 Pod。用户必须手动删除 Pod 以便让控制器创建新的 Pod,以此来对 StatefulSet 的 .spec.template
的变动作出反应。
滚动更新
RollingUpdate
更新策略对 StatefulSet 中的 Pod 执行自动的滚动更新。在没有声明 .spec.updateStrategy
时,RollingUpdate
是默认配置。 当 StatefulSet 的 .spec.updateStrategy.type
被设置为 RollingUpdate
时,StatefulSet 控制器会删除和重建 StatefulSet 中的每个 Pod。 它将按照与 Pod 终止相同的顺序(从最大序号到最小序号)进行,每次更新一个 Pod。它会等到被更新的 Pod 进入 Running 和 Ready 状态,然后再更新其前身。
分区
通过声明 .spec.updateStrategy.rollingUpdate.partition
的方式,RollingUpdate
更新策略可以实现分区。如果声明了一个分区,当 StatefulSet 的 .spec.template
被更新时,所有序号大于等于该分区序号的 Pod 都会被更新。所有序号小于该分区序号的 Pod 都不会被更新,并且,即使他们被删除也会依据之前的版本进行重建。如果 StatefulSet 的 .spec.updateStrategy.rollingUpdate.partition
大于它的 .spec.replicas
,对它的 .spec.template
的更新将不会传递到它的 Pod。 在大多数情况下,您不需要使用分区,但如果您希望进行阶段更新、则这些分区会非常有用。
强制回滚
在默认 Pod 管理策略(OrderedReady
) 时使用 滚动更新 ,可能进入需要人工干预才能修复的损坏状态。
如果更新后 Pod 模板配置进入无法运行或就绪的状态(例如,由于错误的二进制文件或应用程序级配置错误),StatefulSet 将停止回滚并等待。
在这种状态下,仅将 Pod 模板还原为正确的配置是不够的。由于已知问题,StatefulSet 将继续等待损坏状态的 Pod 准备就绪(永远不会发生),然后再尝试将其恢复为正常工作配置。
恢复模板后,还必须删除 StatefulSet 尝试使用错误的配置来运行的 Pod。这样,StatefulSet 才会开始使用被还原的模板来重新创建 Pod。
部署StatefulSet
参考:https://kubernetes.io/zh/docs/tutorials/stateful-application/basic-stateful-set/
目标
StatefulSets 旨在与有状态的应用及分布式系统一起使用。然而在 Kubernetes 上管理有状态应用和分布式系统是一个宽泛而复杂的话题。为了演示 StatefulSet 的基本特性,并且不使前后的主题混淆,你将会使用 StatefulSet 部署一个简单的 web 应用。
在阅读本教程后,你将熟悉以下内容:
- 如何创建StatefulSet
- StatefulSet怎样管理它的Pods
- 如何删除StatefulSet
- 如何对StatefulSet进行扩容/缩容
- 如果更新一个StatefulSet的Pods
创建StatefulSet
需要有动态存储卷本次私有NFS搭建,搭建完毕以后使用命令查看
# kubectl get storageClass NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE managed-nfs-storage fuseim.pri/ifs Delete Immediate false 120m
需要使用两个终端窗口。在一个终端中使用kubectl get来检查StatefulSet创建Pods情况
kubectl get pods -w -l app=nginx
在另一个终端中,使用 kubectl apply
来创建定义在 web.yaml
中的 Headless Service 和 StatefulSet。
kubectl apply -f web.yaml service/nginx created statefulset.apps/web created
上面的命令创建了两个 Pod,每个都运行了一个 NGINX web 服务器。获取 nginx
Service 和 web
StatefulSet 来验证是否成功的创建了它们。
kubectl get service nginx NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE nginx ClusterIP None <none> 80/TCP 2m8s kubectl get statefulset web NAME READY AGE web 2/2 2m10s
顺序创建Pod
对于一个拥有 N 个副本的 StatefulSet,Pod 被部署时是按照 {0 …… N-1} 的序号顺序创建的。在第一个终端中使用 kubectl get
检查输出。这个输出最终将看起来像下面的样子。
kubectl get pods -w -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 Pending 0 2s web-0 0/1 ContainerCreating 0 2s web-0 1/1 Running 0 4s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 0s web-1 0/1 Pending 0 2s web-1 0/1 ContainerCreating 0 2s web-1 1/1 Running 0 4s
请注意在 web-0
Pod 处于 Running和Ready 状态后 web-1
Pod 才会被启动。
StatefulSet中的Pod
StatefulSet终端Pod拥有一个唯一的顺序索引和稳定的网络身份标识。
检查Pod顺序索引
#参数-l更加标签进行匹配 kubectl get pods -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 4m6s web-1 1/1 Running 0 4m1s
如同 StatefulSets 概念中所提到的,StatefulSet 中的 Pod 拥有一个具有黏性的、独一无二的身份标志。这个标志基于 StatefulSet 控制器分配给每个 Pod 的唯一顺序索引。Pod 的名称的形式为<statefulset name>-<ordinal index>
。web
StatefulSet 拥有两个副本,所以它创建了两个 Pod:web-0
和web-1
。
本次示例的statefulset name为web
序号从0开始至设定的副本数n-1
使用稳定的网络身份标识
每个Pod都拥有一个基于其顺序索引的稳定的主机名。
for i in 0 1;do kubectl exec web-$i -- sh -c 'hostname';done web-0 web-1
使用 kubectl run
运行一个提供 nslookup
命令的容器,该命令来自于 dnsutils
包。通过对 Pod 的主机名执行 nslookup
,你可以检查他们在集群内部的 DNS 地址。
kubectl run -it --image busybox:1.28 dns-test --restart=Never --rm nslookup web-0.nginx Server: 10.0.0.2 Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.17.71.3 web-0.nginx.default.svc.cluster.local
DNS格式为 $(Pod名).$(所属服务的 DNS 域名).$(命名空间).svc.cluster.local
所属服务的 DNS 域名由StatefulSet的serviceName设置
其中域设置默认为cluster.local
在一个终端查看StatefulSet的Pods
kubectl get pods -w -l app=nginx
在另一个终端删除StatefulSet中的Pods
kubectl delete pod -l app=nginx pod "web-0" deleted pod "web-1" deleted
等待 StatefulSet 重启它们,并且两个 Pod 都变成 Running 和 Ready 状态。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 19m web-1 1/1 Running 0 19m web-0 1/1 Terminating 0 20m web-1 1/1 Terminating 0 20m web-1 0/1 Terminating 0 20m web-0 0/1 Terminating 0 20m web-1 0/1 Terminating 0 20m web-1 0/1 Terminating 0 20m web-0 0/1 Terminating 0 20m web-0 0/1 Terminating 0 20m 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 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 exec
和 kubectl run
查看 Pod 的主机名和集群内部的 DNS 表项。
for i in 0 1;do kubectl exec web-$i -- sh -c 'hostname';done web-0 web-1
kubectl run -it --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.0.0.2 Address 1: 10.0.0.2 kube-dns.kube-system.svc.cluster.local Name: web-0.nginx Address 1: 172.17.71.3 web-0.nginx.default.svc.cluster.local
Pod 的序号、主机名、SRV 条目和记录名称没有改变,但和 Pod 相关联的 IP 地址可能发生了改变。本次没有改变。这就是为什么不要在其他应用中使用 StatefulSet 中的 Pod 的 IP 地址进行连接,这点很重要。
如果你需要查找并连接一个 StatefulSet 的活动成员,你应该查询 Headless Service 的 CNAME。和 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 时,你的应用就能够发现它们的地址。
写入稳定的存储
获取 web-0
和 web-1
的 PersistentVolumeClaims。
kubectl get pvc -l app=nginx NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pvc-bbc399de-fac4-4f10-bd77-fc40fbf949a9 1Gi RWO managed-nfs-storage 131m www-web-1 Bound pvc-4d147825-2087-41d8-8fc1-a174de4c3bf6 1Gi RWO managed-nfs-storage
StatefulSet 控制器创建了两个 PersistentVolumeClaims,绑定到两个 PersistentVolumes。由于本教程使用的集群配置为动态提供 PersistentVolume,所有的 PersistentVolume 都是自动创建和绑定的。
NGINX web 服务器默认会加载位于 /usr/share/nginx/html/index.html
的 index 文件。StatefulSets spec
中的 volumeMounts
字段保证了 /usr/share/nginx/html
文件夹由一个 PersistentVolume 支持。
将 Pod 的主机名写入它们的index.html
文件并验证 NGINX web 服务器使用该主机名提供服务。
for i in 0 1; do kubectl exec web-$i -- sh -c 'echo $(hostname) > /usr/share/nginx/html/index.html'; done for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
注意: 请注意,如果你看见上面的 curl 命令返回了 403 Forbidden 的响应,你需要像这样修复使用 volumeMounts(due to a bug when using hostPath volumes)挂载的目录的权限: for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done 在你重新尝试上面的 curl 命令之前。
在一个终端查看 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一个终端删除 StatefulSet 所有的 Pod。
kubectl delete pod -l app=nginx pod "web-0" deleted pod "web-1" deleted
在第一个终端里检查kubectl get命令的输出,等待所有Pod变成Running和Ready状态
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 117m web-1 1/1 Running 0 117m web-0 1/1 Terminating 0 118m web-1 1/1 Terminating 0 118m web-1 0/1 Terminating 0 118m web-0 0/1 Terminating 0 118m web-0 0/1 Terminating 0 118m web-0 0/1 Terminating 0 118m 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 web-1 0/1 Terminating 0 118m web-1 0/1 Terminating 0 118m 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 0s
验证所有 web 服务器在继续使用它们的主机名提供服务。
for i in 0 1;do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
虽然 web-0
和 web-1
被重新调度了,但它们仍然继续监听各自的主机名,因为和它们的 PersistentVolumeClaim 相关联的 PersistentVolume 被重新挂载到了各自的 volumeMount
上。不管 web-0
和 web-1
被调度到了哪个节点上,它们的 PersistentVolumes 将会被挂载到合适的挂载点上。
扩容/缩容StatefulSet
扩容/缩容 StatefulSet 指增加或减少它的副本数。这通过更新 replicas
字段完成。你可以使用kubectl scale
或者kubectl patch
来扩容/缩容一个 StatefulSet。
扩容
在一个终端窗口观察 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一个终端窗口使用 kubectl scale
扩展副本数为 5。
kubectl scale sts web --replicas=5 statefulset.apps/web scaled
在第一个 终端中检查 kubectl get
命令的输出,等待增加的 3 个 Pod 的状态变为 Running 和 Ready。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 6m17s web-1 1/1 Running 0 6m12s web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 0s web-2 0/1 Pending 0 1s web-2 0/1 ContainerCreating 0 1s web-2 1/1 Running 0 3s web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 0s web-3 0/1 Pending 0 1s web-3 0/1 ContainerCreating 0 1s web-3 1/1 Running 0 2s web-4 0/1 Pending 0 0s web-4 0/1 Pending 0 0s web-4 0/1 Pending 0 1s web-4 0/1 ContainerCreating 0 1s web-4 1/1 Running 0 3s
缩容
在一个终端观察 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一个终端使用 kubectl patch
将 StatefulSet 缩容回三个副本。
kubectl patch sts web -p '{"spec":{"replicas":3}}' statefulset.apps/web patched
等待 web-4
和 web-3
状态变为 Terminating。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 9m49s web-1 1/1 Running 0 9m44s web-2 1/1 Running 0 2m37s web-3 1/1 Running 0 9s web-4 1/1 Running 0 8s web-4 1/1 Terminating 0 52s web-4 0/1 Terminating 0 53s web-4 0/1 Terminating 0 59s web-4 0/1 Terminating 0 59s web-3 1/1 Terminating 0 60s web-3 0/1 Terminating 0 61s web-3 0/1 Terminating 0 65s web-3 0/1 Terminating 0 65s
顺序终止Pod
控制器会按照与 Pod 序号索引相反的顺序每次删除一个 Pod。在删除下一个 Pod 前会等待上一个被完全关闭。
获取 StatefulSet 的 PersistentVolumeClaims。
kubectl get pvc -l app=nginx NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE www-web-0 Bound pvc-bbc399de-fac4-4f10-bd77-fc40fbf949a9 1Gi RWO managed-nfs-storage 151m www-web-1 Bound pvc-4d147825-2087-41d8-8fc1-a174de4c3bf6 1Gi RWO managed-nfs-storage 151m www-web-2 Bound pvc-28fb3584-78ce-44a3-9523-6e89907cb9fc 1Gi RWO managed-nfs-storage 5m22s www-web-3 Bound pvc-256cfeec-46d5-4b35-9357-acd12ef8ee9c 1Gi RWO managed-nfs-storage 5m19s www-web-4 Bound pvc-26beab6f-852d-46fd-ac32-35632b1dc65e 1Gi RWO managed-nfs-storage 5m17s
五个 PersistentVolumeClaims 和五个 PersistentVolumes 仍然存在。查看 Pod 的 稳定存储,我们发现当删除 StatefulSet 的 Pod 时,挂载到 StatefulSet 的 Pod 的 PersistentVolumes 不会被删除。当这种删除行为是由 StatefulSet 缩容引起时也是一样的。
更新StatefulSet
Kubernetes1.7版本的StatefulSet控制器支持自动更新。更新策略有StatefulSet API Object的spec.updateSreategy字段决定。这个特性能够用来更新一个StatefulSet中的Pod的container images,resource requests,limits和annotaions。RollingUpdate滚动更新是StatefulSet默认策略。
Rolling Update 策略
RollingUpdate
更新策略会更新一个 StatefulSet 中所有的 Pod,采用与序号索引相反的顺序并遵循 StatefulSet 的保证。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate"}}}' statefulset.apps/web patched (no change)
因为默认的更新策略是RollingUpdate所以提示没有修改
在一个终端窗口中 patch web
StatefulSet 来再次的改变容器镜像。
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"nginx"}]'
把镜像值修改成nginx,该镜像需要是可以下载的否则更新时会出现下载镜像错误提示ErrImagePull可以通过命令查看是否更新
kubectl edit sts web
在另一个终端监控 StatefulSet 中的 Pod。
kubectl get pods -w -l app=nginx 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 5s web-1 1/1 Terminating 0 5s web-1 0/1 Terminating 0 6s web-1 0/1 Terminating 0 10s web-1 0/1 Terminating 0 10s 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 2s web-0 1/1 Terminating 0 14s web-0 0/1 Terminating 0 14s web-0 0/1 Terminating 0 15s web-0 0/1 Terminating 0 15s 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 采用和序号相反的顺序更新。在更新下一个 Pod 前,StatefulSet 控制器终止每个 Pod 并等待它们变成 Running 和 Ready。请注意,虽然在顺序后继者变成 Running 和 Ready 之前 StatefulSet 控制器不会更新下一个 Pod,但它仍然会重建任何在更新过程中发生故障的 Pod,使用的是它们当前的版本。已经接收到更新请求的 Pod 将会被恢复为更新的版本,没有收到请求的 Pod 则会被恢复为之前的版本。像这样,控制器尝试继续使应用保持健康并在出现间歇性故障时保持更新的一致性。
获取 Pod 来查看他们的容器镜像。
for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done nginx nginx nginx
小窍门:你还可以使用 kubectl rollout status sts/<name>
来查看 rolling update 的状态。
分段更新
你可以使用 RollingUpdate
更新策略的 partition
参数来分段更新一个 StatefulSet。分段的更新将会使 StatefulSet 中的其余所有 Pod 保持当前版本的同时仅允许改变 StatefulSet 的 .spec.template
。
Patch web
StatefulSet 来对 updateStrategy
字段添加一个分区。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
再次 Patch StatefulSet 来改变容器镜像。
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"lowyard/nginx-slim:0.8"}]'
删除 StatefulSet 中的 Pod。
kubectl delete pod web-2 pod "web-2" deleted
等待 Pod 变成 Running 和 Ready。
获取Pod容器镜像
kubectl get po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' nginx
请注意,虽然更新策略是 RollingUpdate
,StatefulSet 控制器还是会使用原始的容器恢复 Pod。这是因为 Pod 的序号比 updateStrategy
指定的 partition
更小。
灰度扩容
把分段数改成2
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}' statefulset.apps/web patched
删除web-2以后就使用新的镜像更新了
kubectl get po web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' lowyard/nginx-slim:0.8
当你改变 partition
时,StatefulSet 会自动的更新 web-2
Pod,这是因为 Pod 的序号小于或等于 partition
。
删除web-1等待running
kubectl delete pod web-1
查看镜像还是旧的镜像
kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}' nginx
web-1
被按照原来的配置恢复,因为 Pod 的序号小于分区。当指定了分区时,如果更新了 StatefulSet 的 .spec.template
,则所有序号大于或等于分区的 Pod 都将被更新。如果一个序号小于分区的 Pod 被删除或者终止,它将被按照原来的配置恢复。
分阶段扩容
你可以使用类似灰度扩容的方法执行一次分阶段的扩容(例如一次线性的、等比的或者指数形式的扩容)。要执行一次分阶段的扩容,你需要设置 partition
为希望控制器暂停更新的序号。
分区当前为2
。请将分区设置为0
。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}' statefulset.apps/web patched
等待 StatefulSet 中的所有 Pod 变成 Running 和 Ready。
获取Pod的容器
for p in 0 1 2; do kubectl get po web-$p --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done lowyard/nginx-slim:0.8 lowyard/nginx-slim:0.8 lowyard/nginx-slim:0.8
PS:一般扩容都是整体扩容,分阶段扩容用的较少,但在某些特定场合适用
删除StatefulSet
StatefulSet 同时支持级联和非级联删除。使用非级联方式删除 StatefulSet 时,StatefulSet 的 Pod 不会被删除。使用级联删除时,StatefulSet 和它的 Pod 都会被删除。
非级联删除
在一个终端窗口查看 StatefulSet 中的 Pod。
kubectl get pods -w -l app=nginx
使用 kubectl delete
删除 StatefulSet。请确保提供了 --cascade=false
参数给命令。这个参数告诉 Kubernetes 只删除 StatefulSet 而不要删除它的任何 Pod。
kubectl delete statefulset web --cascade=false statefulset.apps "web" deleted
获取pod状态
# kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 3m41s web-1 1/1 Running 0 3m46s web-2 1/1 Running 0 7m51s web-0 1/1 Running 0 4m36s web-2 1/1 Running 0 8m46s web-1 1/1 Running 0 4m41s
虽然 web
已经被删除了,但所有 Pod 仍然处于 Running 和 Ready 状态。 删除 web-0
。
kubectl delete pod web-0
获取 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-1 1/1 Running 0 6m13s web-2 1/1 Running 0 10m
由于 web
StatefulSet 已经被删除,web-0
没有被重新启动。
在一个终端监控 StatefulSet 的 Pod。
kubectl get pods -w -l app=nginx
在另一个终端里重新创建 StatefulSet。请注意,除非你删除了 nginx
Service (你不应该这样做),你将会看到一个错误,提示 Service 已经存在。
kubectl apply -f web.yaml service/nginx unchanged statefulset.apps/web created
在第一个终端中运行并检查 kubectl get
命令的输出。
kubectl get pods -w -l app=nginx
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 14m
web-1 1/1 Running 0 10m
web-2 1/1 Running 0 14m
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
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
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
的内容。
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
尽管你同时删除了 StatefulSet 和 web-0
Pod,但它仍然使用最初写入 index.html
文件的主机名进行服务。这是因为 StatefulSet 永远不会删除和一个 Pod 相关联的 PersistentVolumes。当你重建这个 StatefulSet 并且重新启动了 web-0
时,它原本的 PersistentVolume 会被重新挂载。
级联删除
在一个终端窗口观察 StatefulSet 里的 Pod。
kubectl get pods -w -l app=nginx
另一个窗口中再次删除这个 StatefulSet。这次省略 --cascade=false
参数。
kubectl delete statefulset web statefulset.apps "web" deleted
在第一个终端检查 kubectl get
命令的输出,并等待所有的 Pod 变成 Terminating 状态。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 114s web-1 1/1 Running 0 12m web-0 1/1 Terminating 0 2m30s web-1 1/1 Terminating 0 12m web-0 0/1 Terminating 0 2m31s web-1 0/1 Terminating 0 12m web-0 0/1 Terminating 0 2m32s web-0 0/1 Terminating 0 2m32s
如同你在缩容一节看到的,Pod 按照和他们序号索引相反的顺序每次终止一个。在终止一个 Pod 前,StatefulSet 控制器会等待 Pod 后继者被完全终止。
请注意,虽然级联删除会删除 StatefulSet 和它的 Pod,但它并不会删除和 StatefulSet 关联的 Headless Service。你必须手动删除nginx
Service。
kubectl delete svc nginx service "nginx" deleted
再一次重新创建 StatefulSet 和 Headless Service。
kubectl apply -f web.yaml service/nginx created statefulset.apps/web created
当 StatefulSet 所有的 Pod 变成 Running 和 Ready 时,获取它们的 index.html
文件的内容。
for i in 0 1; do kubectl exec -it web-$i -- curl localhost; done web-0 web-1
即使你已经删除了 StatefulSet 和它的全部 Pod,这些 Pod 将会被重新创建并挂载它们的 PersistentVolumes,并且 web-0
和 web-1
将仍然使用它们的主机名提供服务。
Pod管理策略
对于某些分布式系统来说,StatefulSet 的顺序性保证是不必要和/或者不应该的。这些系统仅仅要求唯一性和身份标志。为了解决这个问题,在 Kubernetes 1.7 中我们针对 StatefulSet API Object 引入了 .spec.podManagementPolicy
。
OrderedReady Pod 管理策略
OrderedReady
pod 管理策略是 StatefulSets 的默认选项。它告诉 StatefulSet 控制器遵循上文展示的顺序性保证。
Parallel Pod 管理策略
Parallel
pod 管理策略告诉 StatefulSet 控制器并行的终止所有 Pod,在启动或终止另一个 Pod 前,不必等待这些 Pod 变成 Running 和 Ready 或者完全终止状态。
# cat 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: selector: matchLabels: app: nginx # has to match .spec.template.metadata.labels serviceName: "nginx" podManagementPolicy: "Parallel" replicas: 2 # by default is 1 template: metadata: labels: app: nginx # has to match .spec.selector.matchLabels spec: terminationGracePeriodSeconds: 10 containers: - name: nginx #image: k8s.gcr.io/nginx-slim:0.8 image: lowyard/nginx-slim:0.8 ports: - containerPort: 80 name: web volumeMounts: - name: www mountPath: /usr/share/nginx/html volumeClaimTemplates: - metadata: name: www spec: accessModes: [ "ReadWriteOnce" ] storageClassName: "managed-nfs-storage" resources: requests: storage: 1Gi
这份清单和你在上文下载的完全一样,只是 web
StatefulSet 的 .spec.podManagementPolicy
设置成了 Parallel
。
在另一个终端窗口创建清单中的 StatefulSet 和 Service。
kubectl apply -f web-parallel.yaml service/nginx created statefulset.apps/web created
查看你在第一个终端中运行的 kubectl get
命令的输出。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-1 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 0s web-1 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 1/1 Running 0 2s
StatefulSet 控制器同时启动了 web-0
和 web-1
。
保持第二个终端打开,并在另一个终端窗口中扩容 StatefulSet。
kubectl scale statefulset/web --replicas=4 statefulset.apps/web scaled
在 kubectl get
命令运行的终端里检查它的输出。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 0/1 Pending 0 0s web-1 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 0s web-1 0/1 ContainerCreating 0 0s web-0 1/1 Running 0 1s web-1 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-3 1/1 Running 0 1s web-2 1/1 Running 0 2s
StatefulSet 控制器启动了两个新的 Pod,而且在启动第二个之前并没有等待第一个变成 Running 和 Ready 状态。
保持这个终端打开,并在另一个终端删除 web
StatefulSet。
kubectl delete sts web statefulset.apps "web" deleted
在另一个终端里再次检查 kubectl get
命令的输出。
kubectl get pods -w -l app=nginx NAME READY STATUS RESTARTS AGE web-0 1/1 Running 0 2m33s web-1 1/1 Running 0 2m33s web-2 1/1 Running 0 91s web-3 1/1 Running 0 91s web-2 1/1 Terminating 0 98s web-1 1/1 Terminating 0 2m40s web-3 1/1 Terminating 0 98s web-0 1/1 Terminating 0 2m40s web-1 0/1 Terminating 0 2m41s web-2 0/1 Terminating 0 99s web-0 0/1 Terminating 0 2m42s web-2 0/1 Terminating 0 100s web-2 0/1 Terminating 0 100s web-1 0/1 Terminating 0 2m42s web-2 0/1 Terminating 0 100s web-3 0/1 Terminating 0 100s web-0 0/1 Terminating 0 2m48s web-0 0/1 Terminating 0 2m48s web-1 0/1 Terminating 0 2m53s web-1 0/1 Terminating 0 2m53s web-3 0/1 Terminating 0 111s web-3 0/1 Terminating 0 111s
StatefulSet 控制器将并发的删除所有 Pod,在删除一个 Pod 前不会等待它的顺序后继者终止。