1.StatulSet概述

1.1 Stateful应用和Stateless应用

应用程序与用户、设备、其他应用程序或外部组件进行通信时,根据其是否需要记录前一次或多次通信中的相关事件信息以作为下一次通信的分类标准,可以将那些需要记录信息的应用程序称为有状态(stateful)应用,而无须记录的则称为无状态(stateless)应用。

下面我们先来了解下状态和存储的关系:

  • 状态是进程的时间属性:无状态意味着一个进程不必跟踪过去的交互操作,本质上可以说它是一个纯粹的功能性行为。对应地,有状态则意味着进程存储了以前交互过程的记录,并且可以基于它对新的请求进行响应。
  • 存储是表述持久保存数据的方法,现今通常是指机械硬盘或 SSD设备。若进程仅需操作内存中的数据,则表示其无须进行磁盘 I/O操作;如果产生了I/O操作,则通常意味着数据 的只读访问或读写访问行为。

基于状态和存储这两个概念,可以归结出如下几种应用程序类型:

  • 具有读写磁盘需求的有状态应用程序,如支持事务功能的各种 RDBMS存储系统;另外各种分布式存储系统也是此类应用程序的典型,如 Redis Cluster、MongoDB、ZooKeeper等;
  • 一类是那些具有读写磁盘需求的无状态应用程序,如具有幕等性的文件上传类服务程序;另一类是仅需只读类I/O访问的无状态应用程序,例如,从外部存储加载静态资摞以响应用户请求的Web服务程序;
  • 无磁盘访问需求的无状态应用程序,如地理坐标转换器应用;
  • 无磁盘访问需求的有状态应用程序,如电子商城程序中的购物车系统;

1.2 StatefulSet控制器概述

StatefulSet是为了解决有状态服务的问题(对应Deployments和ReplicaSets是为无状态服务而设计),其应用场景包括:

  • 稳定的持久化存储,即Pod重新调度后还是能访问到相同的持久化数据,基于PVC来实现;
  • 稳定的网络标志,即Pod重新调度后其PodName和HostName不变,基于Headless Service(即没有ClusterIP的Service)来实现;
  • 有序部署,有序扩展,即Pod是有顺序的,在部署或者扩展的时候要依据定义的顺序依次依次进行(即从0到N-1,在下一个Pod运行之前所有之前的Pod必须都是Running和Ready状态),基于init containers来实现;
  • 有序收缩,有序删除(即从N-1到0)

1.3 StatefulSet的特性

StatefulSet是Pod资源控制器的一种实现,用于部署和扩展有状态应用的Pod资源,确保它们的运行顺序及每个Pod资源的唯一性。其与Deployment控制器不同的是,虽然所有的Pod对象都基于同一个spec配置所创建,但StatefulSet需要为每个Pod维持一个唯一且固定的标识符,必要时还要为其创建专有的存储卷。 StatefulSet主要适用于那些依赖于下列类型资源的应用程序:

  • 稳定且唯一的网络标识符;
  • 稳定且持久的存储;
  • 有序、优雅地部署和扩展;
  • 有序、优雅地删除和终止;
  • 有序而自动地滚动更新;

一个典型、完整可用的StatefulSet通常由三个组件构成:

  • Headless Service用于为Pod资源标识符生成可解析的 DNS资源记录;
  • StatefulSet用于管控Pod资源;
  • volumeClaimTemplate则基于静态或动态的PV供给方式为 Pod资源提供专有且固定的存储;

StatefulSet中每个Pod的DNS格式为statefulSetName-{0..N-1}.serviceName.namespace.svc.cluster.local,其中

  • serviceName为Headless Service的名字
  • 0..N-1为Pod所在的序号,从0开始到N-1
  • statefulSetName为StatefulSet的名字
  • namespace为服务所在的namespace,Headless Servic和StatefulSet必须在相同的namespace
  • .cluster.local为Cluster Domain

2.StatefulSet基础应用

2.1 创建StatefulSet对象

如前所述,一个完整的StatefulSet控制器需要由一个Headless Service、一个StatefulSet和一个volumeClaimTemplate组成。如下面的资源清单中的定义所示:

  1. apiVersion: v1
  2. kind: Service
  3. metadata:
  4. name: myapp-svc
  5. labels:
  6. app: myapp-svc
  7. spec:
  8. ports:
  9. - port: 80
  10. name: web
  11. clusterIP: None
  12. selector:
  13. app: myapp-pod
  14. ---
  15. apiVersion: apps/v1
  16. kind: StatefulSet
  17. metadata:
  18. name: myapp
  19. spec:
  20. serviceName: myapp-svc
  21. replicas: 2
  22. selector:
  23. matchLables:
  24. app: myapp-pod
  25. template:
  26. metadata:
  27. labels:
  28. app: myapp-pod
  29. spec:
  30. containers:
  31. - name: myapp
  32. image: ikubernetes/myapp:v5
  33. ports:
  34. - containerPort: 80
  35. name: web
  36. volumeMounts:
  37. - name: myappdata
  38. mountPath: /usr/share/nginx/html
  39. volumeClaimTeplates:
  40. - metadata:
  41. name: myappdata
  42. spec:
  43. accessModes: ["ReadWriteOnce"]
  44. storageClassName: "gluster-dynamic"
  45. resources:
  46. requests:
  47. storage: 2Gi

上面示例中的配置定义在statefulset-demo.yaml文件中。由于Statefu!Set资源依赖于一个事先存在的Headless类型的Service资源,因此,这里首先定义了一个名为 myapp-svc的HeadlessService资源,用于为关联到的每个Pod资源创建DNS资源记录。接着定义了-个名为myapp的StatefulSet资源,它通过Pod模板创建了两个Pod资源副本,并基于volumeClaimTemplates(存储卷申请模板)向gluster-dynamic存储类请求动态供给PV,从而为每个Pod资源提供大小为2GB的专用存储卷。

默认情况下,StatefulSet控制器以串行的方式创建各Pod副本,如果想要以并行方式创建和删除Pod资源,则可以设定.spec.podManagementPolicy字段的值为“Parallel”,默认值为“OrderedReady”。

2.2 Pod资源标识符及存储卷

由StatefulSet控制器创建的Pod资源拥有固定、唯一的标识和专用存储卷,即便重新调度或终止后重建,其名称也依然保持不变,且此前的存储卷及其数据不会丢失。

2.2.1 Pod资源的固定标识符

由StatefulSet控制器创建的Pod对象拥有固定且唯一的标识符,它们基于唯一的索引序号及相关的StatefulSet对象的名称而生成,格式为”-”。

# kubectl get pods -l app=myapp-pod
NAME      READY   STATUS    RESETARTS   AGE
myapp-01  1/1     Running   0           4m
myapp-02  1/1     Running   0           3m

Pod资源的主机名同其资源名称,因此也是带索引序号的名称格式,如下面的命令结果所示:

# for i in 0 1; do kubectl exec myapp-$i -- sh -c 'hostname'; done
myapp-0
myapp-1

这些名称标识会由StatefulSet资源相关的Headless Service资源创建为DNS资源记录,其域名格式为$(servicename).$(nam巳space).svc.cluster.local, 其中“cluster.local”是集群的默认域名。在Pod资源创建后,与其相关的DNS资源记录格式为“$(pod_name).$(service name).$(namespace).svc.cluster.local”,例如前面创建的两个Pod资源的资源记录为 myapp-0.myapp-svc.default.svc.cluster.local 和 myapp-1.myapp-svc.default.svc.cluster.local。

2.2.2 Pod资源的专有存储卷

前面的StatefulSet资源示例中,控制器通过volumeClaimTemplates为每个Pod副本自动创建并关联一个PVC对象,它们分别绑定了一个动态供给的PV对象。

删除StatefulSet控制器的Pod资源,其存储卷并不会被删除,除非用户或管理员手动操作移除操作。因此,在另一个终端中删除Pod资源myapp-0,经由StatefulSet控制器重建后,它依然会关联到此前的PVC存储卷上,且此前数据依旧可用。

2.3 StatefulSet资源扩缩容

StatefulSet资源的扩缩容与Deployment资源相似,即通过修改资源的副本数来改动其目标Pod资源数量。对StatefulSet资源来说,kubectl scale和kubectl patch命令均可实现此功能,也可以使用kubectl edit命令直接修改其副本数,或者在修改配置文件之后,由kubectl apply命令重新声明。

3.StatefulSet资源升级

StatefulSet资源支持自动更新机制,其更新策略将由spec.updateStrategy字段定义,默认为RollingUpdate,即滚动更新。另一个可用策略为OnDelete,即删除Pod资源重建以完成更新。

3.1 滚动更新

滚动更新StatefulSet控制器的Pod资源以逆序的形式从其最大索引编号的Pod资源逐一进行,它在终止一个Pod资源、更新资源并待其就绪后启动更新下一个资源,即索引号 比当前号小1的Pod资源。对于主从复制类的集群应用来说,这样也能保证起主节点作用的Pod资源最后进行更新,确保兼容性。

StatefulSet的默认更新策略为滚动更新,通过kubectl get statefulset NAME 命令中的输出可以获取相关的信息,myapp控制器的输出如下所示:

updateStrategy:
  rollingUpdate:
    partition: 0
  type: RollingUpdate

3.2 暂存更新操作

当用户需要设定一个更新操作,但又不希望它立即执行时,可将更新操作予以“暂存”,待条件满足后再手动触发其执行更新。StatefulSet资源的分区更新机制能够实现此项功能。在设定更新操作之前,将.spec.update-Strategy.rollingUpdate.partition字段的值设置为Pod资源的副本数量,即比Pod资源的最大索引号大1,这就意味着,所有的Pod资源都不会处于可直接更新的分区之内,那么于其后设定的更新操作也就不会真正执行,直到用户降低分区编号至现有Pod资源索引号范围之内。

暂存状态的更新操作对所有的Pod资源均不产生影晌。

3.3 金丝雀部署

将处于暂存状态的更新操作的partition定位于Pod资源的最大索引号,即可放出一只金丝雀,由其测试第一轮的更新操作,在确认无误后通过修改partition属性的值更新其他的Pod对象是一种更为稳妥的更新操作。