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"更改以下 部分 (第 52 章 “使用定向网络配置(单节点)进行下游集群配置”) 中显示的
capi-provisioning-example.yaml中的块Metal3MachineTemplate:将镜像名称和校验和更改为上一步中生成的新版本。
将指令
nodeReuse添加到true以避免创建新节点。将指令
automatedCleaningMode添加到metadata以启用节点的自动清理功能。
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.yaml60.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:
发现:识别集群中部署的最新 Longhorn Engine 镜像版本
候选选择:定位所有没有活动卷引用的旧的 Longhorn Engine 镜像版本
清理执行:删除符合安全标准的过时的 Longhorn Engine 镜像
Audit 日志记录:记录清理操作以实现运维可见性
该实现需要:
ServiceAccount:用于向 Kubernetes API 进行身份验证
RBAC 权限:对
engineimages.longhorn.io资源的读取和删除访问权限CronJob:带有清理逻辑的定时执行容器
60.4.4 实现 #
完整的清理解决方案由部署到 longhorn-system 名称空间的四个 Kubernetes 清单组成。
创建 ServiceAccount
为清理 CronJob 创建一个专用的 ServiceAccount:
apiVersion: v1 kind: ServiceAccount metadata: name: longhorn-engineimage-cleanup namespace: longhorn-system labels: app: longhorn-engineimage-cleanup配置 RBAC 权限
定义一个具有所需最小权限的 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]将 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
部署清理 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"应用配置
将所有四个清单保存到单个文件(例如
longhorn-engine-cleanup.yaml)中,然后将其部署到集群:kubectl apply -f longhorn-engine-cleanup.yaml验证 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监控手动执行:
查看作业状态:
kubectl get jobs -n longhorn-system -w查看作业日志:
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-system60.4.5.4 校验 #
清理执行后,请验证是否仅保留了当前的引擎镜像:
列出所有 Longhorn Engine 镜像:
kubectl get engineimages.longhorn.io -n longhorn-system检查引用计数:
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