目录
错误一:Error: snapshot restore requires exactly one argument
环境说明:
k8s三主三从高可用集群。
主机名 | IP地址 | 说明 |
---|---|---|
master1 | 192.168.48.11 | master节点 |
master2 | 192.168.48.12 | master节点 |
master3 | 192.168.48.13 | master节点 |
node01 | 192.168.48.14 | node节点 |
node02 | 192.168.48.15 | node节点 |
node02 | 192.168.48.16 | node节点 |
database | 192.168.48.19 | harbor仓库 |
192.168.48.10 | VIP(虚拟IP) |
安装配置信息如下表所示:
配置信息 | 备注 |
---|---|
OS系统版本 | openEuler-24.03 |
Docker版本 | 28.3.2 |
etcdctl版本 | 3.5.20 |
Kubernetes版本 | 1.32.7 |
一、命令行备份
1. etcd备份
确保所有master节点etcdctl命令可用。
master1执行:
scp -r /usr/local/bin/etcdctl root@192.168.48.102:/usr/local/bin/ scp -r /usr/local/bin/etcdctl root@192.168.48.103:/usr/local/bin/
所有节点执行:
cd ~ sudo mkdir -p /etcd/backup/ sudo mkdir -p /etc/kubernetes/manifests.bak #etcd备份的关键命令 ETCDCTL_API=3 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 snapshot save /etcd/backup/etcdbackup.db ETCDCTL_API=3 etcdctl --write-out=table snapshot status /etcd/backup/etcdbackup.db
2. 数据恢复
停止etcd服务和K8s集群的相关组件 在恢复之前,需要停止etcd服务和K8s集群的相关组件(如apiserver、controller-manager、scheduler等)。由于etcd是通过静态Pod方式部署的,你可以通过重命名/etc/kubernetes/manifests/目录来停止所有由该目录下的YAML文件启动的服务。
mv /etc/kubernetes/manifests/* /etc/kubernetes/manifests.bak
物理备份etcd:
这是为了避免我们的备份出错导致集群不可逆损伤。
mv /var/lib/etcd /var/lib/etcd.bck
etcd数据恢复关键命令:
ETCDCTL_API=3 etcdctl snapshot restore /etcd/backup/etcdbackup.db --name master1 --data-dir /var/lib/etcd --initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380" --initial-cluster-token etcd-cluster-token --initial-advertise-peer-urls "https://192.168.48.11:2380"
我们来详细分解并解释这条完整的 etcd 恢复命令中每一个参数的意义和作用。
这条命令的核心目的是:使用一个备份的快照文件,在当前服务器上创建一个全新的 etcd 数据目录,并为加入一个新的 etcd 集群做好初始化配置。
2.1. 参数详解
ETCDCTL_API=3
类型:环境变量
含义:指定使用 etcdctl 工具的 v3 API。
为什么需要:etcdctl 为了兼容旧版本,需要明确指定使用哪个版本的 API。快照(snapshot)功能是 v3 API 引入的,所以必须设置为 3。
etcdctl snapshot restore
类型:主命令
含义:这是 etcdctl 工具的子命令,用于从快照文件执行恢复操作。
/etcd/backup/etcdbackup.db
类型:位置参数
含义:源快照文件的路径。这是你之前通过
etcdctl snapshot save
命令创建的备份文件的位置。说明:执行恢复操作前,必须确保这个文件存在于该路径下。
--name master1
类型:关键配置参数
含义:指定当前节点在即将创建的新集群中的成员名称。
说明:这个名称必须与后面
--initial-cluster
参数列表中对应本节点 IP 的那个成员的名称一致。这里告诉系统:“我这台机器,在新集群里的名字叫master1
。”
--data-dir /var/lib/etcd
类型:关键配置参数
含义:指定恢复操作生成的全新数据目录的路径。
说明:恢复过程会创建一个全新的 etcd 数据目录。重要提示:你不能覆盖当前正在运行的 etcd 实例的数据目录。通常,你会先停止 etcd 服务,删除或移走旧的
/var/lib/etcd
目录,然后执行此命令来生成一个全新的、包含备份数据的数据目录。
--initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380"
类型:最关键的集群配置参数
含义:定义新集群的初始成员列表。
格式:
<成员名称1>=<peer-url1>, <成员名称2>=<peer-url2>, ...
详细解释:
master1=https://192.168.48.11:2380
:名为master1
的节点,其对等通信地址(Peer URL) 是https://192.168.48.11:2380
。master2=...
和master3=...
:定义了集群的另外两个节点。端口 2380:这是 etcd 节点间内部通信的专用端口(用于领导选举、数据同步、心跳等)。
为什么需要:这个参数告诉正在恢复的节点:“你将要加入的这个新集群,总共有这三个成员,这是他们的名字和联系方式。”
--initial-cluster-token etcd-cluster-token
类型:安全配置参数
含义:为新集群设置一个唯一的集群令牌。
为什么需要:这个令牌用于防止新集群在初始启动时与网络中其他无关的 etcd 集群意外连接并形成同一个集群。执行恢复时,必须提供一个新的 token,因为这相当于创建一个全新的集群,而不是加入旧的。
--initial-advertise-peer-urls "https://192.168.48.11:2380"
类型:关键网络配置参数
含义:指定本节点在新集群中对外宣告的对等通信地址(Peer URL)。
详细解释:
对等通信地址用于 etcd 集群成员节点之间的通信(如心跳、数据同步、领导者选举)。
这个地址必须能被集群中其他所有节点访问到。
关键一致性:这个地址必须出现在上面的
--initial-cluster
参数列表中,与--name
参数指定的名称相对应。在这个命令中,--name
是master1
,所以在--initial-cluster
列表中找master1
,它的 URL 也必须是https://192.168.48.11:2380
。两者必须完全匹配,否则集群无法形成。
这条命令在 master1
节点上执行后,会:
读取快照文件
/etcd/backup/etcdbackup.db
中的数据。在
/var/lib/etcd
目录下创建一个全新的数据结构。将快照中的数据恢复到新目录中。
丢弃旧集群的所有成员信息和网络配置。
根据你提供的参数,生成新集群的初始化配置:
集群名叫
etcd-cluster-token
。集群有三个节点:master1, master2, master3。
本节点(master1)告诉其他节点:“想和我进行内部管理通信,请用
https://192.168.48.11:2380
这个地址找我。”
执行流程:
在
master1
(IP: 192.168.48.11) 上执行上面的命令。在
master2
(IP: 192.168.48.12) 上执行类似的命令,只需修改--name
和--initial-advertise-peer-urls
:ETCDCTL_API=3 etcdctl snapshot restore /etcd/backup/etcdbackup.db --name master2 --data-dir /var/lib/etcd --initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380" --initial-cluster-token etcd-cluster-token --initial-advertise-peer-urls "https://192.168.48.12:2380"
在
master3
(IP: 192.168.48.103) 上执行:ETCDCTL_API=3 etcdctl snapshot restore /etcd/backup/etcdbackup.db --name master3 --data-dir /var/lib/etcd --initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380" --initial-cluster-token etcd-cluster-token --initial-advertise-peer-urls "https://192.168.48.13:2380"
所有节点执行:
mv /etc/kubernetes/manifests.bak/* /etc/kubernetes/manifests/ systemctl restart kubelet.service
到此,集群etcd恢复正常。
3. 总结与流程梳理
报错解决:
错误一:Error: snapshot restore requires exactly one argument
原因:这个错误是因为你的命令格式不正确。你在 --name
参数后面直接换行了,导致 etcdctl 认为 master1
是一个独立的参数,而不是 --name
的值。
错误写法(换行破坏了参数结构):
... --name master1 --data-dir ...
这被解析为:--name
(没有值) + 参数 master1
+ --data-dir
...
正确写法(确保参数和它的值在同一行,或者用 \
转义换行符):
... --name master1 --data-dir ...
或者
... --name \ master1 --data-dir ...
错误二:-bash: etcdutl: 未找到命令
原因:你的系统上安装的 etcd 版本可能比较旧(可能是 3.4 之前),还没有提供 etcdutl
这个命令。etcdutl
和 etcdctl
的分拆是较新版本才引入的。
解决方案
使用旧的、但肯定可用的 etcdctl
命令,并确保正确的格式。
请在你的 master1
节点上执行以下完整的一行命令(不要换行):
ETCDCTL_API=3 etcdctl snapshot restore /etcd/backup/etcdbackup.db --name master1 --data-dir /var/lib/etcd --initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380" --initial-cluster-token etcd-cluster-token --initial-advertise-peer-urls "https://192.168.48.11:2380"
我做的改进:
移除了错误的换行:确保
--name master1
是一个完整的单元。给URL列表加上了引号:使用引号
"..."
将包含逗号的--initial-cluster
参数值括起来,这是一个好习惯,可以避免 shell 解析错误。
执行流程:
在
master1
(IP: 192.168.48.11) 上执行上面的命令。在
master2
(IP: 192.168.48.12) 上执行类似的命令,只需修改--name
和--initial-advertise-peer-urls
:ETCDCTL_API=3 etcdctl snapshot restore /etcd/backup/etcdbackup.db --name master2 --data-dir /var/lib/etcd --initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380" --initial-cluster-token etcd-cluster-token --initial-advertise-peer-urls "https://192.168.48.12:2380"
在
master3
(IP: 192.168.48.13) 上执行:ETCDCTL_API=3 etcdctl snapshot restore /etcd/backup/etcdbackup.db --name master3 --data-dir /var/lib/etcd --initial-cluster "master1=https://192.168.48.11:2380,master2=https://192.168.48.12:2380,master3=https://192.168.48.13:2380" --initial-cluster-token etcd-cluster-token --initial-advertise-peer-urls "https://192.168.48.13:2380"
总结:忽略 etcdutl
,坚持使用 etcdctl
,并确保命令的格式正确(参数和值不被人为换行断开)。
二、K8S自动备份
不管是使用命令备份还是脚本备份,亦或是加入cronjob,我们都不能保证备份的时间节点下k8s集群正常。换句话说,我们有可能备份了故障的k8s集群,那如何解决这个问题呢?
答案是将备份任务交给K8S集群本身来做,这样,K8S只有在健康状态下才会执行备份任务。
1. 构建备份工具镜像
1.1. 镜像文件准备
cd ~ mkdir etcd-backup && cd etcd-backup #可能是其他路径,下面以我的为例。 cp -p /usr/local/bin/etcdctl . cp -p /usr/bin/kubectl .
1.2. 编写备份环境初始化脚本
所有master节点都要做。
cat > init-etcd-backup.sh << 'EOF' #!/usr/bin/env bash set -euo pipefail # Create required hostPath directories REQUIRED_DIRS=( "/data/etcd/backups" #备份存储的目录 ) echo "[1/2] Ensuring required directories exist..." for d in "${REQUIRED_DIRS[@]}"; do if [ ! -d "$d" ]; then echo "Creating: $d" mkdir -p "$d" else echo "Exists: $d" fi chown root:root "$d" chmod 755 "$d" done echo "[2/2] Labeling control-plane nodes for etcd backup..." # Replace these names if your masters are named differently kubectl label node master1 etcd-backup=master1 --overwrite kubectl label node master2 etcd-backup=master2 --overwrite kubectl label node master3 etcd-backup=master3 --overwrite echo -e "\e[32mInitialization complete.\e[0m" EOF
执行这个脚本
所有master节点都要做。
chmod +x init-etcd-backup.sh sh init-etcd-backup.sh
1.3. 编写Dockerfile
FROM ubuntu:22.04 # 安装常用工具(不安装 etcd-client,避免与自带 etcdctl 冲突) RUN apt-get update && \ apt-get install -y curl wget vim unzip tar net-tools && \ rm -rf /var/lib/apt/lists/* # 如需要 kubectl,请保留;若不需要可删除以下两行以减小镜像 COPY kubectl /bin RUN chmod +x /bin/kubectl # 固定使用随项目提供的 etcdctl 二进制 COPY etcdctl /bin RUN chmod +x /bin/etcdctl
1.4. 构建镜像
docker build -t etcd-backup:latest .
1.5. 上传镜像
打标签,推送到harbor仓库。
docker tag docker.io/library/etcd-backup:latest 192.168.48.19/etcd-backup/etcd-backup:latest docker push 192.168.48.19/etcd-backup/etcd-backup:latest
2. 编写ConfigMap
vim etcd-backup-config.yaml
apiVersion: v1 kind: ConfigMap metadata: name: etcd-backup-config namespace: kube-system data: backup_etcd.sh: | #!/bin/bash set -euo pipefail # 通过本机网络访问 etcd(CronJob 使用 hostNetwork) ENDPOINT="https://127.0.0.1:2379" # 证书路径(kubeadm 默认路径) ETCD_CA="/etc/kubernetes/pki/etcd/ca.crt" ETCD_CERT="/etc/kubernetes/pki/apiserver-etcd-client.crt" ETCD_KEY="/etc/kubernetes/pki/apiserver-etcd-client.key" # 日期 current_date=$(date +"%Y_%m_%d_%H_%M_%S") # 备份目录 mkdir -p /etcd/backup/ chmod 755 /etcd/backup/ SNAPSHOT="/etcd/backup/etcd-127.0.0.1-${current_date}.db" echo "开始备份到: ${SNAPSHOT}" etcdctl --endpoints="${ENDPOINT}" \ --cacert="${ETCD_CA}" \ --cert="${ETCD_CERT}" \ --key="${ETCD_KEY}" \ snapshot save "${SNAPSHOT}" echo "备份完成: ${SNAPSHOT}" # 清理超过3天的 .db 文件 find /etcd/backup/ -type f -name '*.db' -mtime +3 -delete echo "清理完成,仅保留最近三天的快照"
3. 编写CronJob
由于我的是高可用k8s集群,我把master节点的内置etcd全备份了。理论上只要备份一个master节点的etcd就行,但是为了保险起见,我把所有maser节点的etcd都做了备份,所以我写了3个cronjob。内容都是差不多的,关键在于备份的时间要错开,避免并发。
master1备份的cronjob
vim etcd-backup-cronjob-master1.yaml
apiVersion: batch/v1 kind: CronJob metadata: name: etcd-backup-master1 namespace: kube-system spec: schedule: "0 23 * * *" # 每日23:00(UTC) concurrencyPolicy: Forbid # 避免并发 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: hostNetwork: true dnsPolicy: ClusterFirstWithHostNet nodeSelector: etcd-backup: "master1" # 绑定第1个master tolerations: - key: "node-role.kubernetes.io/control-plane" operator: "Exists" effect: "NoSchedule" - key: "node-role.kubernetes.io/master" operator: "Exists" effect: "NoSchedule" containers: - name: etcd-backup image: 192.168.48.19/etcd-backup/etcd-backup:latest imagePullPolicy: IfNotPresent securityContext: runAsUser: 0 runAsNonRoot: false readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: ["ALL"] resources: requests: cpu: "50m" memory: "128Mi" limits: cpu: "500m" memory: "1Gi" env: - name: ETCDCTL_API value: "3" command: ['bash', '-c'] args: - | bash /usr/local/bin/backup_etcd.sh volumeMounts: - mountPath: /etc/localtime name: localtime readOnly: true - mountPath: /etc/kubernetes/pki name: kube-pki readOnly: true - mountPath: /etcd/backup # 改为脚本用的目录 name: etcd-bak - mountPath: /usr/local/bin/backup_etcd.sh name: backup-script subPath: backup_etcd.sh readOnly: true volumes: - hostPath: path: /etc/localtime type: '' name: localtime - hostPath: path: /etc/kubernetes/pki type: '' name: kube-pki - hostPath: path: /data/etcd/backups # 宿主机保存目录 type: '' name: etcd-bak - configMap: name: etcd-backup-config name: backup-script restartPolicy: OnFailure
master2备份的cronjob
vim etcd-backup-cronjob-master2.yaml
apiVersion: batch/v1 kind: CronJob metadata: name: etcd-backup-master2 namespace: kube-system spec: schedule: "5 23 * * *" # 每日23:05(UTC) concurrencyPolicy: Forbid # 避免并发 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: hostNetwork: true dnsPolicy: ClusterFirstWithHostNet nodeSelector: etcd-backup: "master2" # 绑定第2个master tolerations: - key: "node-role.kubernetes.io/control-plane" operator: "Exists" effect: "NoSchedule" - key: "node-role.kubernetes.io/master" operator: "Exists" effect: "NoSchedule" containers: - name: etcd-backup image: 192.168.48.19/etcd-backup/etcd-backup:latest imagePullPolicy: IfNotPresent securityContext: runAsUser: 0 runAsNonRoot: false readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: ["ALL"] resources: requests: cpu: "50m" memory: "128Mi" limits: cpu: "500m" memory: "1Gi" env: - name: ETCDCTL_API value: "3" command: ['bash', '-c'] args: - | bash /usr/local/bin/backup_etcd.sh volumeMounts: - mountPath: /etc/localtime name: localtime readOnly: true - mountPath: /etc/kubernetes/pki name: kube-pki readOnly: true - mountPath: /etcd/backup # 改为脚本用的目录 name: etcd-bak - mountPath: /usr/local/bin/backup_etcd.sh name: backup-script subPath: backup_etcd.sh readOnly: true volumes: - hostPath: path: /etc/localtime type: '' name: localtime - hostPath: path: /etc/kubernetes/pki type: '' name: kube-pki - hostPath: path: /data/etcd/backups # 宿主机保存目录 type: '' name: etcd-bak - configMap: name: etcd-backup-config name: backup-script restartPolicy: OnFailure
master3备份的cronjob
vim etcd-backup-cronjob-master3.yaml
apiVersion: batch/v1 kind: CronJob metadata: name: etcd-backup-master3 namespace: kube-system spec: schedule: "10 23 * * *" # 每日23:10(UTC) concurrencyPolicy: Forbid # 避免并发 successfulJobsHistoryLimit: 3 failedJobsHistoryLimit: 1 jobTemplate: spec: template: spec: hostNetwork: true dnsPolicy: ClusterFirstWithHostNet nodeSelector: etcd-backup: "master3" # 绑定第3个master tolerations: - key: "node-role.kubernetes.io/control-plane" operator: "Exists" effect: "NoSchedule" - key: "node-role.kubernetes.io/master" operator: "Exists" effect: "NoSchedule" containers: - name: etcd-backup image: 192.168.48.19/etcd-backup/etcd-backup:latest imagePullPolicy: IfNotPresent securityContext: runAsUser: 0 runAsNonRoot: false readOnlyRootFilesystem: true allowPrivilegeEscalation: false capabilities: drop: ["ALL"] resources: requests: cpu: "50m" memory: "128Mi" limits: cpu: "500m" memory: "1Gi" env: - name: ETCDCTL_API value: "3" command: ['bash', '-c'] args: - | bash /usr/local/bin/backup_etcd.sh volumeMounts: - mountPath: /etc/localtime name: localtime readOnly: true - mountPath: /etc/kubernetes/pki name: kube-pki readOnly: true - mountPath: /etcd/backup # 改为脚本用的目录 name: etcd-bak - mountPath: /usr/local/bin/backup_etcd.sh name: backup-script subPath: backup_etcd.sh readOnly: true volumes: - hostPath: path: /etc/localtime type: '' name: localtime - hostPath: path: /etc/kubernetes/pki type: '' name: kube-pki - hostPath: path: /data/etcd/backups # 宿主机保存目录 type: '' name: etcd-bak - configMap: name: etcd-backup-config name: backup-script restartPolicy: OnFailure
现在目录下包含以下文件:
[root@master1 etcd-backup]# ls Dockerfile etcd-backup-cronjob-master2.yaml init-etcd-backup.sh etcd-backup-config.yaml etcd-backup-cronjob-master3.yaml kubectl etcd-backup-cronjob-master1.yaml etcdctl
4. 开启备份任务
[root@master1 etcd-backup]# kubectl apply -f ./ configmap/etcd-backup-config created cronjob.batch/etcd-backup-master1 created cronjob.batch/etcd-backup-master2 created cronjob.batch/etcd-backup-master3 created
5. 查看cronjob状态
[root@master1 etcd-backup]# kubectl get cronjobs.batch -n kube-system NAME SCHEDULE TIMEZONE SUSPEND ACTIVE LAST SCHEDULE AGE etcd-backup-master1 0 20 * * * <none> False 0 <none> 59s etcd-backup-master2 5 20 * * * <none> False 0 <none> 59s etcd-backup-master3 10 20 * * * <none> False 0 <none> 59s
我们可以马上执行这三个cronjob看看效果。
kubectl create job --from=cronjob/etcd-backup-master1 etcd-backup-manual-$(date +%s) -n kube-system kubectl create job --from=cronjob/etcd-backup-master2 etcd-backup-manual-$(date +%s) -n kube-system kubectl create job --from=cronjob/etcd-backup-master3 etcd-backup-manual-$(date +%s) -n kube-system
查看是否备份完毕。
[root@master1 etcd-backup]# kubectl get pod -n kube-system | grep etcd-backup-manual etcd-backup-manual-1756482367-5hprb 0/1 Completed 0 29s etcd-backup-manual-1756482375-7rgmv 0/1 Completed 0 21s etcd-backup-manual-1756482381-smn2l 0/1 Completed 0 15s
显示Completed,说明备份成功了,到备份目录查看。
[root@master1 etcd-backup]# cd /data/etcd/backups/ [root@master1 backups]# ls etcd-127.0.0.1-2025_08_26_19_40_08.db etcd-127.0.0.1-2025_08_29_22_56_33.db etcd-127.0.0.1-2025_08_27_09_51_40.db etcd-127.0.0.1-2025_08_29_23_46_08.db
可以看到保留了三天以内的(其他两天是我之前备份的),和我们预想的一样。
6. 检查备份
在master节点查看备份是否可用
[root@master1 backups]# ETCDCTL_API=3 etcdctl --write-out=table snapshot status /data/etcd/backups/etcd-127.0.0.1-2025_08_29_23_46_08.db Deprecated: Use `etcdutl snapshot status` instead. +----------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +----------+----------+------------+------------+ | e30f1e5f | 1172570 | 2219 | 19 MB | +----------+----------+------------+------------+ [root@master2 ~]# ETCDCTL_API=3 etcdctl --write-out=table snapshot status /data/etcd/backups/etcd-127.0.0.1-2025_08_29_23_46_15.db Deprecated: Use `etcdutl snapshot status` instead. +----------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +----------+----------+------------+------------+ | cde97ef5 | 1172616 | 2267 | 20 MB | +----------+----------+------------+------------+ [root@master3 ~]# ETCDCTL_API=3 etcdctl --write-out=table snapshot status /data/etcd/backups/etcd-127.0.0.1-2025_08_29_23_46_21.db Deprecated: Use `etcdutl snapshot status` instead. +----------+----------+------------+------------+ | HASH | REVISION | TOTAL KEYS | TOTAL SIZE | +----------+----------+------------+------------+ | bbc38825 | 1172663 | 2311 | 18 MB | +----------+----------+------------+------------+
到此,k8s自动备份就完成了。恢复方式和前文一样,这里就不再演示了。