|Index|SUSE Telco Cloud ドキュメント|Day 2運用|ライフサイクルアクション
適用項目 SUSE Telco Cloud 3.7

60 ライフサイクルアクション

このセクションでは、SUSE Telco Cloud経由でデプロイされたクラスターのライフサイクル管理について説明します。

60.1 ロードバランサーの除外

ノードのドレインを必要とするライフサイクルアクションは多数あります。ドレインプロセス中、すべてのポッドはクラスター内の他のノードに移動されます。ドレインプロセスが完了すると、ノードはサービスをホストしなくなるため、トラフィックをルーティングすべきではありません。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章 「ダイレクトネットワークプロビジョニング(シングルノード)によるダウンストリームクラスターのプロビジョニング」)に示す`RKE2ControlPlane`のブロック`capi-provisioning-example.yaml`を変更します。

    • 目的の`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は、ロールバックが必要になった場合に備えて、安全対策として以前のエンジンイメージバージョンを保持します。

しかし、頻繁にアップグレードが行われる本番環境では、これらの未使用のエンジンイメージが時間の経過とともに蓄積され、以下の問題が発生します。

  • ストレージ消費:各エンジンイメージがクラスターノード上のディスク容量を消費する

  • リソースオーバーヘッド:Kubernetesが不要なイメージリソースを管理してしまう

  • 運用上の複雑さ:不要になったイメージの手動特定とクリーンアップが必要になる

60.4.2 クリーンアップを実施すべきタイミング

エンジンイメージのクリーンアップは、以下のシナリオで推奨されます。

  • 定期的なクラスターアップグレード:Longhornのバージョンアップグレードを頻繁に行う環境

  • ストレージ容量が限られた環境:ノードのストレージ容量が限られているクラスター

  • 運用上の衛生管理:日常的なDay 2メンテナンス手順の一環として

注記
注記

このクリーンアップ手順は、アクティブな参照がゼロのエンジンイメージのみを削除するため、低リスクに分類されます (refCount == 0)。現在ボリュームで使用されているイメージは保護されており、削除されません。

60.4.3 ソリューションアーキテクチャ

クリーンアップソリューションは、定期的なメンテナンス作業を実行するKubernetes CronJobを使用します:

  1. 検出:クラスターにデプロイされている最新のLonghorn Engineイメージバージョンを特定します

  2. 候補の選択:アクティブなボリューム参照がない古いエンジンイメージバージョンをすべて特定します

  3. クリーンアップの実行:安全基準を満たす不要なエンジンイメージを削除します

  4. 監査ログ:運用の可視化のためにクリーンアップ操作を記録します

実装には以下が必要です:

  • ServiceAccount:Kubernetes APIへの認証用

  • RBAC権限:`engineimages.longhorn.io`リソースへの読み取りおよび削除アクセス権

  • CronJob:クリーンアップロジックを含むスケジュール実行コンテナ

60.4.4 実装

完全なクリーンアップソリューションは、`longhorn-system`ネームスペースにデプロイされる4つの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. 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エンジンイメージクリーンアップ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 スケジュール設定

デフォルトのスケジュールでは、毎週日曜日の午前2:00(UTC)にクリーンアップが実行されます。

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. すべてのエンジンイメージを一覧表示します:

    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