etcdctl —— 直连 K8s 元数据存储
一句话定义
etcdctl 是 etcd(K8s 的元数据存储)的命令行客户端。K8s 集群里所有"状态"——Node、Pod、Deployment、Secret、ConfigMap、Lease——全部存在 etcd 里。etcdctl 让你绕开 apiserver、直接看/改 etcd。
⚠️ 生产 etcd 不要
put/del。除非你完全清楚自己在干嘛,否则只用它做:snapshot 备份、health 检查、get 看键值(只读)。
典型场景
- 升级 / 故障演练前:
etcdctl snapshot save备份 - 控制面失联时:
etcdctl endpoint health看哪个 etcd 节点挂了 - 排查"K8s 状态错乱":
etcdctl get /registry/...直接看 K8s 元数据 - 集群恢复:
etcdctl snapshot restore从备份重建
etcdctl 的 v3 vs v2
export ETCDCTL_API=3 # **K8s 用的是 v3,永远设这个**
v2 已经废弃。每次跑 etcdctl 之前确认环境变量在:
echo $ETCDCTL_API # 应该是 3
或者写到 .bashrc。
TLS 参数地狱
kubeadm 装的 etcd 默认开启 mTLS(双向证书认证)。每条 etcdctl 命令都要带 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 \
endpoint health
强烈建议把这堆参数写成 shell function 或 alias:
# ~/.bashrc 或 /root/.bashrc
e() {
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 \
"$@"
}
之后:
e endpoint health
e endpoint status -w table
e snapshot save /backup/etcd.db
或者用环境变量:
export ETCDCTL_API=3
export ETCDCTL_ENDPOINTS=https://127.0.0.1:2379
export ETCDCTL_CACERT=/etc/kubernetes/pki/etcd/ca.crt
export ETCDCTL_CERT=/etc/kubernetes/pki/etcd/server.crt
export ETCDCTL_KEY=/etc/kubernetes/pki/etcd/server.key
之后直接 etcdctl endpoint health。
怎么知道 endpoint 和证书路径
kubeadm 装的:
endpoints: https://<节点 IP>:2379 (每个 cp 节点一个)
cacert: /etc/kubernetes/pki/etcd/ca.crt
cert: /etc/kubernetes/pki/etcd/server.crt (或 healthcheck-client.crt)
key: /etc/kubernetes/pki/etcd/server.key (或 healthcheck-client.key)
确认:
ls /etc/kubernetes/pki/etcd/
# ca.crt ca.key healthcheck-client.crt healthcheck-client.key peer.crt peer.key server.crt server.key
也可以看 etcd 静态 pod 配置:
grep -E "listen-client|trusted-ca|cert-file|key-file" /etc/kubernetes/manifests/etcd.yaml
健康检查(最常用 3 条)
endpoint health
etcdctl endpoint health
# https://127.0.0.1:2379 is healthy: successfully committed proposal: took = 5.012345ms
或者对所有 etcd 节点:
etcdctl \
--endpoints=https://10.0.24.28:2379,https://10.0.24.29:2379,https://10.0.24.30:2379 \
endpoint health -w table
# +--------------------------+--------+-------------+-------+
# | ENDPOINT | HEALTH | TOOK | ERROR |
# +--------------------------+--------+-------------+-------+
# | https://10.0.24.28:2379 | true | 5.012345ms | |
# | https://10.0.24.29:2379 | true | 4.876543ms | |
# | https://10.0.24.30:2379 | true | 5.234567ms | |
# +--------------------------+--------+-------------+-------+
endpoint status
etcdctl endpoint status -w table
# 看每个节点的: leader、raft term、raft index、db size
输出例子:
+-------+----+---------+---------+--------+-----------+-----------+
| ENDPT | ID | VERSION | DB SIZE | LEADER | RAFT TERM | RAFT INDEX|
+-------+----+---------+---------+--------+-----------+-----------+
| ... | ...| 3.5.10 | 50 MB | true | 5 | 12345 |
| ... | ...| 3.5.10 | 50 MB | false | 5 | 12345 |
| ... | ...| 3.5.10 | 50 MB | false | 5 | 12345 |
+-------+----+---------+---------+--------+-----------+-----------+
健康集群所有节点 raft term / index 应该相同(或最多差一两个)。差太多说明某节点落后。
member list
etcdctl member list -w table
# +------------------+---------+--------+--------------------------+
# | ID | STATUS | NAME | PEER ADDRS |
# +------------------+---------+--------+--------------------------+
# | abc... | started | m1 | https://10.0.24.28:2380 |
# | def... | started | m2 | https://10.0.24.29:2380 |
# | ghi... | started | m3 | https://10.0.24.30:2380 |
# +------------------+---------+--------+--------------------------+
排查"etcd 集群少了一个节点":member list 给你权威答案。
备份 / 恢复(面试 + CKA 必考)
备份 snapshot save
etcdctl snapshot save /backup/etcd-$(date +%F).db
# Snapshot saved at /backup/etcd-2026-05-27.db
# 验证
etcdctl --write-out=table snapshot status /backup/etcd-2026-05-27.db
# +----------+----------+------------+------------+
# | HASH | REVISION | TOTAL KEYS | TOTAL SIZE |
# +----------+----------+------------+------------+
# | abcdef12 | 12345 | 5678 | 50 MB |
# +----------+----------+------------+------------+
生产强烈建议每天 cron 自动备份:
cat > /etc/cron.d/etcd-backup <<'EOF'
0 2 * * * root /usr/local/bin/etcd-backup.sh
EOF
cat > /usr/local/bin/etcd-backup.sh <<'EOF'
#!/bin/bash
set -euo pipefail
DEST=/backup/etcd
mkdir -p "$DEST"
TS=$(date +%F-%H%M)
ETCDCTL_API=3 etcdctl snapshot save "$DEST/etcd-$TS.db" \
--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_API=3 etcdctl --write-out=table snapshot status "$DEST/etcd-$TS.db"
# 保留 14 天
find "$DEST" -name 'etcd-*.db' -mtime +14 -delete
EOF
chmod +x /usr/local/bin/etcd-backup.sh
恢复 snapshot restore
⚠️ 整个 K8s 控制面要停才能 restore。这是个核选项。
完整流程(CKA 演练):
# 1. 停 kubelet(避免它跟着重启 etcd)
systemctl stop kubelet
# 2. 停所有 etcd pod —— 把 manifest 挪走
mkdir -p /tmp/manifests-backup
mv /etc/kubernetes/manifests/etcd.yaml /tmp/manifests-backup/
# 3. 确认 etcd 容器已经停
crictl ps | grep etcd
# 应该没有
# 4. 备份当前 etcd 数据目录
mv /var/lib/etcd /var/lib/etcd.broken-$(date +%s)
# 5. 从 snapshot 还原
etcdctl snapshot restore /backup/etcd-2026-05-27.db \
--data-dir=/var/lib/etcd \
--name=m1 \ # ← 当前节点的 etcd member name
--initial-cluster=m1=https://10.0.24.28:2380,m2=https://10.0.24.29:2380,m3=https://10.0.24.30:2380 \
--initial-advertise-peer-urls=https://10.0.24.28:2380
# 6. 改 etcd 数据目录权限(kubeadm 默认 etcd 用户)
chown -R root:root /var/lib/etcd # kubeadm 用 root,纯 etcd 用 etcd:etcd
# 7. 恢复 manifest
mv /tmp/manifests-backup/etcd.yaml /etc/kubernetes/manifests/
# 8. 启 kubelet
systemctl start kubelet
sleep 30
# 9. 验证
crictl ps | grep etcd # etcd 应该 Running
etcdctl endpoint health # healthy
kubectl get nodes # 节点恢复
HA 集群(3 etcd 节点)restore 更麻烦:每个节点都要单独 restore、cluster ID 一致。Day12 演练。
看键值(只读)
K8s 把所有资源存在 /registry/... 前缀下:
etcdctl get / --prefix --keys-only | head
# /registry/apiregistration.k8s.io/apiservices/v1.
# /registry/apiregistration.k8s.io/apiservices/v1.apps
# /registry/clusterrolebindings/cluster-admin
# /registry/configmaps/default/kube-root-ca.crt
# /registry/deployments/default/nginx
# ...
# 看某个 pod 的元数据
etcdctl get /registry/pods/default/nginx-abc
# 输出是 protobuf 编码的,配 auger 解码
auger decode < <(etcdctl get /registry/pods/default/nginx-abc --print-value-only)
生产几乎不需要直接看 etcd——kubectl 已经够。这里是给"apiserver 挂了但想看状态"的极端情况。
数键值
etcdctl get / --prefix --keys-only | wc -l # 总数
etcdctl get /registry/pods/ --prefix --keys-only | wc -l # pod 数
etcdctl get /registry/secrets/ --prefix --keys-only | wc -l
K8s 集群里 secret / pod 数量异常大时(被插件污染),这种粗略统计能帮你找问题。
性能与维护
看 db 大小
etcdctl endpoint status -w table | awk -F'|' '{print $2, $5}'
# 看 DB SIZE 列
etcd db 默认上限 2GB(--quota-backend-bytes)。K8s 集群正常应该在 100-500MB。db 涨到 GB 级 = 出问题:
- 死循环创建资源(Operator bug)
- Secret 太多 / 太大
- Event 没清理
Compaction(清理历史 revision)
etcdctl compact <revision>
K8s 1.5+ 默认 5 分钟自动 compaction。一般不用手动。
Defrag(碎片整理)
etcdctl defrag # 单节点
etcdctl defrag --cluster # 全集群(**会有几秒不可用**)
compaction 删了数据但没释放磁盘空间——要 defrag 才行。db 涨大、磁盘紧张时用。
生产 defrag 要轮转:一个节点一个节点做,避免同时不可用。
训练营常用速查
Day 1:刚装完看 etcd 健康
e endpoint health
e member list -w table
Day 2:练备份恢复
e snapshot save /tmp/backup.db
e snapshot status /tmp/backup.db
# 然后模拟故障 → restore
Day 12:灾难恢复演练
模拟某 etcd 节点磁盘损坏:
# 1. 停某 etcd 节点
ssh m2 'mv /etc/kubernetes/manifests/etcd.yaml /tmp/'
ssh m2 'rm -rf /var/lib/etcd'
# 2. cluster 应该还能读写(3 节点容忍 1 挂)
e endpoint health # 在 m1 / m3 上跑应该 OK
# 3. 用 member remove 把它踢出 cluster
e member remove <m2-id>
# 4. 用 add 再把它加回
e member add m2 --peer-urls=https://10.0.24.29:2380
# 5. m2 用 --initial-cluster-state=existing 启动
# ... 复杂的,详见 Day12
常见踩坑
坑 1:error: context deadline exceeded
etcdctl endpoint health
# Error: context deadline exceeded
通常是:
- endpoint URL 错 / 端口错(不是 2379 / 2380 混淆)
- 证书错 / 不匹配
- etcd 真挂了
诊断:
# 1. 端口能通吗
nc -zv 127.0.0.1 2379
# 2. cert 对吗
openssl s_client -connect 127.0.0.1:2379 -CAfile /etc/kubernetes/pki/etcd/ca.crt < /dev/null
# 看到 "Verify return code: 0 (ok)" 说明 cert 链对
# 3. etcd 进程还在吗
crictl ps | grep etcd
ps aux | grep etcd
坑 2:忘了 ETCDCTL_API=3
etcdctl endpoint health
# Error: unknown command "endpoint" for "etcdctl"
v2 没有 endpoint 子命令。export ETCDCTL_API=3。
坑 3:用 server.crt 不行,要用 healthcheck-client.crt
某些 kubeadm 版本里:
server.crt/server.key—— 是 etcd 服务端用的healthcheck-client.crt/healthcheck-client.key—— 是给客户端用的
如果用 server cert 失败,换 healthcheck-client:
etcdctl \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/healthcheck-client.crt \
--key=/etc/kubernetes/pki/etcd/healthcheck-client.key \
endpoint health
坑 4:snapshot 不验证就放着
etcdctl snapshot save /backup/etcd.db
# 看着成功,没验证
# 半年后真灾难恢复,发现备份其实是空的 / 损坏的
备份完一定 snapshot status 验证,否则备份只是心理安慰。
坑 5:restore 用错 member name
etcdctl snapshot restore xxx.db --name=wrong-name --initial-cluster=m1=...,m2=...,m3=...
# 起来后 etcd cluster ID 不一致、永远 join 不进集群
--name 必须和 --initial-cluster 里的 hostname 对应。HA restore 时三个节点的 name / 顺序要完全对齐。
坑 6:直接 etcdctl del 删 K8s 数据
etcdctl del /registry/deployments/default/nginx
# kubectl 看不到 nginx 了,但是 controller 状态错乱
永远不要这样。K8s 状态是 apiserver 维护的、有 watcher / informer / cache。直接改 etcd 让所有 cache 失效、行为难预测。要删资源就 kubectl delete。
坑 7:HA 集群跨节点 endpoint 写错
etcdctl --endpoints=https://127.0.0.1:2379 endpoint health # 只看本机
# OK,但其它节点呢?
etcdctl --endpoints=https://m1:2379,https://m2:2379,https://m3:2379 endpoint health
# 看全集群
HA 集群一定要列所有 endpoint,否则只能看到本机视角。