课程目录(第 24 章 / 共 33 章)
备份与恢复:把「删错了」变成小事
分清集群状态、资源清单、卷数据与镜像四层备份,用 etcd 快照、Velero 和 VolumeSnapshot 做一次真的能恢复的备份。
学完这一章,你将能够
- ✓说得出备份要分哪四层、各层用什么手段
- ✓会用 etcdctl 做快照并知道恢复为什么要先停 apiserver
- ✓用 Velero 完成一次「备份 → 删除 → 恢复」的完整演练
- ✓用 RPO 与 RTO 和业务方对齐备份策略
备份是分层的,不是「备份一下」
「我们有备份」这句话在生产环境里经常不成立。数据库每天 dump 到本机另一块盘、YAML 存在 Git 里、PVC 从来没动过——看起来像有备份,实际上机房的存储一坏就全没了。要回答「备份够不够」,先把备份对象拆成四层:每一层能恢复的东西、用的手段、恢复粒度都不一样。
① etcd 快照 ──── 所有 Kubernetes 对象(Namespace / PVC / CRD / Secret)
② 资源清单 ───── Git 里的 YAML(但 Secret 与部分 CRD 未必在里面)
③ 卷数据 ─────── PVC 里的真实数据(快照或逻辑导出)
④ 镜像仓库 ───── 应用能不能重新跑起来的前提| 层 | 备份什么 | 常用手段 | 恢复粒度 |
|---|---|---|---|
| etcd | 整个集群的对象状态 | etcdctl snapshot save、托管集群备份 | 整个集群 |
| 清单 | Deployment、Service、CRD、配置 | Git 仓库、Velero | 命名空间或单个资源 |
| 卷数据 | PVC 里的文件与数据库文件 | VolumeSnapshot、mysqldump、RDB | 单个卷或单张表 |
| 镜像 | 应用镜像与 chart | 仓库间复制、多机房同步 | 单个镜像 |
一个务实的判断标准是:任何一层单独存在都不算备份。 只有 etcd 快照,恢复回来的是「指向已消失 PVC 的对象」;只有卷快照,恢复回来的是「没人挂载的数据」;只有 Git 里的 YAML,恢复回来的是「空数据库的新集群」。
etcd 快照:集群状态的地基
etcd 存着集群里所有对象的最终状态,它挂了,Deployment、Service、PVC、Secret 就都没了。etcd 快照是唯一能整体恢复集群对象的手段,其它工具(包括 Velero)备份的都是「对象的内容」,恢复时仍要往一个活着的集群里写。
在控制面节点上执行,证书路径是 kubeadm 集群的默认位置:
ETCDCTL="etcdctl --endpoints=https://127.0.0.1:2379 \
--cacert=/etc/kubernetes/pki/etcd/ca.crt --cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key"
$ETCDCTL endpoint status --write-out=table
$ETCDCTL snapshot save /var/backups/etcd-$(date +%F-%H%M).db
$ETCDCTL snapshot status /var/backups/etcd-2024-05-21-0200.db --write-out=tableendpoint status 打出每个成员的角色、版本和数据库大小;snapshot status 打出 totalKey、totalSize、revision 和 hash,用来确认快照文件完整可读。快照做完后必须复制到集群之外的介质,留在 /var/backups 只是「同一块盘上的第二个副本」。
恢复是「用旧状态覆盖现有集群」,比备份危险得多。核心约束是:恢复必须在 etcd 与 apiserver 都停止之后做,否则客户端会继续写入,恢复完立刻被覆盖。
停 apiserver → restore 到新 data-dir → 切 data-dir 启动 etcd → 恢复 apiserver 并验证ETCDCTL_API=3 etcdctl snapshot restore /var/backups/etcd-2024-05-21-0200.db \
--name=<etcd-节点名> --initial-cluster=<etcd-节点名>=https://<节点IP>:2380 \
--initial-advertise-peer-urls=https://<节点IP>:2380 \
--initial-cluster-token=etcd-cluster-restore --data-dir=/var/lib/etcd-restore然后编辑 /etc/kubernetes/manifests/etcd.yaml,把 hostPath 的 path 从 /var/lib/etcd 改成 /var/lib/etcd-restore,etcd 会自动重启。多控制面集群不能只恢复一个成员:要用同一个 --initial-cluster-token、按各自的名字与地址逐节点恢复,步骤以 etcd 官方灾难恢复文档为准。单节点实验集群最适合练这一步,生产上的恢复演练要有变更窗口和回滚预案。
Velero:把资源与卷一起备份
etcd 快照适合「整个集群回滚」,但日常更常见的是「某个命名空间被删了」「某张 PVC 要回到昨天」。这时候用 Velero:它把 Kubernetes 对象导出成备份文件存到对象存储,配合 CSI 快照把卷一起带上。
用 CLI 安装(<插件版本> 按 velero-plugin-for-aws 的最新 release 替换):
velero install --provider aws --plugins velero/velero-plugin-for-aws:<插件版本> \
--bucket kube101-backup --backup-location-config region=cn-north-1,s3Url=https://s3.example.com \
--snapshot-location-config region=cn-north-1 --secret-file ./credentials-velero
kubectl -n velero get pods && velero backup-location get如果 CLI 提示 --provider 已废弃,直接去掉即可,新版靠插件自动识别对象存储类型。credentials-velero 是 AWS 凭证文件,内容是 [default] 段加上 aws_access_key_id 与 aws_secret_access_key 两行。
生产上更推荐把备份策略写成清单交给 Git 管(snapshotVolumes 默认为 true,会连卷一起备份):
apiVersion: velero.io/v1
kind: Backup
metadata:
name: demo-backup
namespace: velero
spec:
includedNamespaces:
- demo
ttl: 720hapiVersion: velero.io/v1
kind: Restore
metadata:
name: demo-restore
namespace: velero
spec:
backupName: demo-backup
namespaceMapping:
demo: demo-restoredapiVersion: velero.io/v1
kind: Schedule
metadata:
name: demo-daily
namespace: velero
spec:
schedule: "0 2 * * *"
template:
includedNamespaces:
- demoSchedule 的 template 就是 Backup 的 spec,所以「每天凌晨 2 点备份 demo」就是这几行;ttl 控制保留时长,上面 Backup 里的 720h 约等于 30 天。对应的 CLI 命令是:
velero backup create demo-backup --include-namespaces demo --wait
velero backup describe demo-backup --details
velero restore create --from-backup demo-backup --namespace-mappings demo:demo-restored
velero backup logs demo-backupvelero backup describe --details 会列出备份了哪些资源、卷快照的状态;velero backup logs 能看出哪些资源因为权限或 CRD 缺失没被备份到——这一步是「备份到底成不成」的关键证据。
PVC 快照:VolumeSnapshot 与从快照恢复
Velero 的卷备份底层依赖 CSI 快照,所以集群里得先有快照能力:快照 CRD、snapshot-controller,以及 CSI 驱动自带的 csi-snapshotter sidecar。多数托管集群已经内置,自建集群需要自己装;如果下面的路径在你的版本里 404,就改用同一 release 下的 deploy/kubernetes/snapshot-controller/rbac-snapshot-controller.yaml 与 setup-snapshot-controller.yaml 直接 kubectl apply -f。
SNAP_VER=v8.2.0 # 按 external-snapshotter 的最新 release 替换
kubectl apply -k "https://github.com/kubernetes-csi/external-snapshotter//client/config/crd?ref=${SNAP_VER}"
kubectl apply -k "https://github.com/kubernetes-csi/external-snapshotter//deploy/kubernetes/snapshot-controller?ref=${SNAP_VER}"
kubectl -n kube-system rollout status deploy/snapshot-controller装好后先定义 VolumeSnapshotClass,driver 必须填集群里实际使用的 CSI 驱动名(用 kubectl get csidrivers 查看):
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: csi-snapclass
annotations:
snapshot.storage.k8s.io/is-default-class: "true"
driver: <你的 CSI 驱动名>
deletionPolicy: RetainapiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: data-snap-20240521
namespace: demo
spec:
volumeSnapshotClassName: csi-snapclass
source: {persistentVolumeClaimName: data}从快照恢复,是新建一个 PVC 并把 dataSource 指向快照:
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data-restored
namespace: demo
spec:
storageClassName: <原 PVC 的 StorageClass>
dataSource:
name: data-snap-20240521
kind: VolumeSnapshot
apiGroup: snapshot.storage.k8s.io
accessModes: ["ReadWriteOnce"]
resources: {requests: {storage: 10Gi}}kubectl -n demo get pvc data-restoredVolumeSnapshot 的 READYTOUSE 变成 true、新 PVC 变成 Bound,就说明恢复完成。注意恢复出的 PVC 不能小于快照的容量,而且必须用兼容的 StorageClass,快照的前提条件在 生产存储 里讲过。
有状态应用:逻辑备份还是卷快照
卷快照快、对应用透明,恢复的是「某个时刻的整个磁盘」;逻辑备份慢,却能跨版本、跨云、按表恢复。两者不是二选一,而是分工。卷快照适合整库回滚到某个时间点,但依赖 CSI 支持、只能整体恢复、跨存储迁移难;逻辑备份适合定期 dump、迁移与精确恢复,但慢、占 CPU/IO,需要应用侧工具。
具体到应用:Redis 用 redis-cli BGSAVE 生成 RDB 后复制到对象存储,开了 AOF 就一起备份;MySQL 用 mysqldump --single-transaction --databases demo > demo.sql 做逻辑备份,再靠 binlog 做时间点恢复。卷快照要配合 FLUSH TABLES WITH READ LOCK 或数据库自带的 backup 模式才是一致的。
# Redis:先落盘再取文件,顺手记下 key 数量作为校验基线(Pod 名以你的集群为准)
kubectl -n demo exec deploy/redis -- redis-cli -a "$REDIS_PASSWORD" BGSAVE
kubectl -n demo exec deploy/redis -- redis-cli -a "$REDIS_PASSWORD" DBSIZE
kubectl -n demo cp redis-0:/data/dump.rdb ./dump-$(date +%F).rdb
# MySQL / PostgreSQL:导出成可读文件,能跨版本、跨云、按表恢复
kubectl -n demo exec deploy/mysql -- sh -c 'mysqldump --single-transaction --databases demo > /tmp/demo.sql'
kubectl -n demo exec deploy/postgres -- sh -c 'pg_dump -Fc -f /tmp/demo.dump demo'--single-transaction 让 InnoDB 在同一个事务快照里读,不锁表;pg_dump -Fc 是自定义格式,能配合 pg_restore 并行恢复、按表恢复。逻辑备份的代价是慢和占 CPU/IO,频率要按 RPO 定,别无脑天天全量。
怎么选,先看你要恢复什么:
| 要恢复什么 | 优先手段 | 原因 |
|---|---|---|
| 整库回到 1 小时前 | 卷快照 | 快,且能保住整库一致性 |
| 只捞回一张误删的表 | 逻辑备份 | 快照无法单表恢复 |
| 跨云 / 跨存储迁移 | 逻辑备份 | 快照格式依赖 CSI 驱动 |
| 升级到新版本引擎 | 逻辑备份 | 快照只有原始字节,版本不兼容 |
| 机房级灾难恢复 | 两者都要 | 快照省时间,逻辑备份保底 |
卷快照的一致性还要靠引擎配合:MySQL 用 FLUSH TABLES WITH READ LOCK(或 XtraBackup 的备份锁)、PostgreSQL 用 pg_backup_start/pg_backup_stop,否则快照可能抓到「写了一半」的页,恢复后起不来。
参考:reference/k8s-in-action/db/redis/README.md、reference/k8s-in-action/db/tikv/backup-restore.md
快照不是备份
卷快照通常和源卷在同一个存储集群里,存储集群整体故障时两者一起消失。快照要定期转存到另一套介质,或者用 Velero 同步到对象存储——「不同介质」是备份的定义之一。
动手练习:备份、删掉、再恢复
动手练习:完整走一遍「备份 → 删除 → 恢复」
准备一个有状态的应用,demo 命名空间里放一个 PVC 加一个会写文件的 Pod:
kubectl create namespace demo
kubectl -n demo apply -f - <<'EOF'
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources: {requests: {storage: 1Gi}}
---
apiVersion: v1
kind: Pod
metadata:
name: writer
spec:
containers:
- name: writer
image: busybox:1.36
command: ["sh", "-c", "echo hello-from-before-backup > /data/proof.txt && sleep 3600"]
volumeMounts: [{name: data, mountPath: /data}]
volumes:
- name: data
persistentVolumeClaim: {claimName: data}
EOF
kubectl -n demo exec writer -- cat /data/proof.txt确认文件内容之后,创建一次备份:
velero backup create demo-backup --include-namespaces demo --wait
velero backup describe demo-backup --details现在模拟事故,把整个命名空间删掉:
kubectl delete namespace demo恢复到一个新命名空间,避免覆盖可能存在的残留:
velero restore create --from-backup demo-backup --namespace-mappings demo:demo-restored
velero restore get
kubectl -n demo-restored exec writer -- cat /data/proof.txt看到 hello-from-before-backup 打印出来,这次备份才算真的可用。如果 Velero 部署时没有配置卷快照,/data 会是空的——那正好说明「只备份了对象,没备份数据」。
演练、验证与 RPO/RTO
备份最大的谎言是「备份任务显示成功」。真正能证明它可用的只有一件事:恢复出来并且对得上。
- 定期恢复演练:至少每季度一次,把生产备份恢复到隔离命名空间,跑一遍业务自检。演练记录要写清楚从发起到验证通过用了多久——这就是实测 RTO。
- 数据校验:数据库比
SELECT COUNT(*)或关键表主键范围,Redis 比DBSIZE,文件比sha256sum。数字对得上才算数据完整。 - 自动化:把演练写成 Job 或 CronJob,结果和耗时发到 可观测性 里的告警通道。人工演练一定会被别的事挤掉。
| 指标 | 含义 | 怎么压低 |
|---|---|---|
| RPO(恢复点目标) | 最多能接受丢多少数据,用时间衡量 | 提高备份频率、开 binlog 或增量、用同步复制 |
| RTO(恢复时间目标) | 最多能接受中断多久 | 自动化恢复流程、定期演练、预置环境 |
对齐的方式是反着问业务方三个问题:丢多少数据算事故?停多久算事故?多久前的数据必须能拿回来? 如果业务说「丢 15 分钟可以,停 1 小时可以」,那备份频率就得高于 15 分钟一次,恢复流程要能在 1 小时内跑完并验证。注意 RTO 包含「发现故障 + 决策 + 执行恢复 + 验证」的全过程,只算 kubectl apply 的时间会严重低估。
恢复演练:从备份到可用数据的五步
演练的目的不是「跑通流程」,而是量出真实 RTO 并发现备份里缺了什么。固定五步,每一步都有产物:
- 建隔离环境:新建命名空间或临时集群,绝不在生产命名空间里做恢复,避免恢复动作污染线上。
- 按依赖顺序恢复:先恢复 CRD 与 StorageClass,再恢复对象,最后灌数据。顺序反了就会卡在「对象找不到 CRD」或「PVC 找不到存储类」。
- 校验数据:数据库比行数与主键范围,Redis 比
DBSIZE,文件比sha256sum。数字对不上就停下来查,别继续往上放流量。 - 跑业务自检:用应用自己的健康检查或一条只读请求验证,而不是只看 Pod 是
Running。 - 记录并清理:记下「发起时间 → 校验通过时间」(这就是实测 RTO)、备份文件大小与缺失项,然后删掉临时环境。
start=$(date +%s)
velero restore create --from-backup demo-backup --namespace-mappings demo:drill
kubectl -n drill exec deploy/redis -- redis-cli -a "$REDIS_PASSWORD" DBSIZE
echo "RTO=$(($(date +%s) - start))s"
kubectl delete namespace drill演练要定期做,而且要换人做。 写脚本的人自己执行,永远发现不了「路径只有作者知道」这类问题。数据库的恢复优先用 Operator 的接口(KubeBlocks 用 kbcli cluster restore,TiDB 用 BR 的 Restore 对象),细节见数据库 Operator。
参考:reference/k8s-in-action/db/kubeblocks/kubeblocks-0.9.3/README.md备份前后的一致性校验
卷快照默认是崩溃一致性的(等于拔电源那一刻的字节):文件类负载打快照前用 fsfreeze -f /data 冻结写入、快照 READYTOUSE 后再 fsfreeze -u /data 解冻,数据库则用引擎自己的静默接口,不要指望卷快照一定一致。校验也要留证据:备份前造一份已知数据并记下校验值,恢复后重算,再用 elbencho 量一次吞吐——数据对得上、速率没塌,这次备份才算可用。
fio --name=prep --filename=/data/verifyfile --size=2G --bs=1M --rw=write --direct=1
sha256sum /data/verifyfile | tee /data/verifyfile.sha256 # 备份前记下
sha256sum -c /data/verifyfile.sha256 # 恢复后,输出 OK 才算成功
elbencho -w -b 4m -s 16g --direct /data/verify # 恢复后的卷再量一次吞吐参考:reference/k8s-in-action/storage/elbencho/README.md、reference/k8s-in-action/storage/volumesnapshots/README.md
常见坑与速查表
备份恢复里最致命的四个坑
- 备份和源数据在同一块盘:本机
/var/backups、同一个存储池的快照,都不算备份。介质要分开,最好跨机房。 - 只备份了 PVC,没备份 CRD 与 ConfigMap:恢复时对象起不来,或者自定义资源找不到对应 CRD。Velero 的
backup logs会提示跳过了哪些资源。 - 恢复时命名空间或 StorageClass 不一致:目标命名空间不存在、原 StorageClass 在新集群没有同名实现,恢复就会卡在 Pending。
- 快照驱动不支持:
VolumeSnapshot一直READYTOUSE=false,或 Velero 报no snapshotter found。用kubectl get csidrivers与 Class 的driver字段核对。
| 现象 | 原因 | 怎么确认 |
|---|---|---|
Velero 备份显示 PartiallyFailed | 部分资源无权限或 CRD 缺失 | velero backup logs <备份名> 看具体跳过项 |
恢复后 PVC 一直 Pending | StorageClass 不存在或不兼容 | kubectl get pvc -o yaml 看 storageClassName,对比目标集群 |
| 恢复后数据是空的 | 只恢复了对象,没有卷快照 | velero backup describe <备份名> --details 看卷快照状态 |
VolumeSnapshot 不 READYTOUSE | CSI 驱动不支持快照或 Class 的 driver 写错 | kubectl describe volumesnapshot 看 Events |
| etcd 恢复后集群行为异常 | 恢复时 apiserver 仍在运行,或只恢复了部分成员 | 检查静态 Pod 清单与 etcd 日志,确认恢复期间没有写入 |
自测题
自测:既然 etcd 快照能恢复一切,为什么还要 Velero?(点击展开答案)
因为两者粒度不同。etcd 快照恢复的是整个集群在某一个时间点的对象状态,是全有或全无:你没法只把 demo 命名空间退回昨天,恢复就意味着整个集群回到那个时刻,之后所有变更都丢失。
Velero 的粒度是命名空间、资源类型甚至单个对象,还能带上卷数据、支持恢复到另一个命名空间或另一个集群,适合「误删了一个命名空间」这类日常事故。生产上的组合是:etcd 快照应对集群级灾难,Velero 应对业务级误删。
自测:为什么 etcd 恢复必须先停掉 apiserver?(点击展开答案)
apiserver 是 etcd 唯一的写入方。如果恢复过程中 apiserver 还在跑,控制器和客户端会继续写新对象,snapshot restore 写回旧状态之后,这些新写入要么被覆盖、要么和新数据混在一起,恢复出来的集群状态无法预测。
停 apiserver 也意味着控制器停止工作,整个集群进入静默状态。所以恢复流程的第一步不是恢复数据,而是先让集群停止写入,这也是 etcd 恢复通常安排在变更窗口里的原因。
自测:卷快照和逻辑备份,为什么说后者不能省?(点击展开答案)
卷快照恢复的是「某个时刻磁盘上的原始字节」,它有三个绕不过去的限制:跨存储后端或跨云时不一定能恢复、只能整体恢复不能只捞一张表、依赖 CSI 驱动支持。
逻辑备份是应用自己导出的可读数据(SQL、RDB),能跨版本、跨云、按表恢复,还能直接校验行数。对核心数据来说,卷快照负责快速整体回滚,逻辑备份负责精确恢复与迁移,两者互为补充。
自测:为什么数据库的卷快照不能直接当备份用?(点击展开答案)
卷快照抓到的是崩溃一致性(crash-consistent)的镜像,相当于拔电源那一刻的字节。数据库恢复时会走自己的崩溃恢复流程(重放 redo / WAL),多数情况能起来,但页可能处于中间状态,跨卷的时序也无法保证(数据卷与日志卷来自不同快照点)。 要拿到应用一致性,必须在快照前让引擎进入备份模式或加锁,或者干脆用引擎自带的备份工具(BGSAVE、XtraBackup、pg_basebackup)。所以生产上不是「快照还是逻辑备份」,而是快照保速度、逻辑备份保正确与可迁移。
小结
- 备份分四层:etcd 对象状态、Git 里的清单、PVC 卷数据、镜像仓库,任何一层单独存在都不算备份。
etcdctl snapshot save/status是集群级灾难的唯一整体恢复手段,恢复必须在 etcd 与 apiserver 停止之后进行。- Velero 把对象与卷一起备份到对象存储,用
Backup、Restore、Schedule三种资源声明策略,backup logs是判断备份完整性的关键证据。 VolumeSnapshot依赖 CSI 驱动与 snapshot-controller,恢复时用dataSource新建 PVC。- 有状态应用用卷快照做整卷回滚、用逻辑备份做精确恢复,快照必须转存到不同介质。
- 备份可用性的唯一证明是恢复演练加数据校验,RPO 与 RTO 是和业务方对齐的共同语言。
练习
- 给你手上的一个业务定一份备份方案:四层里每层用什么手段、多久一次、存在哪、谁负责验证。
- 把
demo-daily的schedule改成每 5 分钟一次,观察备份的产生与过期,说说为什么生产上不能对所有数据都这么干。 - 设计一次恢复演练:列出步骤、预计耗时、验证方法,并说明如果实测 RTO 超出业务要求,你会先优化哪一步。
数据能回来了,但拉镜像还得靠外网。下一章 镜像仓库与加速 讲怎么让拉取又快又稳。