K8S周期性备份etcd数据实战案例
在 Kubernetes(K8S)集群的世界里,etcd 扮演着 “大脑” 的角色,它存储着整个集群的所有状态数据,从 Pod 的配置、服务的注册到网络策略的定义,无一不依赖于 etcd 的稳定运行。一旦 etcd 数据发生丢失或损坏,整个 K8S 集群可能陷入瘫痪,业务运行将遭受严重影响。
然而,在实际运维过程中,误操作、硬件故障、网络异常等突发状况时有发生,这些都可能对 etcd 数据的完整性构成威胁。因此,为 etcd 数据构建一套可靠的备份机制,尤其是实现周期性的自动备份,成为保障 K8S 集群稳定性和业务连续性的关键环节。
本文将聚焦于 K8S 环境下 etcd 数据的周期性备份,通过实战案例的形式,为大家详细展示如何搭建一套高效、稳定的自动备份方案,助力运维人员轻松应对数据安全挑战。
一、备份方案思路
这里后端存储使用Ceph的RBD或者对象存储效果更佳,操作都类似,所以这里演示的为PV-NFS。
本次方案的核心思路是利用 K8S 原生的 CronJob 实现周期性任务调度,通过etcdctl
工具执行备份操作,并依赖 PV 共享存储持久化保存备份文件和证书。具体包括以下关键点:
- 备份工具:使用 etcd 官方提供的
etcdctl
工具,支持 etcd 数据的快照备份; - 调度控制:通过 K8S 的 CronJob 控制器定义备份周期(如每天凌晨 2 点),自动触发备份任务;
- 证书管理:etcd 默认启用 TLS 加密通信,备份时需要挂载 CA 证书、客户端证书和密钥,确保
etcdctl
能正常访问 etcd 集群; - 存储方案:证书和备份文件通过
csi-nfs-storageclass
动态创建的 PVC 挂载,利用 NFS 共享存储实现持久化。
二、实战步骤详解
2.1 准备工作:在 NFS 服务器创建存储路径
NFS 存储依赖服务器端的目录共享,需先在 NFS 服务器的共享目录下创建证书和备份专用路径,并配置权限。
1. 确认 NFS 服务器共享目录
从用户环境可知,NFS 服务器的共享目录为/data/nfs-server
(通过exportfs
命令验证),所有 K8S 节点可访问该目录。
2. 创建证书和备份目录
在 NFS 服务器的/data/nfs-server
下创建证书目录(etcd-certs
)和备份目录(etcd-backup
),并赋予读写权限(确保 K8S Pod 可访问):
root@k8s-master:~# mkdir -p /data/nfs-server/etcd-certs
root@k8s-master:~# mkdir -p /data/nfs-server/etcd-backup
root@k8s-master:~# ll /data/nfs-server/
total 32
drwxr-xr-x 8 root root 4096 Jul 30 16:21 ./
drwxr-xr-x 3 root root 4096 Jul 30 14:54 ../
-rw-r--r-- 1 root root 0 Jul 30 14:55 1.txt
drwxr-xr-x 2 root root 4096 Jul 30 16:21 etcd-backup/
drwxr-xr-x 2 root root 4096 Jul 30 16:20 etcd-certs/
drwxrwxrwx 2 root root 4096 Jul 30 15:03 pv1/
drwxrwxrwx 2 root root 4096 Jul 30 15:03 pv2/
drwxrwxrwx 2 root root 4096 Jul 30 15:03 pv3/
drwxr-xr-x 3 root root 4096 Jul 30 15:22 sc/root@k8s-master:~# chmod 755 /data/nfs-server/etcd-certs
root@k8s-master:~# chmod 755 /data/nfs-server/etcd-backup
node节点保证可以正常使用NFS
root@k8s-node1:~# install -d /data/nfs-server/
root@k8s-node1:~# mount -t nfs 10.0.0.6:/data/nfs-server /data/nfs-server/
root@k8s-node1:~# ll /data/nfs-server/
total 32
drwxr-xr-x 8 root root 4096 Jul 30 16:21 ./
drwxr-xr-x 3 root root 4096 Jul 30 17:02 ../
-rw-r--r-- 1 root root 0 Jul 30 14:55 1.txt
drwxr-xr-x 2 root root 4096 Jul 30 16:21 etcd-backup/
drwxr-xr-x 2 root root 4096 Jul 30 16:22 etcd-certs/
drwxrwxrwx 2 root root 4096 Jul 30 15:03 pv1/
drwxrwxrwx 2 root root 4096 Jul 30 15:03 pv2/
drwxrwxrwx 2 root root 4096 Jul 30 15:03 pv3/
drwxr-xr-x 5 root root 4096 Jul 30 16:26 sc/
3. 复制 etcd 证书到 NFS 目录
将 etcd 证书复制到 NFS 的etcd-certs
目录(证书路径仍为/etc/kubernetes/pki/etcd/
):
root@k8s-master:~# cp /etc/kubernetes/pki/etcd/{ca.crt,peer.crt,peer.key} /data/nfs-server/etcd-certs/
root@k8s-master:~# ll /data/nfs-server/etcd-certs/
total 20
drwxr-xr-x 2 root root 4096 Jul 30 16:22 ./
drwxr-xr-x 8 root root 4096 Jul 30 16:21 ../
-rw-r--r-- 1 root root 1094 Jul 30 16:22 ca.crt
-rw-r--r-- 1 root root 1204 Jul 30 16:22 peer.crt
-rw------- 1 root root 1675 Jul 30 16:22 peer.key
2.2 编写 PVC 清单:基于 NFS 存储类创建持久卷
创建两个 PVC:一个用于挂载证书(只读),一个用于挂载备份目录(读写)。
1. 创建证书 PVC(etcd-certs-pvc)
新建static-pv-pvc-etcd-certs.yaml
,用于挂载 NFS 的etcd-certs
目录(证书需只读访问):
apiVersion: v1
kind: PersistentVolume
metadata:name: static-pv-etcd-certs # PV 名称
spec:capacity:storage: 1Gi # 容量仅为标识,不影响实际存储accessModes:- ReadOnlyMany # 只读,允许多节点访问persistentVolumeReclaimPolicy: Retain # 保留数据,删除 PVC 后不清理nfs:server: 10.0.0.6 # NFS 服务器 IP(从 PV 描述中获取)path: /data/nfs-server/etcd-certs # 手动创建的证书目录storageClassName: "" # 不指定存储类,避免动态供给---apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: static-pvc-etcd-certs # PVC 名称
spec:accessModes:- ReadOnlyManyresources:requests:storage: 1GivolumeName: static-pv-etcd-certs # 手动绑定到上面的 PVstorageClassName: "" # 与 PV 保持一致
2. 创建备份 PVC(etcd-backup-pvc)
新建etcd-backup-pv-pvc.yaml
,用于挂载 NFS 的etcd-backup
目录(备份文件需读写):
apiVersion: v1
kind: PersistentVolume
metadata:name: static-pv-etcd-backup # PV 名称
spec:capacity:storage: 10GiaccessModes:- ReadWriteMany # 读写,允许多节点访问persistentVolumeReclaimPolicy: Retainnfs:server: 10.0.0.6 # NFS 服务器 IPpath: /data/nfs-server/etcd-backup # 手动创建的备份目录storageClassName: ""---apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: static-pvc-etcd-backup # PVC 名称
spec:accessModes:- ReadWriteManyresources:requests:storage: 10GivolumeName: static-pv-etcd-backup # 手动绑定到上面的 PVstorageClassName: ""
3. 创建 PVC 并验证状态
执行以下命令创建 PVC,并确认状态为Bound
:
# 创建静态 PV
kubectl apply -f static-pv-etcd-certs.yaml
kubectl apply -f static-pv-etcd-backup.yaml# 创建静态 PVC(会自动绑定到同名 PV)
kubectl apply -f static-pvc-etcd-certs.yaml
kubectl apply -f static-pvc-etcd-backup.yaml# 验证状态(确保 STATUS 为 Bound)
root@k8s-master:~# kubectl get pv,pvc
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS VOLUMEATTRIBUTESCLASS REASON AGE
persistentvolume/static-pv-etcd-backup 10Gi RWX Retain Bound default/static-pvc-etcd-backup <unset> 2m17s
persistentvolume/static-pv-etcd-certs 1Gi ROX Retain Bound default/static-pvc-etcd-certs <unset> 2m17sNAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS VOLUMEATTRIBUTESCLASS AGE
persistentvolumeclaim/static-pvc-etcd-backup Bound static-pv-etcd-backup 10Gi RWX <unset> 2m16s
persistentvolumeclaim/static-pvc-etcd-certs Bound static-pv-etcd-certs 1Gi ROX <unset> 2m17s
关键说明:
- AccessModes:
- 证书目录用
ReadOnlyMany
(ROX):允许多个节点的 Pod 只读挂载(避免证书被意外修改); - 备份目录用
ReadWriteMany
(RWX):允许任意节点的 Pod 读写(CronJob 的 Pod 可能调度到不同节点)。
- 证书目录用
- StorageClass:必须指定为用户已有的
nfs-csi
,确保 PVC 能动态绑定 NFS 卷。
2.3 编写 Dockerfile
Dockerfile 仅定义容器内的etcdctl
工具和备份命令,与存储类型无关,直接复用之前的内容:
root@k8s-master:~/bak-etcd# cat Dockerfile
FROM alpine:latest
LABEL matainer="NovaCaoFc" \role="bak" \project="etcd"
COPY etcdctl /usr/local/bin/
CMD ["/bin/sh","-c","etcdctl --endpoints=${ETCD_HOST}:${ETCD_PORT} --cacert=/certs/ca.crt --cert=/certs/peer.crt --key=/certs/peer.key snapshot save /backup/etcd-`date +%F-%T`.backup"]
# Etcdctl可以去github下载他的二进制包,然后保留一下这个二进制命令即可。
# 官网地址: https://github.com/etcd-io/etcd/root@k8s-master:~/bak-etcd# ll
total 16096
drwxr-xr-x 2 root root 4096 Jul 30 16:37 ./
drwx------ 13 root root 4096 Jul 30 16:37 ../
-rw-r--r-- 1 root root 302 Jul 30 16:31 Dockerfile
-rwxr-xr-x 1 cao cao 16466072 Jul 26 02:17 etcdctl*
构建镜像
root@k8s-master:~/bak-etcd# docker build -t etcd-bak:v1 .
[+] Building 0.1s (7/7) FINISHED docker:default=> [internal] load build definition from Dockerfile 0.0s=> => transferring dockerfile: 353B 0.0s=> [internal] load metadata for docker.io/library/alpine:latest 0.0s=> [internal] load .dockerignore 0.0s=> => transferring context: 2B 0.0s=> [internal] load build context 0.0s=> => transferring context: 31B 0.0s=> [1/2] FROM docker.io/library/alpine:latest 0.0s=> CACHED [2/2] COPY etcdctl /usr/local/bin/ 0.0s=> exporting to image 0.0s=> => exporting layers 0.0s=> => writing image sha256:8a29a144172a91e01eb81d8e540fb785e9749058be1d6336871036e9fb781adb 0.0s=> => naming to docker.io/library/etcd-bak:v1 root@k8s-master:~/bak-etcd# docker images |grep bak
etcd-bak v1 8a29a144172a 7 minutes ago 24.8MB
root@k8s-master:~# docker save -o etcd-bak.tar etcd-bak:v1
root@k8s-master:~# scp etcd-bak.tar 10.0.0.7:/root/
etcd-bak.tar 100% 24MB 73.5MB/s 00:00
root@k8s-master:~# scp etcd-bak.tar 10.0.0.8:/root/
etcd-bak.tar # 其他节点导入镜像
root@k8s-node1:~# docker load -i etcd-bak.tar
418dccb7d85a: Loading layer [==================================================>] 8.596MB/8.596MB
39e2b60cb098: Loading layer [==================================================>] 16.47MB/16.47MB
Loaded image: etcd-bak:v1
在生产环境中,最好将构建好的镜像上传至我们的harbor仓库中,这样在下面的pod控制器中我们直接写入harbor地址就行了,不需要将镜像分配到其他K8S节点咯。
2.4 编写 CronJob 资源清单(适配 NFS 挂载)
操作步骤:
新建cj-backup-etcd-nfs.yaml
,内容如下:
root@k8s-master:~# cat cj-backup-etcd-nfs.yaml
apiVersion: batch/v1
kind: CronJob
metadata:name: backup-etcd
spec:schedule: "* * * * *" #先用每分钟测试是否可用jobTemplate:spec:template:spec:volumes:- name: certspersistentVolumeClaim:claimName: static-pvc-etcd-certs # 引用静态证书 PVC- name: bakpersistentVolumeClaim:claimName: static-pvc-etcd-backup # 引用静态备份 PVCcontainers:- name: etcd-backupimage: etcd-bak:v1imagePullPolicy: IfNotPresentvolumeMounts:- name: certsmountPath: /certsreadOnly: true- name: bakmountPath: /backupenv:- name: ETCD_HOSTvalue: "10.0.0.6" # 你的 etcd 节点 IP- name: ETCD_PORTvalue: "2379"restartPolicy: OnFailure
部署 CronJob:
root@k8s-master:~# kubectl apply -f cj-backup-etcd-nfs.yaml
cronjob.batch/backup-etcd created
2.5 测试验证:确认 NFS 备份生效
核心确认备份文件是否生成在 NFS 的etcd-backup
目录。
查看 CronJob 和 Pod 状态
root@k8s-master:~# kubectl get -f cj-backup-etcd-nfs.yaml
NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE
backup-etcd * * * * * <none> False 0 <none> 17s
root@k8s-master:~# kubectl get po
NAME READY STATUS RESTARTS AGE
backup-etcd-29231120-9s9dm 0/1 Completed 0 3m4s
backup-etcd-29231121-nfjl5 0/1 CrashLoopBackOff 1 (11s ago) 2m4s
backup-etcd-29231122-gl4df 0/1 Completed 1 64s
backup-etcd-29231123-tfw5f 0/1 Completed 0 4s
root@k8s-master:~# ll /data/nfs-server/etcd-backup/
total 50824
drwxr-xr-x 2 root root 4096 Jul 30 17:23 ./
drwxr-xr-x 8 root root 4096 Jul 30 16:21 ../
-rw------- 1 root root 13004832 Jul 30 17:22 etcd-2025-07-30-09:22:50.backup
-rw------- 1 root root 13004832 Jul 30 17:22 etcd-2025-07-30-09:22:53.backup
-rw------- 1 root root 13004832 Jul 30 17:23 etcd-2025-07-30-09:23:01.backup
三、数据恢复步骤
1. 恢复前的准备工作
确认备份文件有效性
首先检查备份文件是否完整可用,使用etcdctl
工具验证:
# 假设备份文件在NFS目录中,先复制到本地(如/master节点)
cp /data/nfs-server/etcd-backup/etcd-2025-07-30-09:22:50.backup /tmp/etcd-backup.db# 验证备份文件(需使用与集群版本匹配的etcdctl)
etcdctl --write-out=table snapshot status /tmp/etcd-backup.db
停止 Kubernetes 控制平面组件
恢复 etcd 数据时,需停止所有依赖 etcd 的控制平面组件(避免数据写入冲突):
# 在master节点执行(根据实际组件调整)
systemctl stop kubelet
docker stop $(docker ps -q --filter name=k8s_kube-apiserver*)
docker stop $(docker ps -q --filter name=k8s_kube-controller-manager*)
docker stop $(docker ps -q --filter name=k8s_kube-scheduler*)
docker stop $(docker ps -q --filter name=k8s_etcd*)
2. 执行恢复操作
多节点 etcd 集群恢复(适用于生产环境)
如果 etcd 是集群部署(3 节点),需要在所有节点执行恢复,确保集群信息一致:
在所有 etcd 节点备份原有数据:
mv /var/lib/etcd /var/lib/etcd.bak
在第一个节点执行恢复:
etcdctl snapshot restore /tmp/etcd-backup.db \--data-dir=/var/lib/etcd \--name=etcd-1 \ # 节点名称,如etcd-1/etcd-2/etcd-3--initial-cluster=etcd-1=https://10.0.0.6:2380,etcd-2=https://10.0.0.7:2380,etcd-3=https://10.0.0.8:2380 \--initial-cluster-token=etcd-cluster-token \--initial-advertise-peer-urls=https://10.0.0.6:2380 # 当前节点的peer地址
在其他节点执行恢复(修改
--name
和--initial-advertise-peer-urls
为对应节点信息):
# 第二个节点示例
etcdctl snapshot restore /tmp/etcd-backup.db \--data-dir=/var/lib/etcd \--name=etcd-2 \--initial-cluster=etcd-1=https://10.0.0.6:2380,etcd-2=https://10.0.0.7:2380,etcd-3=https://10.0.0.8:2380 \--initial-cluster-token=etcd-cluster-token \--initial-advertise-peer-urls=https://10.0.0.7:2380
3 . 恢复后验证与启动服务
修复目录权限
恢复后需确保 etcd 数据目录权限正确(否则可能启动失败):
chown -R 1000:1000 /var/lib/etcd # etcd默认使用1000用户运行
启动控制平面组件
systemctl start kubelet
# 等待容器自动重启,或手动启动
docker start $(docker ps -aq --filter name=k8s_etcd*)
docker start $(docker ps -aq --filter name=k8s_kube-apiserver*)
docker start $(docker ps -aq --filter name=k8s_kube-controller-manager*)
docker start $(docker ps -aq --filter name=k8s_kube-scheduler*)
验证集群状态
确认恢复成功:
# 查看节点状态
kubectl get nodes# 查看Pod状态
kubectl get pods --all-namespaces# 检查etcd集群健康状态
etcdctl --endpoints=https://127.0.0.1:2379 \--cacert=/etc/kubernetes/pki/etcd/ca.crt \--cert=/etc/kubernetes/pki/etcd/peer.crt \--key=/etc/kubernetes/pki/etcd/peer.key \endpoint health
4. 注意事项
- 恢复会覆盖现有数据:恢复操作会清空当前 etcd 数据,替换为备份文件中的内容,执行前务必确认备份文件是最新且正确的。
- 版本兼容性:
etcdctl
版本必须与 etcd 集群版本完全一致(如 v3.5.0 的 etcd 需用 v3.5.0 的 etcdctl),否则可能导致恢复失败。 - 生产环境建议:
- 恢复前备份当前 etcd 数据(
etcdctl snapshot save
),避免操作失误无法回滚; - 恢复过程会导致集群短暂不可用,建议在业务低峰期执行;
- 多节点 etcd 集群恢复后,需确认所有节点数据同步正常(
etcdctl member list
)。
- 恢复前备份当前 etcd 数据(
通过以上步骤,即可利用备份的 etcd 快照文件恢复 Kubernetes 集群数据,确保在数据异常时能够快速恢复业务。