概述

有状态应用在 Kubernetes 里对应的对象就是 StatefulSet。跟 Deployment 一样,StatefulSet 也是一种控制器,区别是一个控制无状态应用,一个控制有状态应用。当一个有状态的 Pod 挂掉之后(或者是所在节点出现故障),这个 Pod 实例必须在其它的节点上重建,但是新的实例必须和被替换的实例拥有相同的名称、网络标识和状态,StatefulSet 可以保证 Pod 在重新调度以后保留它们之前的标识和状态,这样就可以方便的扩缩容。
应用一旦牵扯到状态,控制起来就会比较麻烦,因此 StatefulSet 要比 Deployment 复杂许多。具体来说,

适用场景

StatefulSet 适合具有以下需求的场景:

  • 稳定的持久化存储 Pod 在重新调度之后还能继续访问之前的存储数据
  • 稳定的网络标志 Pod 在重新调度后其名字和主机名保持不变
  • 有序部署:构成应用的多个 Pod 启动顺序是固定的,适合 Pod 之间有依赖关系的情况
  • 有序删除:删除应用时 Pod 销毁顺序跟启动顺序正好相反

StatefulSet 中每个 Pod 具有固定的名字,Pod 的 DNS 域名格式为 statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local。跟普通域名一样,子域在前,主域在后,只不过节数比较多而已。各部分含义如下:

  • statefulSetName:StatefulSet 名字
  • 0..N-1 Pod 的序号:从 0 到 N-1
  • serviceName:Headless Service(没有 ClusterIP 的 Service) 名字,StatefulSet 无法使用普通的 Service(有一个 ClusterIP 作为服务入口) 来提供应用服务(包括负载均衡)。这是因为 StatefulSet 里的各个 Pod 不是对等的,只能由用户自己来实现服务内部的网络路由
  • namespace:服务所在的命名空间,Headless Service 和 StatefulSet 必须在相同的命名空间下
  • svc.cluster.local:集群域名

StatefulSet 的设置由以下 3 部分组成:

  • 用于定义网络标志的 Headless Service
  • 用于创建 PV(持久存储卷)的 volumeClaimTemplates
  • 定义具体应用的 StatefulSet

    Headless Service(无头服务)

    Headless Service 是指不为 Service 设置 ClusterIP(入口 IP 地址),只通过标签选择器将后端的 Pod 列表返回给调用的客户端,客户端自行决定如何处理这个 Pod 列表。
    在前面讲解 Deployment 时,我们知道每个创建的 Pod 的名称都是没有顺序的,名称最后有一截 Hash 值,因此这样创建的 Pod 是无序的。但是 StatefulSet 中要求 Pod 名称必须有序,每个 Pod 名称具有唯一性,并且当 Pod 重建后名称依然要保持一致,Pod 的名称就是唯一性的标志,必须持久有效,因此会使用 Headless Service 服务。
  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: nginx-headle-svc
  5. namespace: default
  6. labels:
  7. app: nginx
  8. spec:
  9. selector:
  10. app: nginx-sts
  11. ports:
  12. - name: http
  13. port: 80
  14. clusterIP: None

执行

  1. kubectl apply -f headle-svc.yaml
  2. service/nginx-headle-svc created

查看创建结果

  1. $ kubectl get svc -l app=nginx
  2. NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
  3. nginx-headle-svc ClusterIP None <none> 80/TCP 2m25s

Headless Service 所代理的所有 Pod 的 IP 地址都会绑定一个如下所示的 DNS 记录:

  1. <pod-name>.<svc-name>.<namespace>.svc.cluster.local

volumeClaimTemplates(卷申请模板)

有状态自然是需要持久化的,所以 StatefulSet 引入了 PV(持久存储卷) 和 PVC(持久存储卷声明) 对象来持久存储服务产生的状态,这样即便服务可能被 kill 掉或是重启,但是由于数据保存在 PV 中,所以这些状态也不会丢失。它们之间的关系如下图所示:
Kubernetes——StatefulSet - 图1
在前面讲解 Deployment 时,直接在 Pod template 中定义存储卷,所有副本集使用同一个存储卷,数据都是相同的。但是 StatefulSet 中要求有状态存储,也就意味着数据不一样,每个节点必须有专有存储卷,不能使用相同存储卷,因此需要使用 volumeClaimTemplates,为每个 Pod 生成不同的 PVC,并绑定 PV,这样就实现了 Pod 分别有专属的存储卷。

创建pv.yaml

  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4. name: pv01
  5. spec:
  6. capacity:
  7. storage: 1Mi
  8. accessModes:
  9. - ReadWriteOnce
  10. hostPath:
  11. path: /tmp/pv01
  12. ---
  13. apiVersion: v1
  14. kind: PersistentVolume
  15. metadata:
  16. name: pv02
  17. spec:
  18. capacity:
  19. storage: 1Mi
  20. accessModes:
  21. - ReadWriteOnce
  22. hostPath:
  23. path: /tmp/pv02

执行

  1. $ kubectl apply -f pv.yaml
  2. persistentvolume/pv01 created
  3. persistentvolume/pv02 created

查看创建结果,到成功创建了两个 PV 对象,状态是:Available

  1. $ kubectl get pv
  2. NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
  3. pv01 1Mi RWO Retain Available 2m7s
  4. pv02 1Mi RWO Retain Available 2m7s

创建nginx-sts.yaml

  1. apiVersion: apps/v1
  2. kind: StatefulSet
  3. metadata:
  4. name: nginx-sts
  5. spec:
  6. selector:
  7. matchLabels:
  8. app: nginx-sts
  9. serviceName: nginx-headle-svc
  10. replicas: 2
  11. template:
  12. metadata:
  13. labels:
  14. app: nginx-sts
  15. spec:
  16. containers:
  17. - name: nginx
  18. image: nginx:1.14
  19. ports:
  20. - containerPort: 80
  21. name: web
  22. volumeMounts:
  23. - name: www
  24. mountPath: /usr/share/nginx/html
  25. volumeClaimTemplates:
  26. - metadata:
  27. name: www
  28. spec:
  29. accessModes: [ "ReadWriteOnce" ]
  30. resources:
  31. requests:
  32. storage: 1Mi

执行

  1. $ kubectl apply -f nginx-sts.yaml
  2. statefulset.apps/nginx-sts created
  3. $ kubectl get pod -l app=nginx-sts
  4. NAME READY STATUS RESTARTS AGE
  5. nginx-sts-0 0/1 ContainerCreating 0 111s

查看创建过程

  1. $ kubectl get pod -l app=nginx-sts -w
  2. NAME READY STATUS RESTARTS AGE
  3. nginx-sts-0 1/1 Running 0 4m48s
  4. nginx-sts-1 0/1 ContainerCreating 0 96s
  5. nginx-sts-1 1/1 Running 0 2m55s

分别执行

  1. for i in 0 1; do kubectl exec nginx-sts-$i -- sh -c 'echo hello $(hostname) > /usr/share/nginx/html/index.html'; done

查看pvc

  1. kubectl get pvc
  2. NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
  3. www-nginx-sts-0 Bound pv01 1Mi RWO 14m
  4. www-nginx-sts-1 Bound pv02 1Mi RWO 11m

创建一个buybox

  1. kubectl run -it my-box --image busybox:1.28.3 --restart=Never --rm /bin/sh
  2. If you don't see a command prompt, try pressing enter.
  3. / # nslookup nginx-headle-svc
  4. Server: 10.96.0.10
  5. Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
  6. Name: nginx-headle-svc
  7. Address 1: 100.111.156.103 nginx-sts-1.nginx-headle-svc.default.svc.cluster.local
  8. Address 2: 100.116.59.114 nginx-sts-0.nginx-headle-svc.default.svc.cluster.local
  9. / # ping nginx-headle-svc
  10. PING nginx-headle-svc (100.116.59.114): 56 data bytes
  11. 64 bytes from 100.116.59.114: seq=0 ttl=63 time=0.137 ms
  12. 64 bytes from 100.116.59.114: seq=1 ttl=63 time=0.212 ms

查看结果

  1. / # nslookup nginx-sts-0.nginx-headle-svc
  2. Server: 10.96.0.10
  3. Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
  4. Name: nginx-sts-0.nginx-headle-svc
  5. Address 1: 100.116.59.114 nginx-sts-0.nginx-headle-svc.default.svc.cluster.local
  6. / # nslookup nginx-sts-1.nginx-headle-svc
  7. Server: 10.96.0.10
  8. Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
  9. Name: nginx-sts-1.nginx-headle-svc
  10. Address 1: 100.111.156.103 nginx-sts-1.nginx-headle-svc.default.svc.cluster.local

wget 执行结果

  1. / # wget nginx-sts-0.nginx-headle-svc -O- -q
  2. hello nginx-sts-0
  3. / # wget nginx-sts-1.nginx-headle-svc -O- -q
  4. hello nginx-sts-1