Kubernetes 备份恢复可观测与分层排障
凌晨恢复任务显示 PartiallyFailed,大多数 Deployment 也已经 Ready。值班人员准备切流量时,支付服务却一直卡在初始化:三个 PVC 中只有两个恢复完成,第三个 PodVolumeRestore 因节点临时空间不足失败;同时,一个 admission webhook 拒绝了旧版本自定义资源。恢复主对象已经进入终态,但数据面和业务面并没有完成。
这类事故最危险的地方不是缺日志,而是证据被分散在不同层。Restore.status.phase 只描述编排器看到的结果;插件进程、DataDownload、PodVolumeRestore、CSI 快照、对象存储、KMS、调度器和应用各自保存一段事实。排障如果只盯着主对象或 controller 日志,很容易把部分成功误判为可接管。
先画出一次操作真正经过的六层
一次 Velero 备份或恢复至少经过下面六层。其他平台的对象名称不同,但诊断顺序仍然成立。
请求与编排层:Schedule、Backup、Restore 保存筛选条件、阶段、warning、error 和开始结束时间。controller 与插件层:Velero server 负责调和,provider plugin 调用对象存储和块存储 API。插件完成后退出时出现 EOF 并不自动代表失败,必须结合操作状态和相邻错误判断。
数据移动层:FSB 使用 PodVolumeBackup、PodVolumeRestore 与 node-agent;CSI snapshot data movement 使用 DataUpload、DataDownload 和数据移动 Pod。存储与仓库层:VolumeSnapshot、云快照、BackupRepository、Kopia 对象、对象存储版本和 KMS 决定字节是否存在、可读、可解密。
Kubernetes 调和层:scheduler、CSI controller、admission webhook、Operator 和 kubelet 决定对象能否变成可运行实例。业务证据层:数据水位、读写探针、消息位点、鉴权、DNS 和依赖调用决定业务是否真的恢复。
因此,“备份成功”至少要同时回答:预期对象是否都被选中,所有卷走了哪条数据路径,归档和快照是否可读,恢复是否能在隔离环境重建,以及业务水位是否满足 RPO。Completed 只是调查入口,不是最后结论。
建立一个不会泄露凭证的观测入口
下面的命令以 Velero 1.18 稳定发行线为示例。CLI、server、provider plugin、Kubernetes、CSI driver 和 snapshot controller 必须在实际环境中形成兼容矩阵。操作账号需要读取 Velero CR、Pod、Job、Event、PVC/PV、VolumeSnapshot 和日志;对象存储与云快照清单通过只读审计身份获取。恢复写权限不应常驻在日常监控账号中。
先创建本地证据目录。目录只保存元数据、状态、资源名、镜像 digest 和脱敏日志,不保存 Secret 内容、签名 URL、完整 kubeconfig、数据库记录或客户数据。
export VELERO_NS=velero
export BACKUP_NAME=<backup-name>
export RESTORE_NAME=<restore-name>
export EVIDENCE_DIR="./evidence/${BACKUP_NAME}"
mkdir -p "${EVIDENCE_DIR}"
velero version > "${EVIDENCE_DIR}/version.txt"
kubectl -n "${VELERO_NS}" get deploy,ds,pod -o wide \
> "${EVIDENCE_DIR}/runtime.txt"
kubectl -n "${VELERO_NS}" get backup "${BACKUP_NAME}" -o yaml \
> "${EVIDENCE_DIR}/backup.yaml"
kubectl -n "${VELERO_NS}" get restore "${RESTORE_NAME}" -o yaml \
> "${EVIDENCE_DIR}/restore.yaml"
velero backup describe "${BACKUP_NAME}" --details \
> "${EVIDENCE_DIR}/backup-describe.txt"
velero restore describe "${RESTORE_NAME}" --details \
> "${EVIDENCE_DIR}/restore-describe.txt"预期得到的是一个可关联的快照:CLI/server 版本一致,controller 和 node-agent 镜像可识别,Backup/Restore 的 UID、phase、warnings、errors、开始结束时间都被保存。若命令因 RBAC 返回 Forbidden,先补齐只读 get/list/watch,不要为了省事绑定 cluster-admin;若 YAML 中含工具写入的敏感 annotation,应在进入工单或制品库前脱敏。
让 Prometheus 先证明采集链存在
Velero 默认在 8085 暴露 Prometheus 指标。先直接验证端点,再检查 ServiceMonitor 或 PodMonitor,避免在 Grafana 空面板上猜 controller 是否工作。
VELERO_POD=$(kubectl -n "${VELERO_NS}" get pod \
-l deploy=velero -o jsonpath='{.items[0].metadata.name}')
kubectl -n "${VELERO_NS}" port-forward "pod/${VELERO_POD}" 8085:8085
# 另一个终端执行
curl --fail --silent http://127.0.0.1:8085/metrics \
| grep '^velero_' \
| head -n 30端口转发应返回 velero_ 前缀指标;连接拒绝时,依次检查 Pod 的 metrics 容器端口、server 启动参数、NetworkPolicy 和进程监听。端点正常而 Prometheus target 为 down,问题在 Service/selector、ServiceMonitor、TLS 或 Prometheus RBAC,而不是备份 controller。
不要在未确认实际发行线指标名与 label 之前复制旧 Dashboard。部署后先保存 /metrics 中的 metric family,再建立至少四组信号:
每个 Schedule 最近一次成功时间与计划周期的差值;failure、partial failure、validation failure 的增量;备份或恢复在非终态停留的年龄;
node-agent、数据移动 Pod、repository maintenance Job 的失败、重启、排队和资源耗尽。
velero_backup_last_successful_timestamp 可用来判断备份新鲜度,但阈值不能写成固定小时数。告警应比较业务声明的最大恢复点年龄,并叠加跨账号复制延迟和最近一次恢复演练结果。只有计数器下降或面板变绿,无法证明历史恢复点仍可恢复。
正向实验:从主对象追到业务水位
在隔离 namespace 中创建一份带可识别水位的合成数据。T0 写入 checkpoint=A,触发备份;备份结束后在 T+1 写入 checkpoint=B。恢复到另一个 namespace 后,预期读到 A 而不是 B,这才能把工具状态与 RPO 证据关联起来。
kubectl create namespace backup-observe-lab
kubectl -n backup-observe-lab create configmap recovery-checkpoint \
--from-literal=checkpoint=A
velero backup create observe-good \
--include-namespaces backup-observe-lab \
--wait
kubectl -n backup-observe-lab patch configmap recovery-checkpoint \
--type merge -p '{"data":{"checkpoint":"B"}}'
velero restore create observe-good-restore \
--from-backup observe-good \
--namespace-mappings backup-observe-lab:backup-observe-restore \
--wait接着沿六层保存证据:
velero backup describe observe-good --details
velero backup logs observe-good
velero restore describe observe-good-restore --details
velero restore logs observe-good-restore
kubectl -n velero get podvolumebackups \
-l velero.io/backup-name=observe-good -o wide
kubectl -n velero get podvolumerestores \
-l velero.io/restore-name=observe-good-restore -o wide
kubectl -n velero get datauploads,datadownloads -o wide
kubectl get volumesnapshot,volumesnapshotcontent -A
kubectl -n backup-observe-restore get configmap recovery-checkpoint \
-o jsonpath='{.data.checkpoint}{"\n"}'若实验只包含 ConfigMap,卷相关对象可以为空;这本身是“没有卷数据路径”的证据,不能伪装成卷恢复已验证。最后一条命令应输出 A。生产系统则要把它替换成数据库事务位置、消息位点、对象 generation 或应用 checkpoint,并执行读写、鉴权和外部依赖检查。
反向实验:制造一个可归因的部分失败
最有价值的演练不是随便断网,而是只破坏一层,让证据链出现可预测断点。下面在实验恢复前故意创建同名 ConfigMap,并用默认 existing-resource policy 触发冲突。
kubectl create namespace backup-observe-conflict
kubectl -n backup-observe-conflict create configmap recovery-checkpoint \
--from-literal=checkpoint=CONFLICT
velero restore create observe-conflict \
--from-backup observe-good \
--namespace-mappings backup-observe-lab:backup-observe-conflict \
--wait
velero restore describe observe-conflict --details
velero restore logs observe-conflict
kubectl -n backup-observe-conflict get configmap recovery-checkpoint \
-o jsonpath='{.data.checkpoint}{"\n"}'预期现象是目标对象不会被静默替换,日志或结果中出现 existing resource 的跳过/警告,业务水位仍为 CONFLICT。这证明 Restore 到达终态不等于目标数据来自备份。若团队选择 --existing-resource-policy=update,还要验证哪些字段被更新、哪些状态由 controller 重建,以及旧对象上哪些字段仍然保留。
清理实验时,删除 Restore CR 不会删除恢复出的业务对象:
velero restore delete observe-conflict --confirm
kubectl delete namespace backup-observe-conflict backup-observe-restore
velero backup delete observe-good --confirm
kubectl delete namespace backup-observe-lab删除后还要核对云快照、对象版本和 Kopia repository maintenance。kubectl delete backup 只删 CR,不会请求清理归档和快照,不应作为正常清理命令。
分层诊断:先判定停在哪一层
Backup 或 Restore 长时间停在 New
New 表示对象已进入 API,但 controller 尚未处理。先看 Velero server 是否 Ready、leader/controller 是否启动、CRD 是否完整,再看对象是否被错误的 namespace 或 selector 隔离。
kubectl -n velero get deploy velero
kubectl -n velero logs deploy/velero --since=20m
kubectl api-resources --api-group=velero.io
kubectl -n velero get backup,restore -o wide
kubectl auth can-i get backups.velero.io \
--as=system:serviceaccount:velero:velero -n velero如果 controller 根本没有运行,继续检查对象存储不会产生新证据。反过来,controller 正常而单个对象未推进,要检查 validation error、finalizer、并发队列和对象 generation。
Backup 停在 InProgress
Velero server 或被备份 Pod 在处理中重启,可能留下无法续跑的 InProgress。先确认操作年龄、controller 重启点以及是否存在异步子对象;不要直接修改 status.phase。
kubectl -n velero get backup "${BACKUP_NAME}" \
-o jsonpath='{.status.phase}{" "}{.status.startTimestamp}{"\n"}'
kubectl -n velero get pod -l deploy=velero \
-o custom-columns=NAME:.metadata.name,RESTARTS:.status.containerStatuses[0].restartCount
kubectl -n velero get podvolumebackups,datauploads \
-l velero.io/backup-name="${BACKUP_NAME}" -o yaml确认该操作不可继续后,可删除卡住的 Backup CR,再重新创建一个新名字的备份。处理前保存 debug bundle 和对象存储清单,避免把中断原因一起删掉。
快照一直未就绪
CSI 路径要同时检查 VolumeSnapshot.status.readyToUse、绑定的 VolumeSnapshotContent、snapshot-controller、CSI snapshotter 和 provider 快照。常见故障包括缺少默认 VolumeSnapshotClass、driver 不匹配、Secret 权限错误、区域配额不足和快照 API 版本不兼容。
kubectl get volumesnapshot -A \
-o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,READY:.status.readyToUse,ERROR:.status.error.message
kubectl get volumesnapshotcontent
kubectl get volumesnapshotclass -o yaml
kubectl get events -A --sort-by=.lastTimestamp | tail -n 80readyToUse=true 只证明 provider 声称快照可用,不证明快照已跨区复制、可以在目标账号解密,也不证明数据库一致。还要从只读云身份检查 snapshot ID、区域、KMS key 和复制状态。
FSB 或数据移动器超时
FSB 主链看 PodVolumeBackup / PodVolumeRestore,CSI 数据移动看 DataUpload / DataDownload。它们通常在 node-agent、数据移动 Pod、仓库连接或节点资源处失败。
kubectl -n velero get podvolumebackups,podvolumerestores \
-o custom-columns=KIND:.kind,NAME:.metadata.name,PHASE:.status.phase,MESSAGE:.status.message
kubectl -n velero get datauploads,datadownloads -o yaml
kubectl -n velero get ds node-agent -o wide
kubectl -n velero logs ds/node-agent --all-containers --since=20m
kubectl -n velero get pod -l velero.io/data-movement-pod=true -o wide
kubectl describe node <node-name>Pending 且没有 Pod 运行,先查并发配额和 prepare queue;Pod 被 Evicted,查 ephemeral-storage、repository cache 和节点压力;传输运行但无进度,查对象存储延迟、限流、DNS、MTU 和凭证刷新。提高 timeout 只能容忍更慢的链路,不能修复没有吞吐的链路。
仓库不可达或对象存储签名失败
BackupStorageLocation 的 Available 是 controller 对位置的探测结果,不代表每个备份对象和每个历史 version 都可读。灾难恢复时应使用只读 BSL,并以隔离身份实际列举和读取归档。
velero backup-location get
kubectl -n velero get backupstoragelocation -o yaml
velero repo get
kubectl -n velero get backuprepository -o yaml
kubectl -n velero get job -l velero.io/repo-nameSignatureDoesNotMatch 要检查 endpoint、region、path-style、时钟、签名版本和临时凭证是否过期;AccessDenied 则把 list、get、put、delete、KMS decrypt 分开验证。不要把云密钥打印到日志,也不要用 URL 查询参数把签名 URL 放进工单。
Restore 完成但 Pod 卡在 init 或 Pending
恢复主流程会等待 FSB init container;调度失败、PVC 未绑定、目标 StorageClass 不存在、拓扑不匹配或配额不足都会让应用停在主对象之后。
kubectl -n <restore-namespace> get pod,pvc -o wide
kubectl -n <restore-namespace> describe pod <pod-name>
kubectl -n <restore-namespace> get event --sort-by=.lastTimestamp
kubectl get storageclass,csidriver,csinode
kubectl -n velero get podvolumerestores,datadownloads -o wide如果对象创建被 admission webhook 拒绝,保存 webhook 名称、响应 message、目标 GVK 与被拒字段。临时关闭 webhook 会改变安全边界,必须有审批和恢复动作;更稳妥的路径通常是先安装兼容 Operator/CRD,或使用 restore resource modifier 修正已知字段。
Debug bundle 是证据包,不是无条件上传包
Velero debug 可以收集版本、server/plugin 日志、受管资源以及指定 Backup/Restore 日志:
(
cd "${EVIDENCE_DIR}"
velero debug \
--backup "${BACKUP_NAME}" \
--restore "${RESTORE_NAME}"
ls -lh bundle-*.tar.gz
)生成后先在隔离环境检查内容,再决定是否进入工单系统。日志可能包含 bucket、对象 key、namespace、镜像仓库、账号 ID、内部域名和业务对象名;CR 导出可能暴露 Secret 引用和身份绑定。证据库应加密、限权、设置独立保留期,并记录谁下载过。需要厂商支持时,提交最小必要片段,而不是未经审查的全集群包。
临时打开 --log-level debug 会增加敏感元数据和日志量,也可能放大 server IO。记录旧参数,限定时间窗口,问题复现后立即恢复并确认 rollout:
kubectl -n velero get deploy velero -o yaml \
> "${EVIDENCE_DIR}/velero-deploy-before.yaml"
kubectl -n velero edit deploy velero
kubectl -n velero rollout status deploy/velero
# 复现并收集后,恢复版本库中的 Deployment/Helm values从告警走到可执行判断
一条可用告警应带出 Schedule、最近成功恢复点、失败对象、数据路径和责任人,而不是只说“Velero 异常”。推荐把信号分为四级:
新鲜度破坏:最近可用恢复点年龄超过业务预算,或异地复制持续落后;先阻止进一步扩大 RPO。完整性破坏:出现 PartiallyFailed、skipped volume、失败子对象或仓库维护失败;备份不可进入可恢复清单。可读取性破坏:BSL 不可用、KMS 拒绝、对象 version 丢失、快照不可见;启动隔离读取验证。
可恢复性破坏:恢复演练超时、业务水位错误、依赖不可用;升级为灾备能力缺口,而不是关闭工具告警。
容量看板也必须覆盖 controller CPU/内存、node-agent 并发与队列、数据移动临时空间、对象存储请求/限流、快照配额、repository maintenance 时长和失败次数。备份量增长而维护长期失败时,删除 CR 不会释放相同比例的仓库空间,成本和恢复时延会同时恶化。
团队排障合同
平台团队维护 controller、CRD、插件、监控和运行身份;存储团队维护 CSI、快照、仓库、KMS 与配额;应用团队提供一致性 hook、业务水位和恢复验证;安全团队审查凭证、证据包和不可变策略。任何一方都不能用自己的绿色状态替代端到端结论。
一次故障关闭前至少留下这些可重算事实:
操作 UID、版本矩阵、配置摘要和故障发生的相对时间线;主对象与全部预期子对象的终态,且没有未解释 warning、skipped、failed 或 timeout;对象存储 version、云快照、KMS 和异地副本的只读清单;
恢复后的对象、卷、业务水位、读写、鉴权和外部依赖证据;根因所在层、修复动作、反向实验、告警改动和负责人;临时 RBAC、凭证、调试日志、测试卷、快照和公网入口的回收结果。
排障的终点不是找到一行报错,而是让每一层的状态能够解释下一层的现象。只有恢复点可读、子任务完整、Kubernetes 调和成功并且业务水位通过,那个绿色状态才值得相信。
延伸阅读
Velero 1.18 Troubleshooting。Velero 1.18 Restore Reference。Velero 1.18 File System Backup
Velero 1.18 CSI Snapshot Data Movement。Kubernetes Volume Snapshots
