Kubernetes 备份保留、升级、迁移与安全退出
团队把 Velero 的 Backup CR 清空后,控制台终于干净了,对象存储账单却没有下降。进一步核对才发现:有人用 kubectl delete backup 只删了 Kubernetes 对象,Kopia 仓库中的数据没有进入正常删除链;S3 Versioning 又保留了旧 version 和 delete marker。与此同时,升级计划准备移除旧 controller,却仍有一批只能由旧 restic 路径恢复的历史点。
保留、升级、迁移和卸载看似是四项运维工作,实质上共同修改“哪些恢复点仍然可被哪套执行链读取”。先删旧工具再验证新工具,会把兼容问题变成不可逆的数据问题;只看 CR 数量或 bucket 总容量,也无法判断哪些字节仍受保留、复制、KMS 和仓库元数据约束。
一个恢复点同时活在五种生命周期里
删除一个 Backup 不是删除一个文件。至少有五套生命周期共同决定它是否还存在:
调度与 CR 生命周期:Schedule 创建 Backup,Backup 的 TTL 到期后才有资格被 Velero 垃圾回收。归档生命周期:对象存储保存资源归档、日志、结果和 repository 数据;Versioning、lifecycle、Object Lock/WORM、soft delete 会改变删除结果。卷副本生命周期:CSI/云快照、导出卷和跨区副本由 provider 管理,可能有独立保留与父子关系。
仓库生命周期:删除 Kopia snapshot 后,未引用数据要等 repository full maintenance 才真正回收;仓库元数据还会继续存在。密钥与身份生命周期:KMS key、工作负载身份、仓库密码和跨账号信任失效后,字节仍在却不可恢复。
恢复点账本必须把这五层绑定到同一个业务恢复点 ID。最少字段包括 Backup UID、Schedule、业务水位、对象存储 location/prefix、对象 version、快照 ID、仓库类型、KMS key、异地副本状态、到期规则、legal hold、最近恢复结果和责任人。没有这本账,保留策略只是定时删除命令。
先盘点,再讨论保留多久
下面的示例以 Velero 1.18 为执行基线。CLI/server、provider plugin、Kubernetes 和 CSI 版本应从运行环境读取;对象存储和云快照使用只读审计身份。盘点输出可能包含账号、bucket、prefix、Secret 引用和业务资源名,必须进入加密且限权的证据目录。
export VELERO_NS=velero
export INVENTORY_DIR=./backup-lifecycle-inventory
mkdir -p "${INVENTORY_DIR}"
velero version > "${INVENTORY_DIR}/velero-version.txt"
velero schedule get > "${INVENTORY_DIR}/schedules.txt"
velero backup get > "${INVENTORY_DIR}/backups.txt"
velero backup-location get > "${INVENTORY_DIR}/locations.txt"
velero repo get > "${INVENTORY_DIR}/repositories.txt"
kubectl -n "${VELERO_NS}" get schedule,backup,restore \
-o yaml > "${INVENTORY_DIR}/velero-objects.yaml"
kubectl -n "${VELERO_NS}" get backupstoragelocation,volumesnapshotlocation \
-o yaml > "${INVENTORY_DIR}/locations.yaml"
kubectl -n "${VELERO_NS}" get backuprepository \
-o yaml > "${INVENTORY_DIR}/repositories.yaml"
kubectl get volumesnapshot,volumesnapshotcontent -A \
-o yaml > "${INVENTORY_DIR}/csi-snapshots.yaml"云侧还要分别导出对象 version、delete marker、retention/lock、replication status、provider snapshot、KMS key 状态和费用标签。不要把访问密钥、Secret data 或签名 URL写入盘点文件。若团队无法从只读账号完成这些查询,说明恢复链仍依赖生产管理员临时授权,应先修复身份模型。
把业务保留转成可执行策略
一个可靠策略通常分为快速恢复、常规历史、长期审计和隔离副本,而不是所有恢复点都保留相同天数。每层都要写清:
保护的故障类型和业务水位;创建频率、保留数量或期限及例外审批;对象、卷、外部数据库和密钥各自由谁保存;
是否跨账号/区域复制,复制延迟怎样进入 RPO;到期后由哪个 API 删除,谁检查 provider inventory;最近一次隔离恢复何时成功,失败时是否暂停删除。
spec.template.ttl 只控制该 Schedule 产生的 Velero Backup 何时可被垃圾回收,不能替代对象存储 lifecycle、快照保留、合规锁定或业务档案策略。修改 Schedule 的 TTL 也不会自动重写所有历史 Backup 的既有 TTL。
apiVersion: velero.io/v1
kind: Schedule
metadata:
name: payments-standard
namespace: velero
spec:
schedule: "15 */6 * * *"
template:
ttl: 336h0m0s
includedNamespaces:
- payments
storageLocation: primary
snapshotMoveData: true这里的 336h 只是演示值,不是通用标准。生产值要由业务影响分析、变化速率、复制延迟、法规、恢复演练和成本预算共同决定。计划每六小时触发也不等于六小时 RPO;排队、部分失败、复制落后或恢复不可用都会扩大数据损失窗口。
正向实验:证明正常删除链会经过仓库维护
使用专用 namespace、专用 bucket prefix 和合成数据创建一次可销毁备份。先保存 Backup UID、归档对象和 repository 状态,再通过 Velero 删除命令发起清理。
kubectl create namespace retention-lab
kubectl -n retention-lab create configmap retention-marker \
--from-literal=value=DELETE-ME
velero backup create retention-delete-good \
--include-namespaces retention-lab \
--ttl 24h0m0s \
--wait
velero backup describe retention-delete-good --details
velero repo get
velero backup delete retention-delete-good --confirm
kubectl -n velero get backup retention-delete-good最后一条命令预期返回 NotFound,但这只证明 CR 已删除。对象存储版本、provider snapshot 和 Kopia repository 还要继续检查。对于 FSB/Kopia,删除 backup snapshot 后,未引用数据会等 full maintenance 才释放;仓库元数据即使没有业务备份也可能保留。
kubectl -n velero get job -l velero.io/repo-name -o wide
kubectl -n velero get backuprepository -o yaml
kubectl -n velero logs job/<maintenance-job-name>合格证据是:删除请求有审计记录,目标 Backup 的归档和快照按策略进入删除,仓库维护成功后未引用字节回落,对象 version/lock 的残留都有原因和到期时间。容量不会与 CR 删除瞬间等比例下降,因此不要把“bucket 还不为空”直接判为泄漏,也不要因此手工清空仓库。
清理实验 namespace:
kubectl delete namespace retention-lab反向实验:证明删 CR 会留下外部数据
再创建一份测试备份,但这次只删除 Kubernetes CR:
kubectl create namespace retention-lab-negative
kubectl -n retention-lab-negative create configmap retention-marker \
--from-literal=value=ORPHAN-ME
velero backup create retention-delete-wrong \
--include-namespaces retention-lab-negative \
--wait
kubectl -n velero delete backup retention-delete-wrong
kubectl -n velero get backup retention-delete-wrongCR 同样会消失,但 kubectl delete backup 不会调用 Velero 的归档和块存储删除流程。此时用 provider inventory 查询原 prefix、快照和对象 version,预期仍能看到外部数据。这个实验应写进团队门禁:正常保留清理必须调用 velero backup delete 或受控 API,直接删除 CR 只用于已经明确处理外部残留的修复场景。
完成外部残留登记与受控清理后,再删除实验 namespace:
kubectl delete namespace retention-lab-negative不要在不知道对象引用关系时直接 rm -rf 仓库 prefix。Kopia 数据可能被多个 snapshot 去重共享,手工删除单个 blob 会让仍在保留期内的其他恢复点损坏。修复孤儿时先冻结自动删除、备份 provider inventory、确认仓库所有者,再用仓库支持的 maintenance/验证路径处理。
不可变与版本控制会改变删除语义
Versioning、immutability、replication 和 encryption 是四种不同能力:
Versioning 保存旧 version;普通删除可能只创建 delete marker。Object Lock 或 version-level WORM 保护特定 version,未到期前无法永久删除。Replication 异步复制到另一个故障域,目标 owner、删除传播和 KMS 权限决定副本是否独立。
KMS 决定对象能否解密;不可变对象并不保护密钥不会被禁用或删除。
Velero 在备份完成阶段需要更新对象存储中的元数据。S3 的 version 语义可能允许产生新 version,但旧 version 和 delete marker 会继续计费;Azure container-level WORM 与 Google Cloud bucket-level lock 不能被假设为透明兼容。启用任何锁定前,必须在同一 provider 上完成“写入、finalize、删除、跨账号读取、解密、恢复、到期清理”的全链实验。
合规保留尚未到期时,正确动作是登记 version ID、保留到期、legal hold、owner 和费用,而不是提升权限强删。安全团队持有保留策略修改权,备份运行身份只应写入受控 prefix,不应同时拥有缩短保留、删除 KMS key 和清空异地副本的权限。
升级从恢复能力预检开始
升级不是替换一个 Deployment 镜像。Velero CLI/server、CRD、provider plugin、node-agent、CSI driver、snapshot controller、BackupRepository 格式和历史恢复点都在兼容面上。
在变更窗口前先暂停 Schedule,避免升级过程中产生一半由旧 controller、一半由新 controller 处理的操作:
kubectl -n velero get schedule -o name \
| while read name; do
kubectl -n velero patch "${name}" \
--type merge -p '{"spec":{"paused":true}}'
done
kubectl -n velero get schedule \
-o custom-columns=NAME:.metadata.name,PAUSED:.spec.paused接着保存升级账本:
velero version
kubectl -n velero get deploy,ds -o yaml > pre-upgrade-workloads.yaml
kubectl get crd -o yaml | grep -A2 'velero.io' > pre-upgrade-crd-index.txt
kubectl -n velero get backupstoragelocation,volumesnapshotlocation,backuprepository \
-o yaml > pre-upgrade-storage.yaml
kubectl -n velero get backup -o yaml > pre-upgrade-backups.yaml在真实变更中,CRD 应完整导出到受控目录,上面的 grep 只用于快速查看。还要保存 Helm values、镜像 digest、插件兼容矩阵、仓库密码/KMS 的托管位置、可恢复点清单和最近一次隔离恢复证据。不要导出 Secret data 到普通工单附件。
从 1.17 升到 1.18 的官方步骤要求先更新 CLI,再更新 CRD,然后更新 server、provider plugin 和可选 node-agent。渲染 CRD 后先审查 diff:
velero install --crds-only --dry-run -o yaml > velero-crds-next.yaml
kubectl diff -f velero-crds-next.yaml
kubectl apply -f velero-crds-next.yaml
kubectl -n velero set image deployment/velero \
velero=velero/velero:<approved-1.18-patch> \
<provider-container>=<approved-provider-plugin>
kubectl -n velero set image daemonset/node-agent \
node-agent=velero/velero:<approved-1.18-patch>
kubectl -n velero rollout status deployment/velero
kubectl -n velero rollout status daemonset/node-agent
velero version示例中的镜像必须替换为经兼容矩阵和供应链流程批准的版本与 digest。升级完成后不要立刻恢复所有 Schedule。先用新链创建一个 canary backup,再从一个升级前恢复点和一个升级后恢复点分别恢复到隔离 namespace,验证 API 对象、PVC、业务水位、KMS、插件和清理。
velero backup create upgrade-canary \
--include-namespaces <canary-namespace> \
--wait
velero restore create upgrade-old-point-test \
--from-backup <pre-upgrade-backup> \
--namespace-mappings <source>:<isolated-target> \
--wait
velero restore create upgrade-new-point-test \
--from-backup upgrade-canary \
--namespace-mappings <canary-namespace>:<isolated-canary-target> \
--wait回滚也要在升级前设计。回滚 server 镜像并不自动回滚 CRD schema、已写入的新字段、仓库格式或新插件创建的快照。只有旧版本仍能读取当前 CRD 与仓库、旧插件仍与 provider API 兼容、且升级后没有产生不可逆格式变化时,镜像回滚才成立。否则应停止新任务,保留新 controller 的只读恢复环境,并按产品升级路径修复,而不是强行覆盖 CRD。
restic 断点必须在升级前消化
Velero 的旧 restic 路径存在明确断点:1.17 和 1.18 不再创建新的 restic 路径备份,但仍允许恢复既有备份;从 1.19 起,旧 restic 路径的恢复也被禁用。官方没有承诺把历史 restic 仓库原地转换成 Kopia。
升级到 1.19+ 前,先从账本识别全部 restic 恢复点,并为每个点选择一种退出方式:
在仍兼容的 1.18 隔离环境恢复数据,通过业务校验后用 Kopia 重新备份;保留受限、无生产写权限的 1.18 恢复环境,直到旧恢复点按策略到期;若历史点已经损坏或无业务价值,经过数据 owner、安全与合规审批后核销。
反向实验很直接:在目标升级镜像的隔离集群中尝试读取一份旧 restic 恢复点,预期 1.19+ 不再提供该恢复路径。该失败不是临时故障,而是升级停止条件。不要等生产灾难发生时才发现唯一的旧点只能由已卸载版本读取。
双轨迁移:新旧链都能恢复后才切换
从 Velero 迁移到另一套平台,或从一个 repository/provider 迁移到另一个,不能把“新工具安装成功”当作切换点。建议按六个状态推进:
冻结对象边界
列出 API 对象、PVC、外部数据库、Secret/KMS、镜像、DNS 和集群基础设施分别由谁保护。新工具如果不覆盖某一域,就保留原链或建立专门方案,不能让责任在迁移中消失。
隔离新旧写入
新工具使用独立 namespace、ServiceAccount、bucket/prefix、KMS grant 和标签。不要让两套 controller 同时管理同一个 Backup CR、repository prefix、快照标签或 retention policy。重复读取通常可控,重复删除和重复维护最危险。
建立重叠保护期
旧 Schedule 继续运行,新链创建同一业务水位附近的恢复点。账本同时记录两边的备份时长、容量、复制延迟、失败率、权限、对象覆盖和恢复结果。重叠期增加成本,但它购买了可回滚性。
做正反恢复实验
正向实验从新链恢复完整业务;反向实验至少注入仓库只读、KMS 拒绝、目标 StorageClass 缺失、对象冲突和配额不足。两边都用相同业务断言计算 RPO/RTO,不能拿产品 Job 耗时互相比排名。
停止旧写入但保留旧读取
新链连续通过约定轮次后,暂停旧 Schedule,把旧 BSL/仓库切为只读。观察一个完整保留窗口,确认没有应用或审计流程仍引用旧恢复点,再逐步收回旧写权限。
到期退出
旧恢复点按审批策略到期,先处理 legal hold、异地副本和 KMS,再卸载 controller。任何一步出现新链恢复失败,都回到旧链继续保护,而不是继续删除以满足项目时间表。
安全卸载不是一条 uninstall 命令
velero uninstall 会删除 velero install 创建的集群资源,但不会替你删除 bucket、历史 backup、provider snapshot、Kopia repository、云 IAM/KMS、跨账号复制、恢复出的业务对象或临时公网资源。卸载前先把所有 Schedule 暂停,并保存最后一次库存和恢复证据。
推荐按下面顺序退出:
冻结新备份和恢复请求,确认没有运行中的 Backup、Restore、DataUpload、DataDownload、PodVolumeBackup 或 maintenance Job。将需要保留的 BSL 切为 ReadOnly,完成最后一次隔离恢复并保存业务水位。对到期恢复点使用产品删除 API,等待仓库维护并核对 provider inventory;保留期未到的对象登记 owner 和到期动作。
导出 CR、Helm values、镜像 digest、版本矩阵、仓库说明和审计日志;Secret 只记录托管引用,不导出明文。执行 velero uninstall 或对应 Helm 卸载,并观察 namespace、ClusterRoleBinding、webhook、CRD 和 finalizer。收回云 IAM、工作负载身份、KMS grant、bucket policy、跨账号信任、registry pull 权限和临时恢复 RBAC。
核对 bucket/version、快照、复制规则、DNS、LoadBalancer、PVC/PV、staging resource、费用标签和告警,确认每个残留都有保留理由。
kubectl -n velero get backup,restore,podvolumebackup,podvolumerestore \
-o wide
kubectl -n velero get dataupload,datadownload,job -o wide
velero backup-location get
velero repo get
velero uninstall
kubectl get namespace velero
kubectl get clusterrole,clusterrolebinding | grep -i velero
kubectl get crd | grep velero.io
kubectl get validatingwebhookconfiguration,mutatingwebhookconfiguration \
| grep -i veleroCRD 是否立即删除要服从审计和残留对象策略。直接删除 CRD 会级联删除对应 CR,可能抹去恢复点索引和清理线索;只有确认数据已迁移、外部残留已建立独立账本且不再需要旧 controller 读取时才执行。遇到 finalizer 卡住,应先恢复负责清理的 controller 或手工完成外部清理,再移除 finalizer,不能把强删当常规卸载步骤。
用权限、容量和成本证明退出完成
退出核销不能止于 Pod 数量归零。至少检查三张账:
权限账:Kubernetes RBAC、云 IAM、bucket policy、KMS grant、OIDC/工作负载身份、静态 Secret、跨账号信任都已删除或转交明确 owner。数据账:每个 Backup、对象 version、snapshot、repository、replica 和 legal hold 都是已删除、到期保留或迁移完成三种状态之一。
费用账:对象 current/noncurrent version、delete marker、快照、跨区复制、KMS、API 请求、临时卷和公网资源不再产生无 owner 成本。
仓库容量治理要同时观察逻辑保留量、物理占用、去重率、删除待回收量、maintenance 成功率和恢复吞吐。缩短 Kopia full maintenance interval 可以更快回收未引用数据,却也缩短误删后的安全缓冲并增加 IO;参数应由容量压力、恢复窗口和仓库规模共同决定,不应为了一次账单异常直接调到最激进值。
长期责任也要固定下来:应用 owner 决定数据水位与保留价值;平台团队维护 Schedule、CRD、controller 和迁移执行;存储团队负责仓库、快照、复制和容量;安全团队持有 KMS、不可变策略与证据访问;财务或 FinOps 追踪非当前版本、跨区流量和无 owner 资源。删除审批不能由同时持有备份写入和 KMS 删除权的单一身份独立完成。
生命周期决策清单
在允许升级、迁移或卸载继续推进前,逐项回答:
当前可恢复点是否同时有业务水位、对象归档、卷副本、KMS 和异地复制证据?TTL、对象 lifecycle、不可变保留、快照策略和仓库维护是否会产生相互冲突的删除时间?新版本是否能读取升级前的每一种仓库和快照,尤其是旧 restic 恢复点?
CRD、server、provider plugin、node-agent、CSI 和 Kubernetes 的兼容矩阵是否固定到版本与 digest?升级前后恢复点是否都在隔离环境通过业务读写和 RPO/RTO 验证?新旧链是否使用独立写入域,并存在足够长的重叠保护期?
回滚是否覆盖 CRD、仓库格式和插件,而不仅是 Deployment 镜像?卸载后 bucket、快照、repository、KMS、IAM、RBAC、DNS 和临时卷是否逐项核销?未到期不可变对象是否有 owner、到期时间、费用预算和后续删除任务?
最后一个旧恢复点到期前,隔离恢复环境和读取凭证是否仍可重建?
真正安全的退出不是“旧工具已经不运行”,而是新链已经证明能恢复,旧链的每个恢复点都有去向,权限和成本都有 owner,并且任何不可逆删除都发生在证据充分之后。
延伸阅读
Velero 1.18 Schedule API。Velero 1.18 Repository Maintenance。Velero 1.18 Backup Repository Configuration
Velero 1.18 File System Backup 与 restic 弃用。Velero 1.18 Uninstalling。S3 Object Lock
