系统学习 演练学习路径:35 个 lab 的难度排序、4 周基础计划与复盘方法

版本信息最后核对:2026-10-25

Lab 状态:静态审查2026-10-25 · kind + MinIO

在 kind 一次性集群中用 MinIO 演练 Velero 命名空间备份、误删恢复与 PVC 数据核对

演练目标

安全边界:本 Lab 只使用独立的 kind 集群、独立 kubeconfig、一次性 MinIO 和 /tmp/lab-velero 目录,不连接任何真实集群或生产对象存储。不要在生产上下文执行 kubectl delete namespace、velero restore --existing-resource-policy=update 或删除 PV。真实恢复还要遵循变更审批、数据脱敏和备份保留策略。

环境准备

准备 Docker、kind、kubectl 和 Velero CLI。为避免误连其他集群,所有命令都显式使用 /tmp/lab-velero/kubeconfig。镜像标签不写死:请用你已验证过、且与本地工具链兼容的 MinIO、mc、Velero 插件和应用镜像。本仓库其他 Lab 已使用过的标签可以作为起点,但升级前仍要做兼容性检查。

timeout 10 mkdir -p /tmp/lab-velero/pv /tmp/lab-velero/evidence
export KUBECONFIG=/tmp/lab-velero/kubeconfig
export MINIO_IMAGE="${MINIO_IMAGE:?请设置已验证的 MinIO 镜像}"
export MC_IMAGE="${MC_IMAGE:?请设置已验证的 mc 镜像}"
export VELERO_PLUGIN_IMAGE="${VELERO_PLUGIN_IMAGE:?请设置与 Velero 兼容的 AWS 插件镜像}"
export APP_IMAGE="${APP_IMAGE:?请设置带 shell 和 sha256sum 的演示镜像}"
timeout 30 tee /tmp/lab-velero/kind.yaml >/dev/null <<'EOF'
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  extraMounts:
  - hostPath: /tmp/lab-velero/pv
    containerPath: /var/local-lab-pv
EOF
timeout 300 kind create cluster --name lab-velero --config /tmp/lab-velero/kind.yaml --kubeconfig "$KUBECONFIG" --wait 120s
timeout 30 kubectl config current-context
timeout 30 kubectl get nodes

在集群内部署一个临时 MinIO,数据和 Velero 备份都只活在这个一次性集群里。MinIO 使用 emptyDir,集群删除后数据即消失,这是演练隔离的一部分,不要把它当成持久备份:

timeout 30 kubectl create namespace lab-storage
timeout 30 kubectl create namespace lab-app
timeout 30 tee /tmp/lab-velero/minio.yaml >/dev/null <<EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: minio
  namespace: lab-storage
spec:
  selector:
    matchLabels:
      app: minio
  template:
    metadata:
      labels:
        app: minio
    spec:
      containers:
      - name: minio
        image: ${MINIO_IMAGE}
        args: ["server", "/data", "--console-address", ":9001"]
        env:
        - name: MINIO_ROOT_USER
          value: labadmin
        - name: MINIO_ROOT_PASSWORD
          value: labpass123
        ports:
        - containerPort: 9000
        volumeMounts:
        - name: data
          mountPath: /data
      volumes:
      - name: data
        emptyDir: {}
---
apiVersion: v1
kind: Service
metadata:
  name: minio
  namespace: lab-storage
spec:
  selector:
    app: minio
  ports:
  - port: 9000
    targetPort: 9000
EOF
timeout 120 kubectl apply -f /tmp/lab-velero/minio.yaml
timeout 180 kubectl -n lab-storage rollout status deployment/minio --timeout=150s
timeout 120 kubectl -n lab-storage run mc --rm -i --restart=Never --image="$MC_IMAGE" --command -- sh -c 'mc --config-dir /tmp/mc alias set lab http://minio:9000 labadmin labpass123 && mc --config-dir /tmp/mc mb --ignore-existing lab/lab-velero'

安装 Velero 并等待 node-agent 就绪。BackupStorageLocation 可用只说明 Velero 能访问对象存储,不代表备份里一定包含卷数据:

timeout 30 tee /tmp/lab-velero/credentials-velero >/dev/null <<'EOF'
[default]
aws_access_key_id=labadmin
aws_secret_access_key=labpass123
EOF
timeout 300 velero install --kubeconfig "$KUBECONFIG" --provider aws \
  --plugins "$VELERO_PLUGIN_IMAGE" --bucket lab-velero \
  --secret-file /tmp/lab-velero/credentials-velero \
  --backup-location-config region=minio,s3ForcePathStyle=true,s3Url=http://minio.lab-storage.svc:9000 \
  --use-node-agent
timeout 180 kubectl -n velero rollout status deployment/velero --timeout=150s
timeout 120 kubectl -n velero rollout status daemonset/node-agent --timeout=120s
timeout 30 velero backup-location get

创建带持久卷的应用:先建 hostPath PV 和 PVC,再用 Deployment 写入一个带时间戳的 marker 文件。备份前保存资源清单和文件哈希,作为恢复后的对照基线:

timeout 30 tee /tmp/lab-velero/app.yaml >/dev/null <<EOF
apiVersion: v1
kind: PersistentVolume
metadata:
  name: lab-velero-pv
spec:
  capacity:
    storage: 1Gi
  accessModes: ["ReadWriteOnce"]
  persistentVolumeReclaimPolicy: Retain
  storageClassName: lab-local
  hostPath:
    path: /var/local-lab-pv
    type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: lab-data
  namespace: lab-app
spec:
  accessModes: ["ReadWriteOnce"]
  storageClassName: lab-local
  resources:
    requests:
      storage: 1Gi
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: lab-config
  namespace: lab-app
data:
  APP_MODE: lab-backup
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: lab-app
  namespace: lab-app
spec:
  replicas: 1
  selector:
    matchLabels:
      app: lab-app
  template:
    metadata:
      labels:
        app: lab-app
    spec:
      containers:
      - name: app
        image: ${APP_IMAGE}
        command: ["sh", "-c", "sleep 3600"]
        volumeMounts:
        - name: data
          mountPath: /data
      volumes:
      - name: data
        persistentVolumeClaim:
          claimName: lab-data
---
apiVersion: v1
kind: Service
metadata:
  name: lab-app
  namespace: lab-app
spec:
  selector:
    app: lab-app
  ports:
  - port: 80
    targetPort: 80
EOF
timeout 120 kubectl apply -f /tmp/lab-velero/app.yaml
timeout 180 kubectl -n lab-app rollout status deployment/lab-app --timeout=150s
timeout 60 bash -c 'until [ "$(kubectl -n lab-app get pvc lab-data -o jsonpath="{.status.phase}")" = "Bound" ]; do sleep 2; done'
timeout 30 kubectl -n lab-app exec deploy/lab-app -- sh -c 'echo "backup-marker-$(date -u +%Y%m%dT%H%M%SZ)" > /data/marker.txt; sha256sum /data/marker.txt > /data/marker.sha256'
timeout 30 kubectl -n lab-app get all,cm,pvc -o wide | timeout 30 tee /tmp/lab-velero/evidence/resources-before.txt
timeout 30 kubectl -n lab-app exec deploy/lab-app -- sha256sum /data/marker.txt | timeout 30 tee /tmp/lab-velero/evidence/pvc-before.txt

演练步骤

1. 创建备份并检查卷数据

创建备份后不要只看 Phase: Completed。还要检查资源数量、PodVolumeBackup 是否真的产生,以及日志里有没有被忽略的卷错误:

BACKUP="lab-app-$(timeout 5 date +%Y%m%d%H%M%S)"
timeout 600 velero backup create "$BACKUP" --include-namespaces lab-app \
  --default-volumes-to-fs-backup --wait
timeout 30 velero backup describe "$BACKUP" --details | timeout 30 tee /tmp/lab-velero/evidence/backup-describe.txt
timeout 30 velero backup logs "$BACKUP" > /tmp/lab-velero/evidence/backup.log
timeout 30 kubectl -n velero get podvolumebackups -l velero.io/backup-name="$BACKUP" -o wide
timeout 180 kubectl -n velero wait --for=jsonpath='{.status.phase}'=Completed podvolumebackup -l velero.io/backup-name="$BACKUP" --timeout=150s
timeout 30 velero backup get

预期备份阶段为 Completed,错误数为 0,资源清单包含 Deployment、ConfigMap、Service、PVC,并且至少有一个 PodVolumeBackup。若阶段是 PartiallyFailed,先判断缺的是可重建的 ServiceAccount 还是不可替代的 PV 数据,不要直接进入恢复。

2. 删除命名空间并清除本地卷数据

为了证明数据确实来自对象存储,而不是 hostPath 上残留的文件,删除命名空间后还要删除 PV 对象并清空宿主机目录。真实事故中不要主动删除证据和回滚材料,这里只是因为备份已经完成并校验过:

timeout 180 kubectl delete namespace lab-app --wait=true
timeout 60 kubectl delete pv lab-velero-pv --ignore-not-found
timeout 30 find /tmp/lab-velero/pv -mindepth 1 -delete
timeout 30 kubectl get namespace lab-app 2>&1 || true
timeout 30 kubectl get pv lab-velero-pv 2>&1 || true
timeout 30 velero backup get

此时原命名空间和本地文件都不存在,唯一的数据副本是 MinIO 中的 Velero 备份。记录误删时间,后面用于计算恢复耗时。

3. 恢复到隔离命名空间

使用 --namespace-mappings 把 lab-app 恢复到 lab-app-restore,默认的 --existing-resource-policy=none 会跳过已存在对象。不要直接在原命名空间做覆盖式恢复,也不要使用 update 绕过冲突:

RESTORE_START=$(timeout 5 date +%s)
RESTORE="lab-app-restore-$(timeout 5 date +%Y%m%d%H%M%S)"
timeout 600 velero restore create "$RESTORE" --from-backup "$BACKUP" \
  --include-namespaces lab-app --namespace-mappings lab-app:lab-app-restore \
  --existing-resource-policy=none --wait
timeout 30 velero restore describe "$RESTORE" --details | timeout 30 tee /tmp/lab-velero/evidence/restore-describe.txt
timeout 30 velero restore logs "$RESTORE" > /tmp/lab-velero/evidence/restore.log
timeout 180 kubectl -n lab-app-restore rollout status deployment/lab-app --timeout=150s
timeout 60 bash -c 'until [ "$(kubectl -n lab-app-restore get pvc lab-data -o jsonpath="{.status.phase}")" = "Bound" ]; do sleep 2; done'
timeout 30 kubectl -n lab-app-restore get pods,pvc,svc,cm -o wide
timeout 30 kubectl -n velero get podvolumerestores -l velero.io/restore-name="$RESTORE" -o wide
timeout 180 kubectl -n velero wait --for=jsonpath='{.status.phase}'=Completed podvolumerestore -l velero.io/restore-name="$RESTORE" --timeout=150s

4. 核对资源与卷内数据

先看恢复阶段和错误,再看副本数、镜像、ConfigMap、Service Endpoints 和 PVC 状态,最后核对 marker 文件的哈希。资源全部 Running 但哈希不一致,仍然判定恢复失败:

timeout 30 kubectl -n lab-app-restore get deployment lab-app -o jsonpath='{.spec.replicas}{" "}{.status.readyReplicas}{"\n"}'
timeout 30 kubectl -n lab-app-restore get endpoints lab-app -o wide
timeout 30 kubectl -n lab-app-restore get configmap lab-config -o jsonpath='{.data.APP_MODE}{"\n"}'
timeout 30 kubectl -n lab-app-restore exec deploy/lab-app -- cat /data/marker.txt
timeout 30 kubectl -n lab-app-restore exec deploy/lab-app -- sha256sum /data/marker.txt | timeout 30 tee /tmp/lab-velero/evidence/pvc-after.txt
timeout 30 diff -u /tmp/lab-velero/evidence/pvc-before.txt /tmp/lab-velero/evidence/pvc-after.txt
RESTORE_VERIFIED=$(timeout 5 date +%s)
timeout 10 tee /tmp/lab-velero/evidence/rto.txt >/dev/null <<< "rto_seconds=$((RESTORE_VERIFIED-RESTORE_START))"

观察现象

备份阶段应为 Completed,Errors 为 0,PodVolumeBackup 存在并完成;恢复阶段同样应为 Completed,恢复出的 Deployment 有 1 个 Ready 副本,PVC 为 Bound,Service 有 Endpoint。pvc-before.txt 与 pvc-after.txt 的 SHA-256 完全一致,说明卷数据确实被还原,而不是新建了空卷。原命名空间仍然不存在,恢复只发生在 lab-app-restore。

成功的判据是:备份和恢复阶段均完成、错误为 0;PodVolumeBackup 与 PodVolumeRestore 都有完成记录;PVC 绑定且哈希一致;ConfigMap、Service、Deployment 的关键字段与基线一致;恢复过程没有引用生产 kubeconfig 或生产对象存储;RTO 已记录。失败判据是:备份没有任何卷备份记录;恢复后 PVC Pending;Pod 虽 Running 但哈希为空或不一致;恢复日志出现 already exists、no matches for kind 或卷恢复超时;为了消除错误直接改用 update 覆盖。

判断与定位

  1. 先区分备份状态和卷备份状态。velero backup describe --details 只说明资源遍历结果,卷数据要看 PodVolumeBackup、node-agent 日志和对象存储中的实际对象。
  2. PVC Pending 时先查 StorageClass、PV 和容量。跨集群恢复常见原因是目标集群没有同名 StorageClass,或 PV 没有被备份/重建;若备份详情中没有 PV,先按 Velero 版本的集群资源备份选项调整,不要用空 PV 冒充恢复成功;不要用删除 PVC 的方式反复重试。
  3. 恢复日志中的 already exists 表示目标端已有同名对象,默认策略会跳过;确认是否需要保留目标对象,再决定受控更新,而不是直接删除后重跑。
  4. 命名空间映射只改变对象的命名空间归属,不会自动改写 ConfigMap、Secret、启动参数中硬编码的旧命名空间、数据库地址或域名。要逐项检查应用配置。
  5. Pod Running 不代表数据恢复完成。文件系统恢复可能晚于 Pod 启动,必须等 PodVolumeRestore 完成并核对哈希;数据库类应用还要做行数、聚合和写入验证。
  6. 不要把 Completed 当成业务一致性证明。恢复不会回放备份之后的写入,RPO 由备份时点决定;要记录丢失窗口并让业务确认。

恢复与回滚

如果恢复进入 PartiallyFailed,先保留 restore describe --details、日志、PodVolumeRestore 状态和恢复出的 PVC,再按资源类型逐项处理。只对失败资源做受控重试,不要在未备份当前状态时重新执行整份 update 恢复。若只是隔离命名空间验证失败,删除该命名空间属于低风险回滚,但删除前要确认没有需要保留的数据。

如果误删发生在真实环境,立即停止所有写入方,保留现场,确认最近一次可用备份和可接受 RPO。Velero 没有撤销恢复的命令;覆盖式恢复失败时,应使用恢复前创建的备份或 GitOps 上一个提交回退,不要手工删除归属不明的对象。若卷中已经写入脏数据,先停应用、隔离存储,再从更早的备份恢复。

实验结束后的清理只针对带 lab-velero 名称的资源。先归档证据,再删除集群,避免误删其他 kind 集群:

timeout 120 kubectl delete namespace lab-app-restore --ignore-not-found --wait=true
timeout 60 kubectl delete pv lab-velero-pv --ignore-not-found
timeout 120 kind delete cluster --name lab-velero
timeout 30 rm -rf /tmp/lab-velero

验证

复盘

相关命令

参考链接

读完自测

  1. Velero 备份阶段为 Completed,为什么仍不能证明 PVC 数据可以恢复?
  2. 恢复到新命名空间后,Pod Running、PVC Bound,为什么还要核对文件哈希?
  3. 恢复进入 PartiallyFailed 时,第一步应该做什么?什么时候才允许重试?
答案
  1. Completed 只表示备份流程完成,卷数据可能没有包含、备份可能被跳过,或 node-agent 只完成了资源遍历。必须检查 --details 中的资源与卷备份数量、PodVolumeBackup 状态和备份日志,并在隔离环境实际恢复。
  2. PVC Bound 只说明卷对象存在,可能是新建的空卷;Pod Running 也只说明容器启动。只有核对备份前记录的哈希、行数或抽样内容,才能证明数据来自备份而不是空卷或本地残留。
  3. 先保留 restore describe --details、日志和卷恢复状态,按 already exists、CRD 缺失、PVC、node-agent、Webhook 等原因逐项定位;只有在当前状态已备份、资源差异已确认且回滚路径明确时,才对失败资源做受控重试。
微信小程序码:扫码打开命令速查小程序

小程序版

微信扫码,随时查命令