这篇博客主要探讨StatefulSet如何通过Headless Service维持Pod的拓扑状态
StatefulSet 是用来管理有状态应用的工作负载 API 对象。
StatefulSet 用来管理 Deployment 和扩展一组 Pod,并且能为这些 Pod 提供序号和唯一性保证。
和 Deployment 相同的是,StatefulSet 管理了基于相同容器定义的一组 Pod。但和 Deployment 不同的是,StatefulSet 为它们的每个 Pod 维护了一个固定的 ID。这些 Pod 是基于相同的声明来创建的,但是不能相互替换:无论怎么调度,每个 Pod 都有一个永久不变的 ID。
StatefulSet 和其他控制器使用相同的工作模式。你在 StatefulSet 对象 中定义你期望的状态,然后 StatefulSet 的 控制器 就会通过各种更新来达到那种你想要的状态。
StatefulSets 对于需要满足以下一个或多个需求的应用程序很有价值:
在上面,稳定意味着 Pod 调度或重调度的整个过程是有持久性的。如果应用程序不需要任何稳定的标识符或有序的部署、删除或伸缩,则应该使用由一组无状态的副本控制器提供的工作负载来部署应用程序,比如 Deployment 或者 ReplicaSet 可能更适用于您的无状态应用部署需要。
给定 Pod 的存储必须由 PersistentVolume 驱动 基于所请求的 storage class 来提供,或者由管理员预先提供。
删除或者收缩 StatefulSet 并不会删除它关联的存储卷。这样做是为了保证数据安全,它通常比自动清除 StatefulSet 所有相关的资源更有价值。
StatefulSet 当前需要 headless 服务 来负责 Pod 的网络标识。您需要负责创建此服务。
当删除 StatefulSets 时,StatefulSet 不提供任何终止 Pod 的保证。为了实现 StatefulSet 中的 Pod 可以有序和优雅的终止,可以在删除之前将 StatefulSet 缩放为 0。
在默认 Pod 管理策略(OrderedReady) 时使用 滚动更新,可能进入需要 人工干预 才能修复的损坏状态。
StatefulSet将应用状态抽象成了两种情况:
StatefulSet给所有的Pod进行了编号,编号规则是:$(statefulset名称)-$(序号)
,从0开始。
Pod被删除后重建,重建Pod的网络标识也不会改变,Pod的拓扑状态按照Pod的“名字+编号”的方式固定下来,并且为每个Pod提供了一个固定且唯一的访问入口,即Pod对应的DNS记录,同时重建时保证每个pod挂载到原来的卷上。
首先创建Headless service:
[root@server1 pv]# mkdir statefulset
[root@server1 pv]# cd statefulset/
[root@server1 statefulset]# vim service.yaml
[root@server1 statefulset]# cat service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
创建服务:
[root@server1 statefulset]# kubectl apply -f service.yaml
service/nginx created
[root@server1 statefulset]# kubectl get service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 19d
myservice ClusterIP 10.101.31.155 <none> 80/TCP 14d
nginx ClusterIP None <none> 80/TCP 13s
可以看出创建了一个名为nginx的无头服务(Headless service)。
创建使用StatefulSet控制器的pod:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
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: nginx
ports:
- containerPort: 80
name: web
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web created
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 31m
web-0 1/1 Running 0 29s
web-1 1/1 Running 0 26s
可以看出创建了两个名为web-0
和web-1
的pod,service也会由两个endpoint:
其中pod名称中的0和1是pod的唯一标识。
当我们将replicas改为1时,web-1将会被回收:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 1
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 33m
web-0 1/1 Running 0 2m12s
而当我们将replicas改为0时,所有pod将会被回收:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 0
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 34m
之后再次重建pod:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 2
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 35m
web-0 0/1 ContainerCreating 0 2s
[root@server1 statefulset]# kubectl get pod
^[[3~NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 35m
web-0 1/1 Running 0 9s
web-1 1/1 Running 0 7s
[root@server1 statefulset]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 35m 10.244.1.81 server2 <none> <none>
web-0 1/1 Running 0 28s 10.244.2.72 server3 <none> <none>
web-1 1/1 Running 0 26s 10.244.1.83 server2 <none> <none>
可以看出重新创建时是先创建web-0再创建web-1,而删除的时候是先删除web-1,再删除web-0.
但是需要注意的是虽然名称标识没有变,但是pod的ip发生了变化。
现在可以直接访问服务:
也可以解析地址:
也可以使用nslookup web-1.nginx
的方式解析和访问到:
PV和PVC的设计,使得StatefulSet对存储状态的管理成为了可能,现在为以上这些pod添加存储:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
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: nginx
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
storageClassName: managed-nfs-storage #分配器名称
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 100Mi
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web created
[root@server1 statefulset]# kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 85m 10.244.1.81 server2 <none> <none>
test 1/1 Running 2 48m 10.244.2.73 server3 <none> <none>
web-0 1/1 Running 0 25s 10.244.1.84 server2 <none> <none>
web-1 1/1 Running 0 20s 10.244.2.74 server3 <none> <none>
StatefulSet还会为每一个Pod分配并创建一个同样编号的PVC。这样,kubernetes就可以通过Persistent Volume机制为这个PVC绑定对应的PV,从而保证每一个Pod都拥有一个独立的Volume:
[root@server1 statefulset]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-web-0 Bound pvc-41713073-508d-4dcf-897e-dd0575a0945a 100Mi RWO managed-nfs-storage 2m30s
www-web-1 Bound pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6 100Mi RWO managed-nfs-storage 2m25s
[root@server1 statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6 100Mi RWO Delete Bound default/www-web-1 managed-nfs-storage 2m28s
pvc-41713073-508d-4dcf-897e-dd0575a0945a 100Mi RWO Delete Bound default/www-web-0 managed-nfs-storage 2m33s
可以往这些存储里写入测试页面:
[root@server1 statefulset]# ls /nfs/
default-www-web-0-pvc-41713073-508d-4dcf-897e-dd0575a0945a
default-www-web-1-pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6
[root@server1 statefulset]#
[root@server1 statefulset]# echo web-0 > /nfs/default-www-web-0-pvc-41713073-508d-4dcf-897e-dd0575a0945a/index.html
[root@server1 statefulset]# echo web-1 > /nfs/default-www-web-1-pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6/index.html
测试解析和访问:
[root@server1 statefulset]# kubectl attach test -it
/ # nslookup nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: nginx
Address 1: 10.244.1.84 web-0.nginx.default.svc.cluster.local
Address 2: 10.244.2.74 10-244-2-74.myservice.default.svc.cluster.local
可以看出解析成功。
/ # curl nginx
web-1
/ # curl nginx
web-1
/ # curl nginx
web-0
/ # curl nginx
web-0
/ # curl nginx
web-1
/ # curl nginx
web-0
/ # curl web-1.nginx #访问特定pod
web-1
/ # curl web-0.nginx
web-0
访问可以成功负载。
接下来我们将replicas设置为3,再次进行测试:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
修改:
replicas: 3
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 90m
test 1/1 Running 4 53m
web-0 1/1 Running 0 5m14s
web-1 1/1 Running 0 5m9s
web-2 1/1 Running 0 9s
service由三个endpoint:
对应的pv当然也会创建:
[root@server1 statefulset]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6 100Mi RWO Delete Bound default/www-web-1 managed-nfs-storage 5m35s
pvc-41713073-508d-4dcf-897e-dd0575a0945a 100Mi RWO Delete Bound default/www-web-0 managed-nfs-storage 5m40s
pvc-c6e2ee56-02d8-4540-9dc7-b91926d34274 100Mi RWO Delete Bound default/www-web-2 managed-nfs-storage 35s
写入测试页面并访问:
[root@server1 statefulset]# ls /nfs/
default-www-web-0-pvc-41713073-508d-4dcf-897e-dd0575a0945a
default-www-web-1-pvc-36ac2b3c-2f82-452e-ad39-bab2a51e67c6
default-www-web-2-pvc-c6e2ee56-02d8-4540-9dc7-b91926d34274
[root@server1 statefulset]# echo web-2 > /nfs/default-www-web-2-pvc-c6e2ee56-02d8-4540-9dc7-b91926d34274/index.html
[root@server1 statefulset]#
[root@server1 statefulset]# kubectl attach test -it
Defaulting container name to test.
Use 'kubectl describe pod/test -n default' to see all of the containers in this pod.
If you don't see a command prompt, try pressing enter.
/ # nslookup web-2.nginx
Server: 10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name: web-2.nginx
Address 1: 10.244.1.85 web-2.nginx.default.svc.cluster.local
/ # curl web-2.nginx
web-2
/ # curl web-1.nginx
web-1
/ # curl web-0.nginx
web-0
可以看出成功解析且访问。
而当我们将replicas改为0后,StatefulSet会将pod从2 ,1,0有序删除。
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 0
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
[root@server1 statefulset]# kubectl get pod
NAME READY STATUS RESTARTS AGE
nfs-client-provisioner-6b66ddf664-zjvtv 1/1 Running 0 94m
test 1/1 Running 5 57m
删除后进行pod的重建:
[root@server1 statefulset]# vim deployment.yaml
[root@server1 statefulset]# cat deployment.yaml
更改:
replicas: 3
[root@server1 statefulset]# kubectl apply -f deployment.yaml
statefulset.apps/web configured
重键后测试访问:
[root@server1 statefulset]# kubectl attach test -it
/ # curl web-0.nginx
web-0
/ # curl web-1.nginx
web-1
/ # curl web-2.nginx
web-2
依然可以成功访问,这就验证了StatefulSet 控制器的特性。
最后将replicas改为0删除所有pod。
以上的一系列操作都是通过更改部署文件实现的,当然也可以使用命令的方式:
首先,想要弹缩的StatefulSet. 需先清楚是否能弹缩该应用.
$ kubectl get statefulsets <stateful-set-name>
$ kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
$ kubectl apply -f <stateful-set-file-updated>
$ kubectl edit statefulsets <stateful-set-name>
$ kubectl patch statefulsets <stateful-set-name> -p '{"spec":{"replicas":}}'