版本信息最后核对:2026-10-25
Lab 状态:静态审查
安全边界:本 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
创建备份后不要只看 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 数据,不要直接进入恢复。
为了证明数据确实来自对象存储,而不是 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 备份。记录误删时间,后面用于计算恢复耗时。
使用 --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
先看恢复阶段和错误,再看副本数、镜像、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 覆盖。
velero backup describe --details 只说明资源遍历结果,卷数据要看 PodVolumeBackup、node-agent 日志和对象存储中的实际对象。already exists 表示目标端已有同名对象,默认策略会跳过;确认是否需要保留目标对象,再决定受控更新,而不是直接删除后重跑。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
velero backup get 与 velero restore get 的阶段均为 Completed,错误数为 0;若有 PartiallyFailed,已逐条解释。BackupStorageLocation 可用,备份日志中没有凭据、网络或桶权限错误。Bound,Pod 为 Running/Ready。lab-app-restore,原命名空间没有被重新创建;没有旧、新工作负载同时写同一外部依赖。Completed 时,为什么还要检查 PodVolumeBackup 和实际文件哈希?--existing-resource-policy=update 做第一次恢复?Completed,为什么仍不能证明 PVC 数据可以恢复?PartiallyFailed 时,第一步应该做什么?什么时候才允许重试?Completed 只表示备份流程完成,卷数据可能没有包含、备份可能被跳过,或 node-agent 只完成了资源遍历。必须检查 --details 中的资源与卷备份数量、PodVolumeBackup 状态和备份日志,并在隔离环境实际恢复。restore describe --details、日志和卷恢复状态,按 already exists、CRD 缺失、PVC、node-agent、Webhook 等原因逐项定位;只有在当前状态已备份、资源差异已确认且回滚路径明确时,才对失败资源做受控重试。
小程序版
微信扫码,随时查命令