备份与恢复:Velero¶
etcd 快照能救回一个集群,却救不回被误删的命名空间和写坏的 PVC。Velero 把「Kubernetes 对象」和「卷里的数据」拆成两条独立路径备份,再组合成一次可回放的恢复。本章从零装一套 Velero + MinIO,走完备份、删除、恢复、定时、快照五步。
你将学到¶
- 区分「备份对象」与「备份数据」,知道什么时候该用 etcd 快照、什么时候该用 Velero
- 控制面与 node-agent 的分工,BackupStorageLocation 与 VolumeSnapshotLocation 各自存什么
- 用 MinIO 作为对象存储后端安装 Velero,完成
backup/restore/schedule日常操作 - 资源过滤、标签选择、CSI 快照、Kopia 文件级备份与 pre/post hooks 的取舍
为什么需要它¶
Kubernetes 的「备份」经常被简化成「定时 etcd 快照」。但 etcd 快照保护的是 API 对象的整体状态:它能救回整个集群,却无法只恢复一个命名空间,不含 PV 里的数据,回滚还会连带丢掉快照之后的所有变更。真实故障更多是局部且具体的——误删命名空间、升级把 ConfigMap 改坏、PVC 被应用写脏、迁移到另一个集群。
Velero 的定位就是这些局部场景:用 CRD 描述一次备份(Backup)或恢复(Restore),把对象序列化后存进对象存储,把卷数据交给快照或文件级上传器。它不替代 etcd 快照,而是补上「按命名空间、按标签、按资源类型」的恢复粒度。
| 手段 | 保护范围 | 恢复粒度 | 主要局限 |
|---|---|---|---|
| etcd 快照 | 全集群 API 对象 | 整个集群 | 不含 PV 数据;回滚丢弃快照后的所有变更 |
| Velero 对象备份 | 选定的资源与命名空间 | 命名空间 / 标签 / 资源类型 | 本身不含卷数据,需配合快照或 FSB |
| CSI 卷快照 | 单个 PV 的数据 | 单个 PVC | 依赖存储驱动;快照通常不能跨集群、跨云 |
应用级导出(mysqldump 等) |
逻辑数据 | 库 / 表 / 行 | 每个应用都要单独写脚本与调度 |
核心概念¶
对象与数据是两条路径¶
一次 Velero 备份产出两部分:对象清单(API 对象序列化后打包上传到对象存储)和卷数据。后者有三条路径,选错就会出现「恢复成功但数据是空的」:
- CSI 快照:调用
VolumeSnapshotClass让存储侧打时间点快照,一致性最好,但快照留在原存储上。 - 快照 + 数据移动(
--snapshot-move-data):先打快照,再由 node-agent 把内容读出来上传到对象存储,兼顾一致性与可迁移性。 - 文件级备份(FSB,Pod Volume Backup):node-agent 直接读节点上的卷目录打包上传,不需要存储支持快照,但写入频繁时有数据不一致风险;也正因如此,仓库
Velero/tests.md把它定位为「卷不支持快照时唯一可用的方法」。
组件与数据流¶
flowchart LR
S["velero server"] -->|"监听 Backup CR"| API[kube-apiserver]
S -->|"请求 CSI 快照"| VSC["VolumeSnapshotContent"]
N["node-agent DaemonSet"] -->|"读取卷目录"| P["Pod 卷"]
N -->|"Kopia 写入"| O[("对象存储 MinIO")]
VSC -->|"dataSource 重建"| PVC["新 PVC"]
图:server 只管对象与编排,真正搬数据的是每个节点上的 node-agent。
velero install 会创建整套 CRD(kubectl api-resources --api-group=velero.io 可查):Backup、Restore、Schedule、BackupStorageLocation(bsl)、VolumeSnapshotLocation(vsl)、PodVolumeBackup、DeleteBackupRequest。BSL 决定备份元数据存哪里,VSL 决定快照存在哪里——两者可以是完全不同的存储系统,前者必须是对象存储。安装后自动创建的 default BSL,只有 PHASE=Available 才代表凭据、网络、bucket 三者都对。
hooks:让备份发生在「一致的那一刻」¶
文件级备份是「边写边读」,数据库类应用需要先落盘。Velero 用 Pod 注解在备份前后注入命令,hook 失败会让备份变成 PartiallyFailed 而不是静默成功:
常用的是 pre.hook.backup.velero.io/command(备份前执行,如 redis-cli BGSAVE)、post.hook.backup.velero.io/command(备份后清理或解冻)、pre.hook.backup.velero.io/container 与 /timeout(默认第一个容器、30s),恢复侧对应 post.hook.restore.velero.io/command。
动手实验¶
实验:备份一个命名空间,删掉它,再恢复回来
目标:用 MinIO 做 BSL 安装 Velero,完成对象级备份/恢复、定时备份,以及 NFS PVC 的 CSI 快照与恢复。
前置:集群已有 nfs-csi StorageClass;已安装 velero CLI(仓库使用 v1.13.0);一台能跑容器的机器放 MinIO。
步骤 1 · 准备 MinIO 并安装 Velero¶
仓库 Velero/minio/docker-compose.yml 是可直接用的 MinIO(9000/9001,minioadmin / magedu.com)。起服务、建 bucket、写与 Velero/credentials-velero 一致的凭据文件,再安装;s3Url 要填 Velero 能访问到的 MinIO 地址(它在集群内运行,所以写宿主机 IP 或集群内 Service DNS):
cd source-repo/Velero/minio && docker compose up -d
docker run --rm --network host minio/mc \
sh -c 'mc alias set local http://127.0.0.1:9000 minioadmin magedu.com && mc mb local/velero'
cat > credentials-velero <<'EOF'
[default]
aws_access_key_id = minioadmin
aws_secret_access_key = magedu.com
region = minio
EOF
velero install --provider=aws --bucket=velero --use-node-agent --uploader-type=kopia \
--plugins=velero/velero-plugin-for-aws:v1.9.0 \
--backup-location-config region=minio,s3ForcePathStyle=true,s3Url=http://172.29.7.52:9000 \
--snapshot-location-config region=minio --secret-file=./credentials-velero
kubectl -n velero get pods,deploy
velero backup-location get
pod/node-agent-6r5wb 1/1 Running 0 6m
NAME PROVIDER BUCKET/PREFIX PHASE ACCESS MODE DEFAULT
default aws velero Available ReadWrite true
--use-node-agent 创建负责搬数据的 DaemonSet(v1.13 起取代 --use-restic);--uploader-type=kopia 是新部署的默认上传器,restic 已在 v1.14 弃用、v1.15 起移除。
步骤 2 · 备份一个命名空间¶
先造数据:一个带 NFS PVC 的 Redis,并用 pre hook 保证备份前落盘。
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
namespace: demo
spec:
storageClassName: nfs-csi
accessModes: ["ReadWriteMany"]
resources: {requests: {storage: 5Gi}}
---
apiVersion: v1
kind: Pod
metadata:
name: redis-with-nfs-pvc
namespace: demo
annotations:
pre.hook.backup.velero.io/command: '["/bin/sh", "-c", "redis-cli BGSAVE"]'
pre.hook.backup.velero.io/timeout: "60s"
spec:
containers:
- name: redis
image: redis:7-alpine
volumeMounts: [{name: data, mountPath: /data}]
volumes:
- name: data
persistentVolumeClaim: {claimName: nfs-pvc}
kubectl create namespace demo
kubectl apply -f demo-redis.yaml
kubectl -n demo exec redis-with-nfs-pvc -- redis-cli set course k8s
velero backup create demo-fsb --include-namespaces demo --default-volumes-to-fs-backup --wait
velero backup describe demo-fsb --details
步骤 3 · 删掉再恢复¶
kubectl delete namespace demo && velero restore create --from-backup demo-fsb --wait
kubectl -n demo exec redis-with-nfs-pvc -- redis-cli get course
--namespace-mappings demo:demo-restored 可恢复到另一个命名空间;--include-resources、-l/--selector 只恢复备份集的一部分;目标对象已存在时用 --existing-resource-policy=update 覆盖。
步骤 4 · 定时备份¶
velero schedule create demo-nightly --schedule="0 2 * * *" --include-namespaces demo \
--default-volumes-to-fs-backup --ttl 168h
velero schedule get
velero backup create --from-schedule demo-nightly --wait # 不等 cron,立即验证
cron 按 UTC 解释,0 2 * * * 在东八区是上午 10 点;--ttl 控制保留期(默认 720h)。velero backup delete demo-fsb --confirm 删单个,--all --confirm 清空。
步骤 5 · NFS PVC 的 CSI 快照与恢复¶
CSI 快照需要三件套:snapshot.storage.k8s.io/v1 的 CRD、snapshot-controller、存储驱动的快照能力(csi-driver-nfs v4.6.0 的部署清单已含前两者)。
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
name: nfs-csi
labels: {velero.io/csi-volumesnapshot-class: "true"} # 全集群只能标一个
driver: nfs.csi.k8s.io
parameters:
server: 172.29.7.2
share: /data/nfs
deletionPolicy: Delete
仓库同名文件把 parameters: 写了两遍(第一处是空值),kubectl apply 会报 duplicate field;不同 YAML 解析器对重复键的行为并不一致,直接用上面这版。
kubectl apply -f nfs-csi-volumesnapshotclass.yaml
velero backup create demo-csi --include-namespaces demo --snapshot-volumes --wait
velero backup describe demo-csi --details
恢复时 Velero 用快照内容重建 PVC,机理与手工写 dataSource: {name: nfs-pvc-snap, kind: VolumeSnapshot} 的 PVC 相同;执行 velero restore create demo-csi-restore --from-backup demo-csi --namespace-mappings demo:demo-restored --wait 后用 kubectl -n demo-restored get pvc,pod 验证。
若目标是另一个集群,快照内容并不跟着走,此时加 --snapshot-move-data,由 Kopia 把快照数据上传到对象存储,describe --details 会出现 Data Movement: Data Mover: velero。另一种指定快照类的方式是在 Backup 上加注解 velero.io/csi-volumesnapshot-class_nfs.csi.k8s.io: "nfs-csi",适合一个集群有多个 CSI 驱动的场景。
验证清单¶
-
velero backup-location get显示Available,velero version能连上 server -
kubectl -n velero get ds node-agent的DESIRED等于工作节点数 - 备份
Phase=Completed、ERRORS 0,且--details中卷段非空 - 快照恢复出的 PVC
Bound,kubectl get vs中READYTOUSE=true
常见问题与排错¶
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
BSL 一直 Unavailable |
s3Url 不可达、bucket 不存在、凭据错 |
kubectl -n velero logs deploy/velero 看 NoSuchBucket / SignatureDoesNotMatch,再用 mc ls 从集群内验证 |
node-agent 0/1 CrashLoopBackOff |
未挂载 /var/lib/kubelet/pods、SELinux 拒绝、hostPath 只读 |
检查 DaemonSet 的 hostPath 与 privileged;kubectl -n velero logs ds/node-agent |
备份 PartiallyFailed,错误提到 volumes |
PVC 后端不支持快照且没开 FSB | 加 --default-volumes-to-fs-backup,或给驱动配带 Velero 标签的 VolumeSnapshotClass |
恢复后 PVC Pending,事件 no volume snapshot class |
目标集群缺 VolumeSnapshotClass 或快照内容已删 | 恢复前重建快照类;kubectl get vsc 确认 READYTOUSE |
| 恢复成功但 Pod 里没数据 | 备份时只备了对象,未备卷数据 | 用 backup describe --details 确认卷段非空,重做带卷的备份 |
velero backup delete 后 bucket 里还有对象 |
删除是异步的 DeleteBackupRequest |
kubectl -n velero get deletebackuprequests,等 Processed |
进阶挑战¶
- 用
--selector app=demoapp做一次「按标签」备份,恢复到新命名空间,验证只带走带标签的对象。 - 配置两个 BSL(MinIO 两个 bucket),用
--storage-location做「日常 / 归档」分离,再用velero backup-location set切换默认位置。 - 把
demo-nightly的 TTL 改成24h,连续触发两次,观察旧备份是否按 TTL 过期。
小结¶
- Velero 备份的是「对象 + 卷数据」,两条路径要分别确认,否则会得到一次「成功但空」的恢复。
- server 负责编排与对象打包,node-agent 负责搬数据;BSL 存元数据,VSL /
VolumeSnapshotClass管快照。 - 卷数据有快照、快照+数据移动、文件级备份三种方式,一致性依次降低、可迁移性依次升高。