• Post author:
  • Post category:Kubernetes
  • Post comments:0评论
Like
Like Love Haha Wow Sad Angry
基于 Kubernetes 1.22 版本

一、基础概念

  StatefulSet 是用于管理有状态应用程序的,所谓有状态即应用程序在操作中会存储数据。它管理着一组 Pod 的部署和扩展,并保证这些 Pod 的顺序和唯一性。
  在 Kubernetes 中 Pod 的管理对象 Deployment、DaemonSet、Job 都是面向无状态服务的。但实际中有很多服务是有状态,特别是存储或者中间件集群,例如 MySQL、MongoDB、Kafka、ZooKeeper 集群等。
  与 Deployment 类似,StatefulSet 管理基于相同容器规范的 Pod。与 Deployment 不同的是,StatefulSet 会为每个 Pod 维护一个粘性标识。这些 Pod 是根据相同的规范创建的,但不可互换。每个 Pod 都有一个持久标识符,它在任何重新调度过程中都会维护该标识符。
  StatefulSet 应用场景:

• 稳定唯一的网络标识符。
• 稳定持久的存储
• 有序优雅的部署和扩展
• 有序的自动滚动更新

使用 StatefulSet 的一些限制:

• 给定 Pod 的存储必须由 PersistentVolume Provisioner 根据请求的 Storage Class 进行配置,或者由管理员预先配置。
• 删除和缩小 StatefulSet 不会删除与 StatefulSet 关联的卷,这样做是为了确保数据安全。
• StatefulSets 目前需要一个 Headless Service 来负责 Pod 的网络标识,你需要责任创建此服务。
• StatefulSet 不保证删除StatefulSet 时 Pod 的终止。为了实现 StatefulSet 中 Pod 的有序和优雅终止,可以在删除之前将 StatefulSet 缩小到 0。
• 使用带有默认 Pod 管理策略(OrderedReady)的滚动更新时,可能会进入需要手动干预才能修复的损坏状态。

官方文档:

二、定义详解

我们先来看一个示例:

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         # 必须匹配 .spec.template.metadata.labels
  serviceName: "nginx"   # 管理此 StatefulSet 的服务的名称。该服务必须存在于 StatefulSet 之前,并负责该集合的网络标识。
  replicas: 3            # 副本数,默认为1
  minReadySeconds: 10    # 最小就绪秒数,默认为0
  template:         # Pod定义模板
    metadata:
      labels:
        app: nginx  # 必须匹配 .spec.template.metadata.labels
    spec:
      terminationGracePeriodSeconds: 10     # Pod 需要优雅终止的可选持续时间
      containers:
      - name: nginx
        image: k8s.gcr.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:     # PVC模板
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

在上面的例子中:

• 名为 nginx 的 Headless Service 用于控制网络域。
• 名为 web 的 StatefulSet 有一个规范,指出 nginx 容器的 3 个副本将在唯一的 Pod 中启动。
• 该 volumeClaimTemplates 会使用由 PersistentVolume 提供稳定的存储。

1、Pod 标识

  StatefulSet Pods 有一个唯一的标识,它由一个序号、一个稳定的网络标识和一个稳定的存储组成。无论它(重新)调度在哪个节点上,标识都会附着在 Pod 上。

(1)序数索引

  对于具有 N 个副本的 StatefulSet,StatefulSet 中的每个 Pod 将被分配一个整数序数,从 0 到 N-1,它在整个 Set 中是唯一的。

(2)稳定的网络ID

  StatefulSet 中的每个 Pod 都从 StatefulSet 的名称和 Pod 的序号派生其主机名。构造的主机名的模式是 $(statefulset name)-$(ordinal)。上面的示例将创建三个名为 web-0、web-1、web-2。
  StatefulSet 可以使用Headless Service 来控制其 Pod 的域。此服务管理的域采用以下形式: $(service name).$(namespace).svc.cluster.local,其中“cluster.local”是集群域。在创建每个 Pod 时,它会获得一个匹配的 DNS 子域,格式为: $(podname).$(governing service domain),其中管理服务由 StatefulSet 上的 serviceName 字段定义。
  根据集群中 DNS 的配置方式,您可能无法立即查找新运行 Pod 的 DNS 名称。当集群中的其他客户端在创建 Pod 之前已经发送了对主机名的查询时,可能会发生此行为。负缓存(在 DNS 中很正常)意味着即使在 Pod 运行后至少几秒钟,之前失败的查找结果也会被记住并重复使用。
  如果您需要在创建 Pod 后立即发现它们,您有以下几种选择:

• 直接查询 Kubernetes API,而不是依赖 DNS 查找。
• 减少 Kubernetes DNS 提供程序中的缓存时间(通常这意味着编辑 CoreDNS 的配置映射,当前缓存 30 秒)。

以下是集群域、服务名称、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。

(3)稳定的存储

  对于 StatefulSet 中定义的每个 VolumeClaimTemplate 条目,每个 Pod 都会收到一个 PVC。在上面的 nginx 示例中,每个 Pod 都会收到一个 PV,其中 StorageClass 为 my-storage-class 和 1 Gib 的预置存储。如果未指定 StorageClass,则将使用默认 StorageClass。当 Pod 被(重新)调度到节点上时,其 volumeMounts 会挂载与其 PVC 关联的 PV。 请注意,当 Pod 或 StatefulSet 被删除时,与 Pod 的 PVC 关联的 PV 不会被删除,这必须手动完成。

(4)Pod 名称标签

  当 StatefulSet 控制器创建一个 Pod,它会添加一个标签 statefulset.kubernetes.io/pod-name,该标签设置为 Pod 的名称。此标签允许您将 Service 附加到 StatefulSet 中的特定 Pod。

2、部署和扩展

• 对于有 N 个副本的 StatefulSet,在部署 Pod 时,它们是按顺序创建的,从 {0..N-1} 开始。
• 当 Pod 被删除时,它们会以相反的顺序从 {N-1..0} 终止。
• 在将扩展操作应用于 Pod 之前,它的所有前任都必须处于 Running 和 Ready 状态。
• 在终止 Pod 之前,必须完全关闭其所有后继者。

StatefulSet 不应将 pod.Spec.TerminationGracePeriodSeconds 指定为 0。这种做法是不安全的,强烈建议不要这样做。进一步解释请参考 强制删除 StatefulSet Pods

  上面的 nginx 示例创建后,会按照 web-0、web-1、web-2 的顺序部署三个 Pod。在 web-0 处于Running 和 Ready 状态之前不会部署 web-1,而在 web-1 处于Running 和 Ready 状态之前不会部署 web-2。如果 web-0 失败,则在 web-1 运行并准备好之后,但在 web-2 启动之前,web-2 将不会启动,直到 web-0 成功重新启动并变为运行并准备好。
  果用户要通过编辑 StatefulSet 来扩展已部署的示例,例如设置 replicas=1。则 web-2 将首先终止。在 web-2 完全关闭并删除之前,web-1 不会被终止。如果 web-0 在 web-2 终止并完全关闭之后失败,但在 web-1 终止之前,web-1 将不会终止,直到 web-0 运行并准备好。

3、PVC 保留

  在 1.23 中,有可选 .spec.persistentVolumeClaimRetentionPolicy 字段控制在 StatefulSet 的生命周期中是否保留或者删除 PVC。
  您必须启用 StatefulSetAutoDeletePVC feature gate 才能使用此字段。启用后,您可以为每个 StatefulSet 配置两个策略:

  • whenDeleted:配置删除 StatefulSet 时应用的卷保留行为。
  • whenScaled:配置当 StatefulSet 的副本数减少时应用的卷保留行为。

对于上面两个策略,可以将值设置为 Delete 或 Retain。

  • Delete:对于受策略影响的每个Pod,将删除从 StatefulSet volumeClaimTemplate 创建的PVC。使用 whenDeleted 策略,volumeClaimTemplate 中的所有PVC 将在其 Pod 被删除后被删除。使用 whenScaled 策略,在删除 Pod 副本后,仅删除与正在缩小的 Pod 副本相对应的PVC。
    • Retain(默认):volumeClaimTemplate 中的 PVC 在其 Pod 被删除时不受影响。1.23 之前版本也是这样的行为。

三、示例

下面创建一个 StatefulSet, 同时创建了一个 Headless Service nginx:

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
  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: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: nfs
      resources:
        requests:
          storage: 1Gi

查看 StatefulSet Pod 创建过程,如上所述为顺序创建,当然 StatefulSet 也支持并行启动。

[root@cp statefulset]# kubectl apply -f web.yaml
[root@cp statefulset]# kubectl get pod -w
NAME    READY   STATUS              RESTARTS   AGE
web-1   0/1     Pending             0          0s
web-0   0/1     ContainerCreating   0          12s
web-0   1/1     Running             0          30s
web-1   0/1     Pending             0          0s
web-1   0/1     ContainerCreating   0          11s
web-1   1/1     Running             0          18s

[root@cp statefulset]# kubectl get sts
NAME   READY   AGE
web    2/2     82s

查看 PV 和 PVC,删除 StatefulSet 后,其 Pod 关联 PVC 和 PV 默认会保留。

[root@cp statefulset]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS   REASON   AGE
pvc-26df6b71-1e95-470d-bfd0-88b2ceaa2fb1   1Gi        RWO            Delete           Bound    default/www-web-0    nfs                     6m20s
pvc-ba4e5bf1-85b1-47e0-bbe8-b8d94fa4a510   1Gi        RWO            Delete           Bound    default/www-web-1    nfs                     6m1s

[root@cp statefulset]# kubectl get pvc
NAME        STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
www-web-0   Bound    pvc-26df6b71-1e95-470d-bfd0-88b2ceaa2fb1   1Gi        RWO            nfs            6m25s
www-web-1   Bound    pvc-ba4e5bf1-85b1-47e0-bbe8-b8d94fa4a510   1Gi        RWO            nfs            6m6s

DNS 测试:

[root@cp statefulset]# kubectl run -it --rm --image busybox:1.28 dns-test
/ # nslookup web-0.nginx
Server:    10.1.0.10
Address 1: 10.1.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-0.nginx
Address 1: 10.244.0.192 web-0.nginx.default.svc.cluster.local
/ # nslookup web-1.nginx
Server:    10.1.0.10
Address 1: 10.1.0.10 kube-dns.kube-system.svc.cluster.local

Name:      web-1.nginx
Address 1: 10.244.0.193 web-1.nginx.default.svc.cluster.local

访问测试:

[root@cp ~]# for i in 0 1; do kubectl exec "web-$i" -- sh -c 'echo "$(hostname)" > /usr/share/nginx/html/index.html'; done
[root@cp ~]# for i in 0 1; do kubectl exec -it "web-$i" -- curl http://localhost/; done
web-0
web-1

# 使用 FQDN 的方式访问
[root@cp statefulset]# kubectl run -it --rm --image=nginx test -- bash
If you don't see a command prompt, try pressing enter.
root@test:/# curl web-0.nginx
web-0
root@test:/# curl web-1.nginx
web-1


参考文章:

https://kubernetes.io/docs/

Like
Like Love Haha Wow Sad Angry

发表评论