|Index|SUSE Telco Cloud 文档|第 2 天运维|生命周期操作
适用范围 SUSE Telco Cloud 3.7

60 生命周期操作

本节涵盖了通过 SUSE Telco Cloud 部署的集群的生命周期管理操作。

60.1 负载均衡器排除

有许多生命周期操作需要对节点进行排空。在排空过程中,所有 Pod 都将被移动到集群中的其他节点。排空过程完成后,该节点不再承载任何服务,因此不应有任何流量路由到该节点。可以通过为节点应用标签,使 MetalLB 等负载均衡器感知到这一点:

node.kubernetes.io/exclude-from-external-load-balancers: "true"

有关更多详细信息,请参阅: Kubernetes 文档。

要查看集群中所有节点的标签,您可以运行:

kubectl get nodes -o json | jq -r '.items[].metadata | .name, .labels'

对于下游集群的升级,可以通过在管理集群上对 RKE2ControlPlane 添加注释来实现自动化:

rke2.controlplane.cluster.x-k8s.io/load-balancer-exclusion="true"

这会立即在管理集群上为该 RKE2ControlPlane 的所有机器对象创建注释。

pre-drain.delete.hook.machine.cluster.x-k8s.io/rke2-lb-exclusion: ""

有了机器对象上的此注释,下游集群中任何计划进行排空的节点都会在排空过程开始前附加上述节点标签。一旦节点再次可用并就绪,该标签将从节点中移除。

60.2 管理集群升级

管理集群的升级在 Day 2 管理集群 (第 59 章 “管理群集”) 文档中有相关描述。

60.3 下游集群升级

升级下游集群涉及更新多个组件。以下章节涵盖了每个组件的升级过程。

升级操作系统

对于此过程,请查看以下 参考 (第 50 章 “为连接场景准备下游集群镜像”) 以使用新的操作系统版本构建新镜像。 使用由 EIB 生成的新镜像,下一个配置阶段将使用所提供的新操作系统版本。 在接下来的步骤中,新镜像将用于升级节点。

升级 RKE2 集群

使用自动化工作流升级 RKE2 集群所需的更改如下:

  • 更改以下 部分 (第 52 章 “使用定向网络配置(单节点)进行下游集群配置”) 中显示的 capi-provisioning-example.yaml 中的块 RKE2ControlPlane:

    • 指定所需的 rolloutStrategy

      • 请注意,在至少包含 3 个控制平面节点的 RKE2 多节点集群中,将 RKE2ControlPlane.spec.rolloutStrategy.type 字段设置为 "RollingUpdate" 并将 rolloutStrategy.rollingUpdate.maxSurge 设置为 0,可确保在相关 Metal3MachineTemplate.spec.nodeReuse 也设置为 True 时,升级期间不会创建或需要额外的节点(如下所述)。

    • 将 RKE2 集群的版本更改为替换 ${RKE2_NEW_VERSION} 的新版本。

apiVersion: controlplane.cluster.x-k8s.io/v1beta2
kind: RKE2ControlPlane
metadata:
  name: multinode-cluster
  namespace: default
spec:
  infrastructureRef:
    apiGroup: infrastructure.cluster.x-k8s.io
    kind: Metal3MachineTemplate
    name: multinode-cluster-controlplane
  version: ${RKE2_NEW_VERSION}
  replicas: 3
  rolloutStrategy:
    type: "RollingUpdate"
    rollingUpdate:
      maxSurge: 0
  serverConfig:
    cni: cilium
    tlsSan:
      - ${EDGE_VIP_ADDRESS_IPV4}
      - ${EDGE_VIP_ADDRESS_IPV6}
      - https://${EDGE_VIP_ADDRESS_IPV4}.sslip.io
      - https://${EDGE_VIP_ADDRESS_IPV6}.sslip.io
  rolloutStrategy:
    rollingUpdate:
      maxSurge: 0
  registrationMethod: "control-plane-endpoint"
  registrationAddress: ${EDGE_VIP_ADDRESS}
  agentConfig:
    format: ignition
    additionalUserData:
      config: |
        variant: fcos
        version: 1.4.0
        systemd:
          units:
          - name: rke2-preinstall.service
            enabled: true
            contents: |
              [Unit]
              Description=rke2-preinstall
              Wants=network-online.target
              Before=rke2-install.service
              ConditionPathExists=!/run/cluster-api/bootstrap-success.complete
              [Service]
              Type=oneshot
              User=root
              ExecStartPre=/bin/sh -c "mount -L config-2 /mnt"
              # Note we use _ as a delimiter for sed here because the providerid includes / characters
              ExecStart=/bin/sh -c "sed -i \"s_PROVIDERID_$(jq -r .providerid /mnt/openstack/latest/meta_data.json)_\" /etc/rancher/rke2/config.yaml"
              ExecStart=/bin/sh -c "echo \"node-name: $(jq -r .name /mnt/openstack/latest/meta_data.json)\" >> /etc/rancher/rke2/config.yaml"
              ExecStart=/bin/sh -c "echo \"node-label:\" >> /etc/rancher/rke2/config.yaml"
              ExecStart=/bin/sh -c "echo \"  - metal3.io/uuid=$(jq -r .uuid /mnt/openstack/latest/meta_data.json)\" >> /etc/rancher/rke2/config.yaml"
              ExecStartPost=/bin/sh -c "umount /mnt"
              [Install]
              WantedBy=multi-user.target
        storage:
          directories:
          - path: /var/lib/rancher/rke2/server/manifests
            overwrite: true
          files:
            - path: /var/lib/rancher/rke2/server/manifests/endpoint-copier-operator.yaml
              overwrite: true
              contents:
                inline: |
                  apiVersion: helm.cattle.io/v1
                  kind: HelmChart
                  metadata:
                    name: endpoint-copier-operator
                    namespace: kube-system
                  spec:
                    chart: oci://registry.suse.com/edge/charts/endpoint-copier-operator
                    targetNamespace: endpoint-copier-operator
                    version: {version-endpoint-copier-operator-chart}
                    createNamespace: true
            - path: /var/lib/rancher/rke2/server/manifests/metallb.yaml
              overwrite: true
              contents:
                inline: |
                  apiVersion: helm.cattle.io/v1
                  kind: HelmChart
                  metadata:
                    name: metallb
                    namespace: kube-system
                  spec:
                    chart: oci://registry.suse.com/edge/charts/metallb
                    targetNamespace: metallb-system
                    version: {version-metallb-chart}
                    createNamespace: true
            - path: /var/lib/rancher/rke2/server/manifests/metallb-cr.yaml
              overwrite: true
              contents:
                inline: |
                  apiVersion: metallb.io/v1beta1
                  kind: IPAddressPool
                  metadata:
                    name: kubernetes-vip-ip-pool
                    namespace: metallb-system
                  spec:
                    addresses:
                      - ${EDGE_VIP_ADDRESS_IPV4}/32
                      - ${EDGE_VIP_ADDRESS_IPV6}/128
                    serviceAllocation:
                      priority: 100
                      namespaces:
                        - default
                      serviceSelectors:
                        - matchExpressions:
                          - {key: "serviceType", operator: In, values: [kubernetes-vip]}
                  ---
                  apiVersion: metallb.io/v1beta1
                  kind: L2Advertisement
                  metadata:
                    name: ip-pool-l2-adv
                    namespace: metallb-system
                  spec:
                    ipAddressPools:
                      - kubernetes-vip-ip-pool
            - path: /var/lib/rancher/rke2/server/manifests/endpoint-svc.yaml
              overwrite: true
              contents:
                inline: |
                  apiVersion: v1
                  kind: Service
                  metadata:
                    name: kubernetes-vip
                    namespace: default
                    labels:
                      serviceType: kubernetes-vip
                  spec:
                    ipFamilyPolicy: PreferDualStack
                    ports:
                    - name: rke2-api
                      port: 9345
                      protocol: TCP
                      targetPort: 9345
                    - name: k8s-api
                      port: 6443
                      protocol: TCP
                      targetPort: 6443
                    type: LoadBalancer
            ###############################################################################
            # In case of deploying the RKE2 ingress controller, the following 2 files must
            # be added.
            # NOTE: The .providers stanza can be removed from the Helm parameters settings
            #       here below in case of not deploying/not planning to deploy Ingress
            #       objects including ingress-nginx annotations in its `.metadata` stanza.
            ###############################################################################
            - path: /var/lib/rancher/rke2/server/manifests/rke2-traefik-config.yaml
              overwrite: true
              contents:
                inline: |
                  apiVersion: helm.cattle.io/v1
                  kind: HelmChartConfig
                  metadata:
                    name: rke2-traefik
                    namespace: kube-system
                  spec:
                    valuesContent: |-
                      ingressClass:
                        isDefaultClass: false
                      ports:
                        web:
                          hostPort: null    # disallow hostPort
                          exposedPort: 80
                        websecure:
                          hostPort: null    # disallow hostPort
                          exposedPort: 443
                      service:
                        enabled: true
                        spec:
                          type: LoadBalancer
                          ipFamilyPolicy: PreferDualStack
                          externalTrafficPolicy: Local
                          allocateLoadBalancerNodePorts: false
                      providers:
                        kubernetesIngressNGINX:  # this provider allows Traefik to "understand" most of the Ingress-NGINX annotations
                          enabled: true
                          ingressClass: "rke2-ingress-nginx-migration"
                          controllerClass: "rke2.​cattle.​io/ingress-nginx-migration"
              mode: 0644
              user:
                name: root
              group:
                name: root
            - path: /var/lib/rancher/rke2/server/manifests/metallb-traefik-cr.yaml
              overwrite: true
              contents:
                inline: |
                  apiVersion: metallb.io/v1beta1
                  kind: IPAddressPool
                  metadata:
                    name: ingress-ippool
                    namespace: metallb-system
                  spec:
                    addresses:
                      - ${INGRESS_IPV4}/32
                      - ${INGRESS_IPV6}/128
                    serviceAllocation:
                      priority: 100
                      serviceSelectors:
                      - matchExpressions:
                        - {key: app.kubernetes.io/name, operator: In, values: [rke2-traefik]} # assign to rke2-traefik LB svc
                  ---
                  apiVersion: metallb.io/v1beta1
                  kind: L2Advertisement
                  metadata:
                    name: ingress-l2-adv
                    namespace: metallb-system
                  spec:
                    ipAddressPools:
                      - ingress-ippool
              mode: 0644
              user:
                name: root
              group:
                name: root
    kubelet:
      extraArgs:
      - provider-id=metal3://PROVIDERID
    nodeName: "localhost.localdomain"
apiVersion: infrastructure.cluster.x-k8s.io/v1beta2
kind: Metal3MachineTemplate
metadata:
  name: multinode-cluster-controlplane
  namespace: default
spec:
  nodeReuse: True
  template:
    spec:
      automatedCleaningMode: metadata
      dataTemplate:
        name: multinode-cluster-controlplane-template
      hostSelector:
        matchLabels:
          cluster-role: control-plane
      image:
        checksum: http://imagecache.local:8080/${NEW_IMAGE_GENERATED}.sha256
        checksumType: sha256
        diskFormat: raw
        url: http://imagecache.local:8080/${NEW_IMAGE_GENERATED}.raw

在应用 capi-provisioning-example.yaml 文件之前,最好先通知外部负载均衡器(例如 MetalLB)有关节点正在被排空的信息,以便它们不会将流量路由到处于此状态的节点。正如 第 60.1 节 “负载均衡器排除” 部分所提到的,您可以通过在管理集群上标注 RKE2ControlPlane 来实现自动化。在此示例中,名为 multinode-cluster 的 RKE2ControlPlane 对象被注释:

kubectl annotate  RKE2ControlPlane/multinode-cluster  rke2.controlplane.cluster.x-k8s.io/load-balancer-exclusion="true"

验证机器对象是否已被注释:

pre-drain.delete.hook.machine.cluster.x-k8s.io/rke2-lb-exclusion: ""

获取所有机器对象的注释:

kubectl get machines -o json | jq -r '.items[].metadata | .name, .annotations'
注意
注意

如果没有这些注释,用户可能会遇到服务响应时间变长的情况,因为负载均衡器无法感知已排空的节点。

进行这些更改后,可以使用以下命令将 capi-provisioning-example.yaml 文件应用到集群:

kubectl apply -f capi-provisioning-example.yaml

60.4 Longhorn Engine 镜像清理

本节介绍了如何实现自动清理集群升级后积累的过时的 Longhorn Engine 镜像。

60.4.1 问题描述

Longhorn 不会在升级后自动删除旧的 Longhorn Engine 镜像。这是一个刻意的设计决策,旨在防止意外的数据丢失——Longhorn 保留了之前的 Longhorn Engine 镜像版本,作为需要回滚时的安全措施。

然而,在频繁升级的生产环境中,这些未使用的引擎镜像会随时间推移而积累,导致:

  • 存储消耗:每个引擎镜像都会占用集群节点上的磁盘空间

  • 资源开销:Kubernetes 不必要地管理了额外的镜像资源

  • 运维复杂性:需要手动识别并清理过时的镜像

60.4.2 何时实施清理

建议在以下场景中进行引擎镜像清理:

  • 定期集群升级:频繁进行 Longhorn 版本升级的环境

  • 存储受限的部署:节点存储容量有限的集群

  • 运维规范:作为日常运维流程的一部分

注意
注意

此清理过程被归类为 低风险,因为它仅删除零活动引用的引擎镜像 (refCount == 0)。当前卷正在使用的镜像受到保护,不会被删除。

60.4.3 解决方案架构

清理解决方案使用执行定期维护任务的 Kubernetes CronJob:

  1. 发现:识别集群中部署的最新 Longhorn Engine 镜像版本

  2. 候选选择:定位所有没有活动卷引用的旧的 Longhorn Engine 镜像版本

  3. 清理执行:删除符合安全标准的过时的 Longhorn Engine 镜像

  4. Audit 日志记录:记录清理操作以实现运维可见性

该实现需要:

  • ServiceAccount:用于向 Kubernetes API 进行身份验证

  • RBAC 权限:对 engineimages.longhorn.io 资源的读取和删除访问权限

  • CronJob:带有清理逻辑的定时执行容器

60.4.4 实现

完整的清理解决方案由部署到 longhorn-system 名称空间的四个 Kubernetes 清单组成。

  1. 创建 ServiceAccount

    为清理 CronJob 创建一个专用的 ServiceAccount:

    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: longhorn-engineimage-cleanup
      namespace: longhorn-system
      labels:
        app: longhorn-engineimage-cleanup
  2. 配置 RBAC 权限

    1. 定义一个具有所需最小权限的 Role:

      apiVersion: rbac.authorization.k8s.io/v1
      kind: Role
      metadata:
        name: longhorn-engineimage-cleanup
        namespace: longhorn-system
        labels:
          app: longhorn-engineimage-cleanup
      rules:
      - apiGroups: [longhorn.io]
        resources: [engineimages]
        verbs: [get, list, delete]
    2. 将 Role 绑定到 ServiceAccount:

      apiVersion: rbac.authorization.k8s.io/v1
      kind: RoleBinding
      metadata:
        name: longhorn-engineimage-cleanup
        namespace: longhorn-system
        labels:
          app: longhorn-engineimage-cleanup
      subjects:
      - kind: ServiceAccount
        name: longhorn-engineimage-cleanup
        namespace: longhorn-system
      roleRef:
        kind: Role
        name: longhorn-engineimage-cleanup
        apiGroup: rbac.authorization.k8s.io
  3. 部署清理 CronJob

    部署自动清理 CronJob:

    apiVersion: batch/v1
    kind: CronJob
    metadata:
      name: longhorn-engineimage-cleanup
      namespace: longhorn-system
      labels:
        app: longhorn-engineimage-cleanup
      annotations:
        description: "Cleanup old Longhorn engine images left over after upgrades"
        source: "SUSE Edge Telco Cloud - Day 2 Operations"
    spec:
      # Execute weekly on Sunday at 2:00 AM UTC
      schedule: "0 2 * * 0"
      successfulJobsHistoryLimit: 3
      failedJobsHistoryLimit: 3
      concurrencyPolicy: Forbid
      jobTemplate:
        spec:
          backoffLimit: 2
          activeDeadlineSeconds: 600
          template:
            metadata:
              labels:
                app: longhorn-engineimage-cleanup
            spec:
              serviceAccountName: longhorn-engineimage-cleanup
              restartPolicy: Never
              automountServiceAccountToken: true
              containers:
              - name: cleanup
                image: registry.suse.com/bci/bci-base:15.6
                command:
                - sh
                - -c
                - |
                  set -e
    
                  # Install required tools
                  zypper --non-interactive install curl jq
    
                  # Install kubectl from official Kubernetes
                  curl -LO "https://dl.k8s.io/release/v1.36.3/bin/linux/amd64/kubectl"
                  chmod +x kubectl
                  mv kubectl /usr/local/bin/
    
                  echo "=== Longhorn Engine Image Cleanup ==="
                  echo "Automated maintenance - Day 2 Operations"
                  echo ""
    
                  echo "Step 1: Discovering latest engine image version..."
    
                  ENGINE_IMAGES=$(kubectl get engineimages.longhorn.io -n longhorn-system -o json)
    
                  LATEST_VERSION=$(echo "$ENGINE_IMAGES" | jq -r '
                    .items[]
                    | select(.status.version != null)
                    | .status.version' |
                    sed 's/^v//' |
                    sort -V |
                    tail -1)
    
                  if [ -z "$LATEST_VERSION" ]; then
                    echo "No engine images found. Nothing to cleanup."
                    exit 0
                  fi
    
                  echo "Latest version detected: v${LATEST_VERSION}"
                  echo ""
    
                  echo "Step 2: Identifying obsolete engine images..."
    
                  TO_DELETE=$(echo "$ENGINE_IMAGES" | jq -r --arg latest "v${LATEST_VERSION}" '
                    .items[]
                    | select(.status.version != $latest)
                    | select(.status.refCount == 0)
                    | .metadata.name')
    
                  if [ -z "$TO_DELETE" ]; then
                    echo "No obsolete engine images found"
                    echo "All engine images are either:"
                    echo "  - Latest version (v${LATEST_VERSION})"
                    echo "  - Still in use (refCount > 0)"
                    exit 0
                  fi
    
                  DELETE_COUNT=$(echo "$TO_DELETE" | wc -l | tr -d ' ')
                  echo "Found $DELETE_COUNT obsolete engine image(s) for deletion:"
                  echo "$TO_DELETE"
                  echo ""
    
                  COUNT=0
                  FAILED=0
                  for IMAGE_NAME in $TO_DELETE; do
                    VERSION=$(echo "$ENGINE_IMAGES" | jq -r --arg name "$IMAGE_NAME" \
                      '.items[] | select(.metadata.name == $name) | .status.version')
                    REFCOUNT=$(echo "$ENGINE_IMAGES" | jq -r --arg name "$IMAGE_NAME" \
                      '.items[] | select(.metadata.name == $name) | .status.refCount')
    
                    echo "Deleting: $IMAGE_NAME (version: $VERSION, refCount: $REFCOUNT)"
    
                    if kubectl delete engineimages.longhorn.io "$IMAGE_NAME" -n longhorn-system; then
                      COUNT=$((COUNT + 1))
                      echo "  ✓ Deleted successfully"
                    else
                      FAILED=$((FAILED + 1))
                      echo "  ✗ Failed to delete"
                    fi
                  done
    
                  echo ""
                  echo "=== Cleanup Summary ==="
                  echo "Latest version: v${LATEST_VERSION}"
                  echo "Successfully deleted: $COUNT obsolete engine images"
                  echo "Failed deletions: $FAILED engine images"
    
                  if [ $FAILED -gt 0 ]; then
                    exit 1
                  fi
    
                  echo ""
                  echo "Cleanup completed successfully"
  4. 应用配置

    1. 将所有四个清单保存到单个文件(例如 longhorn-engine-cleanup.yaml)中,然后将其部署到集群:

      kubectl apply -f longhorn-engine-cleanup.yaml
    2. 验证 CronJob 是否已创建:

      kubectl get cronjob -n longhorn-system longhorn-engineimage-cleanup

      预期输出:

      NAME                            SCHEDULE     SUSPEND   ACTIVE   LAST SCHEDULE   AGE
      longhorn-engineimage-cleanup    0 2 * * 0    False     0        <none>          5s

60.4.5 操作流程

本节介绍了管理 Longhorn Engine 镜像清理 CronJob 的日常操作任务,包括手动执行、计划自定义、监控和验证。

60.4.5.1 手动执行

若要立即执行清理而不等待预定时间,请运行以下命令:

kubectl create job --from=cronjob/longhorn-engineimage-cleanup \
  longhorn-engineimage-cleanup-manual-$(date +%Y%m%d-%H%M%S) \
  -n longhorn-system

监控手动执行:

  1. 查看作业状态:

    kubectl get jobs -n longhorn-system -w
  2. 查看作业日志:

    kubectl logs -n longhorn-system -l app=longhorn-engineimage-cleanup --tail=100

60.4.5.2 调度配置

默认计划于每周日协调世界时 (UTC) 上午 2:00 执行清理:

schedule: "0 2 * * 0"

常见的计划替代方案:

  • 每天凌晨 3:00:"0 3 * * *"

  • 每月 1 日凌晨 4:00:"0 4 1 * *"

  • 每 6 小时:"0 */6 * * *"

修改调度:

kubectl patch cronjob longhorn-engineimage-cleanup \
  -n longhorn-system \
  --type='merge' \
  -p '{"spec":{"schedule":"0 3 * * *"}}'

60.4.5.3 监控和日志

查看清理历史记录:

  • 列出已完成的作业:

    kubectl get jobs -n longhorn-system -l app=longhorn-engineimage-cleanup
  • 查看上次成功运行的日志:

    kubectl logs -n longhorn-system \
      -l app=longhorn-engineimage-cleanup \
      --tail=200 \
      | grep -A 50 "Cleanup Summary"

检查特定作业执行情况:

# Get job name
JOB_NAME=$(kubectl get jobs -n longhorn-system \
  -l app=longhorn-engineimage-cleanup \
  --sort-by=.metadata.creationTimestamp \
  -o jsonpath='{.items[-1].metadata.name}')

# View job details
kubectl describe job $JOB_NAME -n longhorn-system

# View job logs
kubectl logs job/$JOB_NAME -n longhorn-system

60.4.5.4 校验

清理执行后,请验证是否仅保留了当前的引擎镜像:

  1. 列出所有 Longhorn Engine 镜像:

    kubectl get engineimages.longhorn.io -n longhorn-system
  2. 检查引用计数:

    kubectl get engineimages.longhorn.io -n longhorn-system \
      -o custom-columns=NAME:.metadata.name,VERSION:.status.version,REFCOUNT:.status.refCount

预期输出仅显示带有活动引用的最新版本:

NAME                                VERSION     REFCOUNT
ei-acba2c08                         v1.12.1     3