这篇文章已超过一年。较旧的文章可能包含过时内容。请检查页面信息自发布以来是否已失效。

Kubernetes 1.27:StatefulSet 起始序号简化迁移

Kubernetes v1.26 为 StatefulSet 引入了一个新的 Alpha 级别特性,用于控制 Pod 副本的序号编号。从 Kubernetes v1.27 开始,此特性已升级到 Beta。序号可以从任意非负数开始。本文将讨论如何使用此特性。

背景

StatefulSet 序号为 Pod 副本提供了顺序标识。使用 OrderedReady Pod 管理时,Pod 会按照序号索引 0N-1 的顺序创建。

如今在 Kubernetes 中,协调跨集群的 StatefulSet 迁移具有挑战性。备份和恢复解决方案确实存在,但这些方案要求应用在迁移前缩容到零副本。在当今完全连接的世界中,即使是计划内的应用停机也可能无法满足你的业务目标。你可以使用 Cascading DeleteOn Delete 来迁移单个 Pod,但这容易出错且管理繁琐。当 Pod 失败或被逐出时,你会失去 StatefulSet 控制器的自愈益处。

Kubernetes v1.26 使 StatefulSet 能够负责 {0..N-1} 范围内的序号(即从 0 到 N-1 的序号)。有了它,你可以在源集群中缩容 {0..k-1} 范围内的副本,并在目标集群中扩容剩余范围 {k..N-1} 的副本,同时保持应用可用性。这使得你在协调跨集群迁移时,能够保留最多一个的语义(意味着在一个 StatefulSet 中,具有给定标识的 Pod 最多只有一个)和 滚动更新行为。

我为什么想使用这个特性?

假设你在一个集群中运行 StatefulSet,需要将其迁移到另一个不同的集群。有很多原因可能导致你需要这样做:

  • 可扩展性:你的 StatefulSet 对当前集群而言规模过大,并已开始影响集群中其他工作负载的服务质量。
  • 隔离性:你在一个被多个用户访问的集群中运行 StatefulSet,并且 namespace 隔离不足。
  • 集群配置:你希望将 StatefulSet 迁移到另一个集群,以使用当前集群中不可用的某些环境。
  • 控制平面升级:你希望将 StatefulSet 迁移到运行已升级控制平面的集群,并且无法承担原地控制平面升级的风险或停机时间。

如何使用它?

在集群上启用 StatefulSetStartOrdinal Feature Gate,并创建一个具有自定义 .spec.ordinals.start 的 StatefulSet。

动手试试

在本演示中,我将使用新的机制将一个 StatefulSet 从一个 Kubernetes 集群迁移到另一个。将使用 redis-cluster Bitnami Helm Chart 来安装 Redis。

所需工具

先决条件

为此,我需要两个 Kubernetes 集群,它们都能够访问共同的网络和存储;我将我的集群命名为 sourcedestination。具体而言,我需要:

  • 在两个集群上都启用了 StatefulSetStartOrdinal Feature Gate。
  • 允许我作为管理员访问两个集群的 kubectl 客户端配置。
  • 在两个集群上都安装了相同的 StorageClass,并将其设置为两个集群的默认 StorageClass。此 StorageClass 应能提供可从任一或两个集群访问的基础存储。
  • 一种扁平网络拓扑,允许 Pod 在任一集群中相互发送和接收数据包。如果你在云提供商上创建集群,此配置可能被称为私有云或私有网络。
  1. 在两个集群上创建 demo namespace

    kubectl create ns kep-3335
    
  2. 在源集群中部署一个包含六个副本的 Redis 集群

    helm repo add bitnami https://charts.bitnami.com/bitnami
    helm install redis --namespace kep-3335 \
      bitnami/redis-cluster \
      --set persistence.size=1Gi \
      --set cluster.nodes=6
    
  3. 检查源集群中的复制状态

    kubectl exec -it redis-redis-cluster-0 -- /bin/bash -c \
      "redis-cli -c -h redis-redis-cluster -a $(kubectl get secret redis-redis-cluster -o jsonpath="{.data.redis-password}" | base64 -d) CLUSTER NODES;"
    
    2ce30362c188aabc06f3eee5d92892d95b1da5c3 10.104.0.14:6379@16379 myself,master - 0 1669764411000 3 connected 10923-16383                                                                                                                                              
    7743661f60b6b17b5c71d083260419588b4f2451 10.104.0.16:6379@16379 slave 2ce30362c188aabc06f3eee5d92892d95b1da5c3 0 1669764410000 3 connected                                                                                             
    961f35e37c4eea507cfe12f96e3bfd694b9c21d4 10.104.0.18:6379@16379 slave a8765caed08f3e185cef22bd09edf409dc2bcc61 0 1669764411000 1 connected                                                                                                             
    7136e37d8864db983f334b85d2b094be47c830e5 10.104.0.15:6379@16379 slave 2cff613d763b22c180cd40668da8e452edef3fc8 0 1669764412595 2 connected                                                                                                                    
    a8765caed08f3e185cef22bd09edf409dc2bcc61 10.104.0.19:6379@16379 master - 0 1669764411592 1 connected 0-5460                                                                                                                                                   
    2cff613d763b22c180cd40668da8e452edef3fc8 10.104.0.17:6379@16379 master - 0 1669764410000 2 connected 5461-10922
    
  4. 在目标集群中部署一个包含零个副本的 Redis 集群

    helm install redis --namespace kep-3335 \
      bitnami/redis-cluster \
      --set persistence.size=1Gi \
      --set cluster.nodes=0 \
      --set redis.extraEnvVars\[0\].name=REDIS_NODES,redis.extraEnvVars\[0\].value="redis-redis-cluster-headless.kep-3335.svc.cluster.local" \
      --set existingSecret=redis-redis-cluster
    
  5. 在源集群中将 redis-redis-cluster StatefulSet 缩容 1 个,以移除副本 redis-redis-cluster-5

    kubectl patch sts redis-redis-cluster -p '{"spec": {"replicas": 5}}'
    
  6. 将依赖项从源集群迁移到目标集群

    以下命令将资源从 source 复制到 destination。与 destination 集群无关的详细信息将被移除(例如:uid, resourceVersion, status)。

    源集群操作步骤

    注意:如果使用的 StorageClass 配置了 reclaimPolicy: Delete,在删除之前,你应该将 source 中的 PV 打补丁,设置 reclaimPolicy: Retain,以便保留在 destination 中使用的底层存储。更多详细信息请参阅更改 PersistentVolume 的回收策略

    kubectl get pvc redis-data-redis-redis-cluster-5 -o yaml | yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.annotations, .metadata.finalizers, .status)' > /tmp/pvc-redis-data-redis-redis-cluster-5.yaml
    kubectl get pv $(yq '.spec.volumeName' /tmp/pvc-redis-data-redis-redis-cluster-5.yaml) -o yaml | yq 'del(.metadata.uid, .metadata.resourceVersion, .metadata.annotations, .metadata.finalizers, .spec.claimRef, .status)' > /tmp/pv-redis-data-redis-redis-cluster-5.yaml
    kubectl get secret redis-redis-cluster -o yaml | yq 'del(.metadata.uid, .metadata.resourceVersion)' > /tmp/secret-redis-redis-cluster.yaml
    

    目标集群操作步骤

    注意:对于 PV/PVC,此过程仅在你的 PV 使用的底层存储系统支持复制到 destination 时才有效。与特定节点或拓扑关联的存储可能不受支持。此外,某些存储系统可能会在 PV 对象外部存储关于卷的额外元数据,并且可能需要更特殊的步骤来导入卷。

    kubectl create -f /tmp/pv-redis-data-redis-redis-cluster-5.yaml
    kubectl create -f /tmp/pvc-redis-data-redis-redis-cluster-5.yaml
    kubectl create -f /tmp/secret-redis-redis-cluster.yaml
    
  7. 在目标集群中将 redis-redis-cluster StatefulSet 扩容 1 个,起始序号为 5

    kubectl patch sts redis-redis-cluster -p '{"spec": {"ordinals": {"start": 5}, "replicas": 1}}'
    
  8. 检查目标集群中的复制状态

    kubectl exec -it redis-redis-cluster-5 -- /bin/bash -c \
      "redis-cli -c -h redis-redis-cluster -a $(kubectl get secret redis-redis-cluster -o jsonpath="{.data.redis-password}" | base64 -d) CLUSTER NODES;"
    

    我应该会看到新的副本(标记为 myself)已加入 Redis 集群(其 IP 地址属于与源集群中副本不同的 CIDR 块)。

    2cff613d763b22c180cd40668da8e452edef3fc8 10.104.0.17:6379@16379 master - 0 1669766684000 2 connected 5461-10922
    7136e37d8864db983f334b85d2b094be47c830e5 10.108.0.22:6379@16379 myself,slave 2cff613d763b22c180cd40668da8e452edef3fc8 0 1669766685609 2 connected
    2ce30362c188aabc06f3eee5d92892d95b1da5c3 10.104.0.14:6379@16379 master - 0 1669766684000 3 connected 10923-16383
    961f35e37c4eea507cfe12f96e3bfd694b9c21d4 10.104.0.18:6379@16379 slave a8765caed08f3e185cef22bd09edf409dc2bcc61 0 1669766683600 1 connected
    a8765caed08f3e185cef22bd09edf409dc2bcc61 10.104.0.19:6379@16379 master - 0 1669766685000 1 connected 0-5460
    7743661f60b6b17b5c71d083260419588b4f2451 10.104.0.16:6379@16379 slave 2ce30362c188aabc06f3eee5d92892d95b1da5c3 0 1669766686613 3 connected
    
  9. 对其余副本重复步骤 #5 到 #7,直到源集群中的 Redis StatefulSet 缩容到 0,并且目标集群中的 Redis StatefulSet 健康运行,总共有 6 个副本。

下一步是什么?

此特性为将 StatefulSet 分散到跨集群提供了构建块,但并未规定 StatefulSet 应如何迁移的机制。迁移需要协调 StatefulSet 副本,以及编排存储和网络层。这取决于 StatefulSet 安装的应用的存储和连接要求。此外,许多 StatefulSet 由 Operator 管理,这为迁移增加了另一层复杂性。

如果你有兴趣构建增强功能以简化这些过程,请加入 SIG Multicluster 贡献!