在一个终端窗口中,持续监视pod:
kubectl get pods --watch -l app=nginx
创建文件 application/web/web.yaml
如下:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
# image: registry.k8s.io/nginx-slim:0.8
image: kaiding1/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
注:因为访问不了 registry.k8s.io
,所以事先把image pull下来,并push到了可访问的位置。
$ kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
$ kubectl get service nginx
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx ClusterIP None 80/TCP 2m30s
$ kubectl get statefulset web
NAME READY AGE
web 2/2 3m10s
如果StatefulSet有 n
个副本,则pod在部署时是按 {0..n-1}
的顺序创建的。
回到监视窗口,如下:
$ kubectl get pods --watch -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
注意:pod web-0
处于 Running
状态并 READY
后,pod web-1
才会启动。
StatefulSet中的每个pod都有一个唯一的顺序索引和稳定的网络身份标识。
$ kubectl get pods -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 7m28s
web-1 1/1 Running 0 7m26s
StatefulSet中的pod拥有一个粘性的、唯一的身份标志。该标志基于StatefulSet控制器分配给每个pod 的唯一顺序索引。Pod的命名格式为
。本例中, web
StatefulSet 拥有两个副本,所以创建了两个pod:web-0
和 web-1
。
每个pod都有一个基于顺序索引的稳定的hostname:
$ for i in 0 1; do kubectl exec "web-$i" -- sh -c 'hostname'; done
web-0
web-1
使用 kubectl run
运行一个提供 nslookup
命令的容器,该命令来自于 dnsutils
包。通过 nslookup
pod的hostname,可以检查它们在集群内部的DNS地址:
$ kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
If you don't see a command prompt, try pressing enter.
/ #
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.0.144 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.0.145 web-1.nginx.default.svc.cluster.local
输入 exit
退出。
Headless service的CNAME指向SRV记录(每个 Running
和 Ready
的pod对应一条记录)。SRV记录指向一个包含pod IP地址的记录条目。
删除StatefulSet中的pod:
$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
回到监视窗口,如下:
$ kubectl get pods --watch -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 2s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
web-0 1/1 Terminating 0 33m
web-1 1/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-1 0/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-1 0/1 Terminating 0 33m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Terminating 0 33m
web-1 0/1 Terminating 0 33m
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
$ kubectl run -i --tty --image busybox:1.28 dns-test --restart=Never --rm
If you don't see a command prompt, try pressing enter.
/ #
/ # nslookup web-0.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-0.nginx
Address 1: 10.244.0.147 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-1.nginx
Address 1: 10.244.0.148 web-1.nginx.default.svc.cluster.local
输入 exit
退出。
Pod 的序号、主机名、SRV记录和记录名称没有改变,但pod关联的IP地址可能发生了改变。所以在其它应用中,不要通过IP地址来连接StatefulSet中的pod。
如果要查找并连接StatefulSet的活动成员,需要查询headless 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
时,应用就能够发现其地址。
获取 web-0
和 web-1
的PVC(PersistentVolumeClaims):
$ kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-71418599-e0d4-4c45-9f3b-f56ebd992509 1Gi RWO standard 75m
www-web-1 Bound pvc-c6741fad-942a-434a-b2a7-05efe540ca1e 1Gi RWO standard 68m
StatefulSet 控制器创建了两个绑定到PV(PersistentVolume)的PVC。
本例中使用的集群配置为动态provision PV,因此PV都是自动创建和绑定的。
NginX web服务器默认会加载 /usr/share/nginx/html/index.html
。StatefulSet spec
中的 volumeMounts
字段会确保 /usr/share/nginx/html
目录由一个PV支持。
将pod的hostname写入它们的 index.html
文件,并验证 NginX web服务器使用该hostname提供服务:
for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
注:如果遇到了 403 Forbidden
:
for i in 0 1; do kubectl exec web-$i -- chmod 755 /usr/share/nginx/html; done
在监视窗口,重新监视:
kubectl get pod --watch -l app=nginx
删除pod:
$ kubectl delete pod -l app=nginx
pod "web-0" deleted
pod "web-1" deleted
回到监视窗口,如下:
$ kubectl get pods --watch -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 33m
web-1 1/1 Running 0 33m
web-0 1/1 Terminating 0 33m
web-1 1/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-1 0/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-0 0/1 Terminating 0 33m
web-1 0/1 Terminating 0 33m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Terminating 0 33m
web-1 0/1 Terminating 0 33m
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
验证web服务器会继续通过hostname提供服务:
$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
虽然 web-0
和 web-1
被重新调度了,但它们仍然继续监听hostname,因为和PVC相关联的PV被重新mount到 volumeMount
。无论 web-0
和 web-1
被调度到了哪个node,它们的PV将会被mount到合适的mount point。
可通过 kubectl scale
或者 kubectl patch
来伸缩StatefulSet。
(注:原文写的是“scale up/down”(纵向伸缩),我觉得其实应该是“scale out/in”(横向伸缩)。)
在监视窗口,重新监视:
kubectl get pods --watch -l app=nginx
kubectl scale sts web --replicas=5
回到监视窗口,如下:
$ kubectl get pods --watch -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (110s ago) 153m
web-1 1/1 Running 1 (111s ago) 153m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 23s
web-2 0/1 ContainerCreating 0 23s
web-2 1/1 Running 0 24s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-3 0/1 Pending 0 2s
web-3 0/1 ContainerCreating 0 2s
web-3 1/1 Running 0 3s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 0s
web-4 0/1 Pending 0 2s
web-4 0/1 ContainerCreating 0 2s
web-4 1/1 Running 0 3s
StatefulSet按顺序索引串行创建pod,前一个pod变为 Running
和 Ready
后才会启动下一个pod。
在监视窗口,重新监视:
kubectl get pods --watch -l app=nginx
kubectl patch sts web -p '{"spec":{"replicas":3}}'
回到监视窗口,如下:
$ kubectl get pods --watch -l app=nginx
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (8m25s ago) 160m
web-1 1/1 Running 1 (8m26s ago) 160m
web-2 1/1 Running 0 6m23s
web-3 1/1 Running 0 5m59s
web-4 1/1 Running 0 5m56s
web-4 1/1 Terminating 0 6m2s
web-4 0/1 Terminating 0 6m2s
web-4 0/1 Terminating 0 6m3s
web-4 0/1 Terminating 0 6m3s
web-4 0/1 Terminating 0 6m3s
web-3 1/1 Terminating 0 6m6s
web-3 0/1 Terminating 0 6m6s
web-3 0/1 Terminating 0 6m7s
web-3 0/1 Terminating 0 6m7s
web-3 0/1 Terminating 0 6m7s
控制器会按照pod顺序索引的相反顺序,依次删除pod。在完全删除一个pod之后,才会删除下一个pod。
获取StatefulSet的PVC:
$ kubectl get pvc -l app=nginx
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-71418599-e0d4-4c45-9f3b-f56ebd992509 1Gi RWO standard 4h9m
www-web-1 Bound pvc-c6741fad-942a-434a-b2a7-05efe540ca1e 1Gi RWO standard 4h2m
www-web-2 Bound pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643 1Gi RWO standard 10m
www-web-3 Bound pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64 1Gi RWO standard 10m
www-web-4 Bound pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee 1Gi RWO standard 10m
可见,仍然有五个PVC和五个PV。当删除StatefulSet的pod时,mount的PV不会被删除。当StatefulSet缩容导致pod被删除时,也是一样。
StatefulSet控制器支持自动更新。其策略由StatefulSet API对象的 spec.updateStrategy
字段决定。该特性可用来更新容器image、资源请求和/或限制、label和注解。
有两种更新策略:
RollingUpdate
(默认)OnDelete
RollingUpdate
更新策略会更新StatefulSet中的所有pod,采用与顺序索引相反的顺序,同时遵循对StatefulSet的保证(guarantee)。
对 web StatefulSet 应用 Patch 操作来应用 RollingUpdate 更新策略:
在监视窗口,重新监视:
kubectl get pods --watch -l app=nginx
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"docker.io/kaiding1/nginx-slim:0.7"}]'
注:原文是 gcr.io/google_containers/nginx-slim:0.8
,这里有两个问题:
gcr.io
无法访问,所以事先要把image pull下来,并push到可访问的位置。gcr.io/google_containers/nginx-slim:0.8
和 registry.k8s.io/nginx-slim:0.8
是相同的,所以并不会触发patch,为了有差异,改为 0.7
。回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 3m59s
web-1 1/1 Running 0 4m1s
web-2 1/1 Running 0 4m3s
web-2 1/1 Terminating 0 15m
web-2 0/1 Terminating 0 15m
web-2 0/1 Terminating 0 15m
web-2 0/1 Terminating 0 15m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 0/1 ErrImagePull 0 4s
web-2 0/1 ImagePullBackOff 0 18s
web-2 0/1 ErrImagePull 0 34s
web-2 0/1 ImagePullBackOff 0 45s
web-2 0/1 ErrImagePull 0 61s
web-2 0/1 ImagePullBackOff 0 72s
web-2 0/1 ErrImagePull 0 113s
web-2 0/1 ImagePullBackOff 0 2m4s
web-2 0/1 ErrImagePull 0 3m29s
web-2 0/1 ImagePullBackOff 0 3m43s
web-2 0/1 ErrImagePull 0 6m23s
web-2 0/1 ImagePullBackOff 0 6m35s
web-2 0/1 ErrImagePull 0 11m
web-2 0/1 ImagePullBackOff 0 11m
web-2 1/1 Running 0 16m
web-1 1/1 Terminating 0 31m
web-1 0/1 Terminating 0 31m
web-1 0/1 Terminating 0 31m
web-1 0/1 Terminating 0 31m
web-1 0/1 Terminating 0 31m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
web-0 1/1 Terminating 0 31m
web-0 0/1 Terminating 0 31m
web-0 0/1 Terminating 0 31m
web-0 0/1 Terminating 0 31m
web-0 0/1 Terminating 0 31m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
可见,StatefulSet里的pod采用和序号相反的顺序更新。StatefulSet控制器终止每个pod,并等待它们变成 Running
和 Ready
,然后再更新下一个pod。注意,StatefulSet控制器仍然会恢复在更新过程中发生故障的pod,恢复为当前版本。
注:从监视的输出结果,可见出问题时,watch的输出间隔大概是上一次的1.5倍到2倍时间。
接收到更新请求的pod将会被恢复为更新的版本,而没有收到更新请求的pod会被恢复为之前的版本。这样,即使出现间歇故障,控制器会尝试继续使应用保持健康以及更新的一致性。
查看pod:
$ for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
docker.io/kaiding1/nginx-slim:0.7
docker.io/kaiding1/nginx-slim:0.7
docker.io/kaiding1/nginx-slim:0.7
查看更新:
$ kubectl rollout status sts/web
partitioned roll out complete: 3 new pods have been updated...
对于使用 RollingUpdate
策略的StatefulSet,可通过 .spec.updateStrategy.rollingUpdate.partition
把更新分隔为多个 partition
。具体参见 https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/#partitions
。
可以使用 .spec.updateStrategy.rollingUpdate
的 partition
字段来分段更新StatefulSet。 这样,使得StatefulSet中的pod不变的同时,改变StatefulSet的pod模板。然后,就可以触发准备好的升级。
首先,patch web
StatefulSet,为 updateStrategy
字段添加partition:
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":3}}}}'
kubectl patch statefulset web --type='json' -p='[{"op": "replace", "path": "/spec/template/spec/containers/0/image", "value":"docker.io/kaiding1/nginx-slim:0.8"}]'
kubectl delete pod web-2
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (4m53s ago) 5h19m
web-1 1/1 Running 1 (4m53s ago) 5h19m
web-2 1/1 Running 1 (4m53s ago) 5h36m
web-2 1/1 Terminating 1 (6m28s ago) 5h38m
web-2 0/1 Terminating 1 (6m28s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
$ kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.7
注意:虽然更新策略是 RollingUpdate
,StatefulSet还是会使用原先的容器恢复pod。这是因为pod序号比 updateStrategy
指定的 partition
小。
注:关于金丝雀部署,参见 https://glossary.cncf.io/canary-deployment
。
金丝雀部署是一种部署策略,开始时有两个环境:一个有实时流量,另一个包含没有实时流量的更新代码。 流量逐渐从应用程序的原始版本转移到更新版本。 它可以从移动 1% 的实时流量开始,然后是 10%,25%,以此类推,直到所有流量都通过更新的版本运行。 企业可以在生产中测试新版本的软件,获得反馈,诊断错误,并在必要时快速回滚到稳定版本。
“金丝雀” 一词是指 “煤矿中的金丝雀” 的做法,即把金丝雀带入煤矿以保证矿工的安全。 如果出现无味的有害气体,鸟就会死亡,而矿工们知道他们必须迅速撤离。 同样,如果更新后的代码出了问题,现场交通就会被 “疏散” 回原来的版本。
可以通过减少 partition
来进行金丝雀发布,以测试修改的模板。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":2}}}}'
Control plane会触发 web-2
的替换(先delete再create)。
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (4m53s ago) 5h19m
web-1 1/1 Running 1 (4m53s ago) 5h19m
web-2 1/1 Running 1 (4m53s ago) 5h36m
web-2 1/1 Terminating 1 (6m28s ago) 5h38m
web-2 0/1 Terminating 1 (6m28s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-2 1/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
$ kubectl get pod web-2 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.8
当改变 partition
时,StatefulSet会自动更新 web-2
pod,这是因为pod的序号大于等于 partition
。
$ kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.7
删除 web-1
pod:
kubectl delete pod web-1
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (4m53s ago) 5h19m
web-1 1/1 Running 1 (4m53s ago) 5h19m
......
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
$ kubectl get pod web-1 --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'
docker.io/kaiding1/nginx-slim:0.7
web-1
被恢复为初始配置,因为pod序号小于分区。当指定了分区时,如果更新了StatefulSet的 .spec.template
,则所有序号大于等于分区的pod都将被更新。如果序号小于分区的pod被删除或者终止,它将被恢复为初始配置。
与金丝雀发布的方法类似,可以执行分阶段发布(例如:线性的、几何的、或者指数的发布)。 要执行分阶段发布,要把 partition
设置为希望控制器暂停更新的序号。
kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"RollingUpdate","rollingUpdate":{"partition":0}}}}'
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (4m53s ago) 5h19m
web-1 1/1 Running 1 (4m53s ago) 5h19m
web-2 1/1 Running 1 (4m53s ago) 5h36m
web-2 1/1 Terminating 1 (6m28s ago) 5h38m
web-2 0/1 Terminating 1 (6m28s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Terminating 1 (6m29s ago) 5h38m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-2 1/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Terminating 0 14m
web-2 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-1 1/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Terminating 1 (28m ago) 5h42m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
web-1 1/1 Terminating 0 10m
web-1 0/1 Terminating 0 10m
web-1 0/1 Terminating 0 10m
web-1 0/1 Terminating 0 10m
web-1 0/1 Terminating 0 10m
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
web-0 1/1 Terminating 1 (38m ago) 5h53m
web-0 0/1 Terminating 1 (38m ago) 5h53m
web-0 0/1 Terminating 1 (38m ago) 5h53m
web-0 0/1 Terminating 1 (38m ago) 5h53m
web-0 0/1 Terminating 1 (38m ago) 5h53m
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 0s
web-0 1/1 Running 0 1s
$ for p in 0 1 2; do kubectl get pod "web-$p" --template '{{range $i, $c := .spec.containers}}{{$c.image}}{{end}}'; echo; done
docker.io/kaiding1/nginx-slim:0.8
docker.io/kaiding1/nginx-slim:0.8
docker.io/kaiding1/nginx-slim:0.8
将 partition
设置为 0
,则允许StatefulSet继续更新流程。
$ kubectl patch statefulset web -p '{"spec":{"updateStrategy":{"type":"OnDelete"}}}'
The StatefulSet "web" is invalid: spec.updateStrategy.rollingUpdate: Invalid value: apps.RollingUpdateStatefulSetStrategy{Partition:0, MaxUnavailable:(*intstr.IntOrString)(nil)}: only allowed for updateStrategy 'RollingUpdate'
报错了,应该是哪里有冲突。
那就直接 kubectl edit
好了:
kubectl edit statefulset web
找到 updateStrategy
,修改如下:
updateStrategy:
type: OnDelete
对于 OnDelete
更新策略,当StatefulSet的 .spec.template
字段有修改时,StatefulSet控制器不会自动更新pod。需要自己处理更新——要么采取手工方式,要么使用其它自动化手段。
StatefulSet支持级联和非级联删除。对于非级联删除,当StatefulSet被删除时,其pod不会被删除。对于级联删除,StatefulSet和它的pod都会被删除。
在监视窗口,重新监视:
kubectl get pods --watch -l app=nginx
kubectl delete statefulset web --cascade=orphan
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (95s ago) 11h
web-1 1/1 Running 1 (95s ago) 11h
web-2 1/1 Running 1 (95s ago) 12h
web-2 1/1 Running 1 (12m ago) 12h
web-1 1/1 Running 1 (12m ago) 12h
web-0 1/1 Running 1 (12m ago) 12h
可见, web
虽然被删除了,其pod仍然处于 Running
和 Ready
状态。
删除 web-0
:
kubectl delete pod web-0
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 1 (95s ago) 11h
web-1 1/1 Running 1 (95s ago) 11h
web-2 1/1 Running 1 (95s ago) 12h
web-2 1/1 Running 1 (12m ago) 12h
web-1 1/1 Running 1 (12m ago) 12h
web-0 1/1 Running 1 (12m ago) 12h
web-0 1/1 Terminating 1 (14m ago) 12h
web-0 0/1 Terminating 1 (14m ago) 12h
web-0 0/1 Terminating 1 (14m ago) 12h
web-0 0/1 Terminating 1 (14m ago) 12h
web-0 0/1 Terminating 1 (14m ago) 12h
可见, web-0
不会再被重建。
在监视窗口,重新监视:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 1 (16m ago) 12h
web-2 1/1 Running 1 (16m ago) 12h
重新创建 web
:
$ kubectl apply -f web.yaml
service/nginx unchanged
statefulset.apps/web created
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-1 1/1 Running 1 (16m ago) 12h
web-2 1/1 Running 1 (16m ago) 12h
web-1 1/1 Running 1 (18m ago) 12h
web-2 1/1 Running 1 (18m ago) 12h
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 1s
web-0 0/1 ContainerCreating 0 1s
web-0 1/1 Running 0 1s
web-2 1/1 Terminating 1 (18m ago) 12h
web-2 0/1 Terminating 1 (18m ago) 12h
web-2 0/1 Terminating 1 (18m ago) 12h
web-2 0/1 Terminating 1 (18m ago) 12h
web-2 0/1 Terminating 1 (18m ago) 12h
web-1 1/1 Terminating 1 (18m ago) 12h
web-1 0/1 Terminating 1 (18m ago) 12h
web-1 0/1 Terminating 1 (18m ago) 12h
web-1 0/1 Terminating 1 (18m ago) 12h
web-1 0/1 Terminating 1 (18m ago) 12h
web-1 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-1 0/1 ContainerCreating 0 0s
web-1 1/1 Running 0 1s
当重新创建 web
StatefulSet时,首先重新启动 web-0
。当 web-0
变成 Running
和 Ready
时,由于 web-1
已经处于 Running
和 Ready
状态,StatefulSet会接收这个pod。由于重新创建的StatefulSet的 replicas
等于 2
,一旦 web-0
被重新创建,且 web-1
被认为已经处于 Running
和 Ready
状态,则 web-2
将会被终止。
$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
尽管删除了StatefulSet和 web-0
pod,但它仍然使用最初写入 index.html
文件的hostname进行服务。这是因为StatefulSet永远不会删除和pod相关联的PV。当重建StatefulSet,并且重新启动了 web-0
时,它原本的PV会被重新mount。
在监视窗口,重新监视:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 13m
web-1 1/1 Running 0 13m
kubectl delete statefulset web
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 13m
web-1 1/1 Running 0 13m
web-1 1/1 Terminating 0 13m
web-0 1/1 Terminating 0 13m
web-0 0/1 Terminating 0 13m
web-1 0/1 Terminating 0 13m
web-1 0/1 Terminating 0 13m
web-1 0/1 Terminating 0 13m
web-1 0/1 Terminating 0 13m
web-0 0/1 Terminating 0 13m
web-0 0/1 Terminating 0 13m
web-0 0/1 Terminating 0 13m
可见,pod按照序号索引相反的顺序依次终止。StatefulSet控制器会等待pod后继者(注:指序号加1的pod)完全终止,才会终止前一个pod。
注意:尽管级联删除会删除StatefulSet及其pod,但不会删除与StatefulSet关联的headless service。必须手动删除 nginx
service。
kubectl delete service nginx
重新创建StatefulSet和headless service:
$ kubectl apply -f web.yaml
service/nginx created
statefulset.apps/web created
$ for i in 0 1; do kubectl exec -i -t "web-$i" -- curl http://localhost/; done
web-0
web-1
即使你已经完全删除了StatefulSet及其pod,重新创建pod时,并mount它们的PV,并且 web-0
和 web-1
将继续使用hostname提供服务。
最后,删除 nginx
service 和 web
StatefulSet:
kubectl delete service nginx
kubectl delete statefulset web
对于某些分布式系统来说,确保StatefulSet的顺序性是不必要和/或不应该的。这些系统仅仅要求唯一性和身份标志。
为了避免这种严格顺序性,可通过 OrderedReady
(默认)或 Parallel
来指定pod管理策略。
Parallel
pod管理策略告诉StatefulSet控制器并行启动或终止所有pod,不必等待pod变成 Running
和 Ready
状态或者完全终止状态,就可以启动或终止另一个pod。该选项只影响缩放行为,不影响更新。
创建文件 web-parallel.yaml
如下:
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
spec:
serviceName: "nginx"
podManagementPolicy: "Parallel"
replicas: 2
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
# image: registry.k8s.io/nginx-slim:0.8
image: docker.io/kaiding1/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
在监视窗口,重新监视:
$ kubectl get pod -l app=nginx --watch
$ kubectl apply -f web-parallel.yaml
service/nginx created
statefulset.apps/web created
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 1s
web-1 0/1 Pending 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 2s
web-0 1/1 Running 0 2s
可见,StatefulSet控制器几乎同时启动了 web-0
和 web-1
。
kubectl scale statefulset/web --replicas=4
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 1s
web-1 0/1 Pending 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 2s
web-0 1/1 Running 0 2s
web-2 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-3 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-3 1/1 Running 0 2s
StatefulSet启动了两个新的pod,而且在启动第二个之前并没有等待第一个变成 Running
和 Ready
状态。
kubectl delete sts web
回到监视窗口,如下:
$ kubectl get pod -l app=nginx --watch
NAME READY STATUS RESTARTS AGE
web-0 0/1 Pending 0 0s
web-0 0/1 Pending 0 0s
web-1 0/1 Pending 0 0s
web-0 0/1 ContainerCreating 0 1s
web-1 0/1 Pending 0 1s
web-1 0/1 ContainerCreating 0 1s
web-1 1/1 Running 0 2s
web-0 1/1 Running 0 2s
web-2 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-2 0/1 Pending 0 0s
web-3 0/1 Pending 0 0s
web-2 0/1 ContainerCreating 0 0s
web-3 0/1 ContainerCreating 0 0s
web-2 1/1 Running 0 1s
web-3 1/1 Running 0 2s
web-3 1/1 Terminating 0 2m12s
web-1 1/1 Terminating 0 4m25s
web-0 1/1 Terminating 0 4m25s
web-2 1/1 Terminating 0 2m12s
web-3 0/1 Terminating 0 2m12s
web-0 0/1 Terminating 0 4m25s
web-2 0/1 Terminating 0 2m12s
web-1 0/1 Terminating 0 4m25s
web-0 0/1 Terminating 0 4m26s
web-0 0/1 Terminating 0 4m26s
web-0 0/1 Terminating 0 4m26s
web-2 0/1 Terminating 0 2m13s
web-2 0/1 Terminating 0 2m13s
web-2 0/1 Terminating 0 2m13s
web-1 0/1 Terminating 0 4m26s
web-1 0/1 Terminating 0 4m26s
web-1 0/1 Terminating 0 4m26s
web-3 0/1 Terminating 0 2m13s
web-3 0/1 Terminating 0 2m13s
web-3 0/1 Terminating 0 2m13s
可见,StatefulSet并发的删除所有pod。
kubectl delete svc nginx
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-71418599-e0d4-4c45-9f3b-f56ebd992509 1Gi RWO standard 23h
www-web-1 Bound pvc-c6741fad-942a-434a-b2a7-05efe540ca1e 1Gi RWO standard 23h
www-web-2 Bound pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643 1Gi RWO standard 19h
www-web-3 Bound pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64 1Gi RWO standard 19h
www-web-4 Bound pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee 1Gi RWO standard 19h
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643 1Gi RWO Delete Bound default/www-web-2 standard 19h
pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64 1Gi RWO Delete Bound default/www-web-3 standard 19h
pvc-71418599-e0d4-4c45-9f3b-f56ebd992509 1Gi RWO Delete Bound default/www-web-0 standard 23h
pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee 1Gi RWO Delete Bound default/www-web-4 standard 19h
pvc-c6741fad-942a-434a-b2a7-05efe540ca1e 1Gi RWO Delete Bound default/www-web-1 standard 23h
$ kubectl delete pvc www-web-0 www-web-1 www-web-2 www-web-3 www-web-4
persistentvolumeclaim "www-web-0" deleted
persistentvolumeclaim "www-web-1" deleted
persistentvolumeclaim "www-web-2" deleted
persistentvolumeclaim "www-web-3" deleted
persistentvolumeclaim "www-web-4" deleted
$ kubectl get pvc
No resources found in default namespace.
$ kubectl delete pv pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643 pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64 pvc-71418599-e0d4-4c45-9f3b-f56ebd992509 pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee pvc-c6741fad-942a-434a-b2a7-05efe540ca1e
persistentvolume "pvc-3164d5af-59b1-4984-ad2a-8a54ae2f0643" deleted
persistentvolume "pvc-5e5253e5-290c-4bd1-9727-c25a751e5d64" deleted
persistentvolume "pvc-71418599-e0d4-4c45-9f3b-f56ebd992509" deleted
persistentvolume "pvc-797b39a1-a55c-47c4-92ab-6e0e2ff567ee" deleted
persistentvolume "pvc-c6741fad-942a-434a-b2a7-05efe540ca1e" deleted
$ kubectl get pv
No resources found
https://kubernetes.io/docs/tutorials/stateful-application/basic-stateful-set