问题背景
生产里见过几次 etcd 故障,根因都不复杂但代价都不小:
一次是 kubeadm 升级前没做 etcd 快照,升级失败后 etcd 启动报 wal: file does not exist,整个集群 NotReady,业务流量断。
一次是磁盘写满,etcdserver: mvcc: database space exceeded,apiserver 拒绝所有写,Pod 卡死、Ingress 配置无法更新。
一次是磁盘 IO 抖动,wal_fsync_duration_seconds 飙到 1.5s,etcd 心跳超时触发选举,集群不可用窗口达 4 分钟。
一次是误操作,etcdctl snapshot restore 用了错误的 --data-dir 覆盖了 /var/lib/etcd,只能从备份恢复。
etcd 是 K8s 唯一的状态来源:所有 Pod、Service、ConfigMap、Secret、CRD 都存在 etcd 里。etcd 出问题,整个集群就出问题。但很多同学日常只会 kubectl get,从来没碰过 etcd 本身,等到真出事了手忙脚乱。
这篇文章面向初中级运维和 SRE,讲清楚 etcd 在 K8s 里的角色、备份怎么打、怎么恢复、慢在哪、怎么调优、怎么排错。看完之后能独立完成 etcd 备份恢复、参数调优、监控告警、版本升级的全套操作。
适用读者
维护 K8s 集群的运维工程师、SRE。
准备从 K8s 1.28 升级到 1.30 的同学。
想要为 etcd 加监控、备份、灾备方案的工程师。
遇到 etcd 性能问题、选举抖动、磁盘满、启动失败的同学。
适用场景
单控制面(1 control-plane + 2 worker)的小型集群。
多控制面(3 / 5 control-plane)的中大型集群。
kubeadm 自建集群、kubespray、sealoscale 1.20 之前默认自带的 etcd。
阿里云 ACK、华为云 CCE、腾讯云 TKE 中自管 etcd 模式。
核心知识点
etcd 在 K8s 中的角色
K8s 不是一个分布式数据库。K8s 把所有状态存在 etcd 里,apiserver 是 etcd 的客户端。任何对集群状态的修改,最终都落盘到 etcd。常见存储对象:
Pod、Deployment、StatefulSet、DaemonSet、Job 等。
Service、Ingress、Endpoint、EndpointSlice。
ConfigMap、Secret。
RBAC 角色、角色绑定、ServiceAccount。
CRD 和 CR。
Lease(用于节点心跳)。
这些对象在 etcd 中以扁平 key 形式存在,例如:
/registry/pods/default/nginx-7c8d9f-x7k2m /registry/services/specs/default/kubernetes /registry/configmaps/kube-system/kube-proxy
etcd 版本与 K8s 版本对应
K8s 1.28 之前默认带 etcd 3.5.x。K8s 1.29+ 同样支持 etcd 3.5.x,K8s 1.30+ 引入 etcd 3.5.13+。要避免错配:
K8s 1.24~1.27:etcd 3.5.4~3.5.6
K8s 1.28~1.29:etcd 3.5.9~3.5.12
K8s 1.30+:etcd 3.5.13+ 优先
不能跨大版本升级 etcd,e.g. 3.4 → 3.5 之间需要数据迁移。etcdctl snapshot restore 出来的快照,可以做一次完整恢复后滚动升级版本。
etcd 架构
存储
etcd 用 boltdb(后改为 bbolt 的 fork)做 key-value 持久化。每个 key 关联一个 revision(MVCC),每次写都生成新 revision。读按 revision 隔离,避免读写冲突。
Raft 共识
etcd 用 Raft 算法做多副本共识。一写三读(默认)下,集群至少 3 个节点,建议奇数个:
1 节点:开发测试用。
3 节点:最常见的生产拓扑。
5 节点:大型集群,能容忍 2 节点故障。
7 节点:少数。增加写延迟,不推荐。
每个写请求都要超过半数节点确认(majority quorum),所以 3 节点集群只能挂 1 个,5 节点只能挂 2 个。
WAL
WAL(Write-Ahead Log)记录每次写请求,先落 WAL 再 apply 到 boltdb。WAL 损坏时,etcd 会 replay WAL 恢复。WAL 增长过快是性能问题,但会被定期 snapshot 截断。
Snapshot
snapshot 是某个时间点的完整数据快照,存为 .db 文件。etcd 后台定期 snapshot(默认每 10000 条记录),snapshot 后旧 WAL 可以删除。
MVCC
Multi-Version Concurrency Control。每次写都创建新版本,历史版本保留到压缩(compaction)才删。频繁读写场景下,DB 会膨胀,需要调 --auto-compaction-mode 和 --auto-compaction-retention。
部署拓扑
K8s 控制面部署 etcd 有两种模式:
Stacked(堆叠)
kubeadm 默认模式,etcd 跑在 control-plane 节点上,和 kube-apiserver 同一个节点。
优点:部署简单,etcd 数量和 control-plane 数量一致。
缺点:etcd 故障会拖累 control-plane 节点,etcd 性能受节点噪声影响。
External(外置)
etcd 跑在独立节点上,独立于 control-plane。
优点:etcd 集群资源隔离,监控、备份、扩容独立。
缺点:运维成本高,需要额外的机器和端口规划。
etcd 默认 2379(client)、2380(peer)。K8s 1.22+ 调整过端口,请以实际 K8s 版本为准。
网络端口
2379 - etcd client(kube-apiserver 连接) 2380 - etcd peer(节点间 Raft 同步)
生产里必须防火墙隔离,2379 只对 control-plane 开放,2380 只对 etcd 节点开放。
实战一:手动备份
前置准备
# 进入 control-plane 节点(以 kubeadm 部署为例) ssh control-plane-1 # 确认 etcd pod(堆叠模式) sudo crictl ps | grep etcd # 输出形如: # 1d2f3e4a5b6c k8s.gcr.io/etcd:3.5.13 etcd Running # 拿到 etcd 数据目录 sudo crictl inspect 1d2f3e4a5b6c | grep -i 'workdir|datadir' # 通常是 /var/lib/etcd # 找到 etcd 证书路径 ls /etc/kubernetes/pki/etcd/ # server.crt server.key ca.crt
一次性备份
# 用 etcdctl 做 snapshot sudo ETCDCTL_API=3 etcdctl snapshot save /backup/etcd-$(date +%Y%m%d-%H%M%S).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 snapshot status /backup/etcd-20260605-103045.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
预期输出类似:
: "hash": 1234567890, : "revision": 9876543, : "totalKey": 8231, : "totalSize": 134217728
totalKey 应该在数千到数万之间(取决于集群规模),totalSize 通常 100MB~500MB。
风险提示:备份操作会触发 etcd 内部数据页拷贝,IO 抖动可能会让线上 apiserver 短暂延迟。生产环境建议在业务低峰期做,或者在从节点上跑备份(从节点 Raft 状态可读)。
验证备份可恢复
# 拷贝快照到测试机 scp /backup/etcd-20260605-103045.db test-host:/tmp/ # 在测试机上恢复并启动一个临时 etcd ETCDCTL_API=3 etcdctl snapshot restore /tmp/etcd-20260605-103045.db --name=verify --initial-cluster=verify=https://127.0.0.1:2380 --initial-advertise-peer-urls=https://127.0.0.1:2380 --data-dir=/tmp/etcd-restore-verify # 用 etcd 镜像起一个临时容器 docker run -d --name etcd-verify -p 12379:2379 -v /tmp/etcd-restore-verify:/etcd-data -e ALLOW_NONE_AUTHENTICATION=yes quay.io/coreos/etcd:v3.5.13 etcd --name=verify --data-dir=/etcd-data --listen-client-urls=http://0.0.0.0:2379 --advertise-client-urls=http://127.0.0.1:2379 # 列出 key ETCDCTL_API=3 etcdctl get / --prefix --keys-only --endpoints=http://127.0.0.1:12379 | head -20 # 清理 docker rm -f etcd-verify rm -rf /tmp/etcd-restore-verify
这是验证备份能否恢复的标准动作。每周至少做一次。
实战二:自动备份 CronJob
方案 A:宿主机 cron + 备份脚本
#!/bin/bash
# /opt/scripts/etcd-backup.sh
# 风险提示:此脚本在所有 control-plane 节点上跑,配置 crontab 错开时间,避免同时 IO 抖动
set -euo pipefail
BACKUP_DIR="/backup/etcd"
RETENTION_DAYS=14
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE="${BACKUP_DIR}/etcd-${TIMESTAMP}.db"
mkdir -p "${BACKUP_DIR}"
export ETCDCTL_API=3
etcdctl snapshot save "${BACKUP_FILE}"
--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 snapshot status "${BACKUP_FILE}"
--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
# 上传到远端(这里用 NFS,也可以是 S3 / OSS / COS)
cp "${BACKUP_FILE}" /nfs/etcd-backup/etcd-${TIMESTAMP}.db
# 清理本地 7 天前的备份
find "${BACKUP_DIR}" -name "etcd-*.db" -mtime +7 -delete
# 清理远端 14 天前的备份
find /nfs/etcd-backup/ -name "etcd-*.db" -mtime +${RETENTION_DAYS} -delete
echo"[$(date)] etcd backup ok: ${BACKUP_FILE}"
加到 crontab:
# 凌晨 3 点执行,错开 30 分钟 30 3 * * * /opt/scripts/etcd-backup.sh >> /var/log/etcd-backup.log 2>&1
方案 B:etcd-operator / k8s-sidecar 备份
不少团队用 etcd-backup-restore-operator 或者自建 CronJob 在集群内部跑备份。把 etcd 客户端证书挂载到 Pod 里,Pod 跑 etcdctl snapshot save,然后上传到 S3/OSS。
# etcd-backup-cronjob.yaml
apiVersion:batch/v1
kind:CronJob
metadata:
name:etcd-backup
namespace:kube-system
spec:
schedule:"30 3 * * *"
concurrencyPolicy:Forbid
successfulJobsHistoryLimit:3
failedJobsHistoryLimit:3
jobTemplate:
spec:
template:
spec:
hostNetwork:true
nodeSelector:
node-role.kubernetes.io/control-plane:""
tolerations:
-operator:Exists
containers:
-name:etcd-backup
image:registry.k8s.io/etcd:3.5.13
command:
-/bin/sh
--c
-|
set -euo pipefail
BACKUP_DIR=/backup
TIMESTAMP=$(date +%Y%m%d-%H%M%S)
BACKUP_FILE=${BACKUP_DIR}/etcd-${TIMESTAMP}.db
mkdir -p ${BACKUP_DIR}
ETCDCTL_API=3 etcdctl snapshot save ${BACKUP_FILE}
--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 snapshot status ${BACKUP_FILE}
--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
ls -lh ${BACKUP_FILE}
# 此处可加 aws s3 cp / ossutil cp 等上传命令
volumeMounts:
-name:etcd-certs
mountPath:/etc/kubernetes/pki/etcd
readOnly:true
-name:etcd-data
mountPath:/backup
restartPolicy:OnFailure
volumes:
-name:etcd-certs
hostPath:
path:/etc/kubernetes/pki/etcd
type:Directory
-name:etcd-data
hostPath:
path:/backup/etcd
type:DirectoryOrCreate
风险提示:hostPath 挂载备份目录要确保权限正确,容器内进程对 hostPath 有写权限。
备份策略总结
频率:每天至少 1 次,业务关键(金融、电商)每 4~6 小时 1 次。
保留:本地 7 天,远端(异地)30 天起步。
验证:每月至少抽 1 次做 restore 演练。
加密:备份上传到 S3 / OSS 时用 SSE-KMS 或 SSE-S3,加密磁盘上的备份文件。
上传:用 cosutil / aws s3 cp / ossutil 异步上传,规避同步 IO 抖动。
实战三:恢复
场景 1:单节点故障
3 节点 etcd 集群,挂了 1 节点。剩余 2 节点仍然能服务(因为 quorum 是 2)。这时不需要从 snapshot 恢复,只需要:
# 在故障节点上把 etcd 拉起来 # 1. 确认故障原因(磁盘 / 网络 / OOM) # 2. 修复故障 # 3. 把节点从 etcd 集群中移除旧成员(如果是磁盘损坏) sudo ETCDCTL_API=3 etcdctl member remove--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 # 4. 重新 join 集群(kubeadm 部署) # 4.1 备份 manifest sudo cp /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/manifests/etcd.yaml.bak # 4.2 重新生成 etcd 静态 pod 配置 sudo kubeadm init phase etcd local --config=/etc/kubernetes/kubeadm-config.yaml # 用实际的 kubeadm 配置文件 # 风险提示:如果 kubeadm-config.yaml 不存在,可从备份恢复或手工生成 etcd.yaml
etcd 启动时会自动从 leader 拉数据,状态会追平。追平期间 etcdctl endpoint status 显示 dbSize 逐渐增加。
如果是堆叠模式、control-plane 节点整体重建,更稳的做法是用 kubeadm join --control-plane 重新加入集群(带 --certificate-key),etcd 会作为 control-plane 加入流程的一部分被重新拉起。
# 完整 join 示例(control-plane 节点重加入) sudo kubeadm join 10.0.0.1:6443 --token--discovery-token-ca-cert-hash sha256: --control-plane --certificate-key
场景 2:etcd 集群全挂,需要从 snapshot 恢复
这是最严重的场景。生产里往往是误操作、磁盘损坏、K8s 升级失败导致。
风险提示:恢复操作是不可逆的,恢复前必须确认备份文件完好、目标目录正确、IP 和证书对应。
# 1. 停掉所有 control-plane 节点上的 kube-apiserver # 避免它继续往坏 etcd 写数据 sudo systemctl stop kube-apiserver # 2. 在所有 control-plane 节点上停掉 etcd # kubeadm 部署的 etcd 是 static pod,删除 manifest 即可 sudo mv /etc/kubernetes/manifests/etcd.yaml /etc/kubernetes/manifests/etcd.yaml.bak # 3. 在每个 control-plane 节点上清理旧数据(风险高,必须先备份) sudo mkdir -p /var/lib/etcd.bak.$(date +%s) sudo mv /var/lib/etcd/* /var/lib/etcd.bak.$(date +%s)/ # 4. 用 snapshot 初始化一个新 etcd 集群 sudo ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-20260605-103045.db --name=control-plane-1 --initial-cluster=control-plane-1=https://10.0.0.1:2380,control-plane-2=https://10.0.0.2:2380,control-plane-3=https://10.0.0.3:2380 --initial-advertise-peer-urls=https://10.0.0.1:2380 --data-dir=/var/lib/etcd # 在 control-plane-2 节点: sudo ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-20260605-103045.db --name=control-plane-2 --initial-cluster=control-plane-1=https://10.0.0.1:2380,control-plane-2=https://10.0.0.2:2380,control-plane-3=https://10.0.0.3:2380 --initial-advertise-peer-urls=https://10.0.0.2:2380 --data-dir=/var/lib/etcd # 在 control-plane-3 节点: sudo ETCDCTL_API=3 etcdctl snapshot restore /backup/etcd-20260605-103045.db --name=control-plane-3 --initial-cluster=control-plane-1=https://10.0.0.1:2380,control-plane-2=https://10.0.0.2:2380,control-plane-3=https://10.0.0.3:2380 --initial-advertise-peer-urls=https://10.0.0.3:2380 --data-dir=/var/lib/etcd # 5. 恢复 etcd static pod manifest sudo mv /etc/kubernetes/manifests/etcd.yaml.bak /etc/kubernetes/manifests/etcd.yaml # 6. 等待 etcd pod 起来 sudo crictl ps -a | grep etcd # 大概 30 秒到 2 分钟,etcd 集群重新选主 # 7. 启动 kube-apiserver sudo systemctl start kube-apiserver # 8. 验证集群状态 sudo kubectl get nodes sudo kubectl get pods -A
恢复后做几件事:
检查 etcd 集群健康:etcdctl endpoint health --cluster
检查 apiserver 健康:curl -k https://localhost:6443/healthz
检查所有节点是否 Ready:可能需要 kubectl uncordon 触发节点状态同步
检查所有 Pod 是否能正常启动:部分 Pod 因为 etcd 数据版本问题可能需要重启
场景 3:恢复到新集群(灾备)
把 snapshot 恢复到一台独立机器,验证后用 etcdctl member add 加到新集群,做数据迁移。
风险提示:跨集群恢复时,service account token、cluster role binding 等可能因为新集群的 CA 证书不同而失效,需要重新签发。
# 1. 在新集群的临时 etcd 容器里加载 snapshot docker run -d --name etcd-tmp -p 12379:2379 -v /tmp/etcd-restore:/etcd-data quay.io/coreos/etcd:v3.5.13 etcd --name=tmp --data-dir=/etcd-data --listen-client-urls=http://0.0.0.0:2379 --advertise-client-urls=http://127.0.0.1:2379 # 2. 用 etcdctl migrate 把 v2 数据转 v3(如果需要) # 较新的 etcd 集群都是 v3,跳过 # 3. 把所有 key dump 成 JSON ETCDCTL_API=3 etcdctl get / --prefix --keys-only --endpoints=http://127.0.0.1:12379 | wc -l # 4. 用 etcd-dump-restore / etcd-rebuild 工具重建到目标集群 # 或者用原生 etcd migration protocol
更简单的做法是直接在新 control-plane 上跑 etcdctl snapshot restore,生成新 etcd 数据目录,然后用 K8s 引导脚本(kubeadm init / join)重新初始化集群。
场景 4:CRD 误删
不少同学遇到的是 CRD 误删,CRD 没了,所有 CR 还在 etcd 里但无法访问。
# 1. 从 snapshot 找到 CRD 定义 ETCDCTL_API=3 etcdctl get /registry/apiextensions.k8s.io/customresourcedefinitions/--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 # 2. 转成 YAML ETCDCTL_API=3 etcdctl get /registry/apiextensions.k8s.io/customresourcedefinitions/ --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 -o json | jq -r '.value' | base64 -d > crd.yaml # 3. kubectl apply -f crd.yaml
回滚方案
恢复操作本身就是回滚方案的一部分。完整流程:
立即停服(关 apiserver、关 etcd)。
评估快照版本:etcdctl snapshot status。
在新节点验证快照。
选合适时间窗执行 restore。
验证业务(先恢复非关键 Pod,最后恢复核心 Pod)。
保留坏 etcd 数据目录 7 天。
实战四:性能调优
关键参数
etcd 的性能调优集中在几个方向:磁盘、内存、CPU、网络、参数。
启动参数
/etc/kubernetes/manifests/etcd.yaml 里的关键字段:
spec: containers: -command: -etcd ---name=control-plane-1 ---data-dir=/var/lib/etcd ---listen-client-urls=https://10.0.0.1:2379 ---advertise-client-urls=https://10.0.0.1:2379 ---listen-peer-urls=https://10.0.0.1:2380 ---initial-advertise-peer-urls=https://10.0.0.1:2380 ---initial-cluster=control-plane-1=https://10.0.0.1:2380,... ---quota-backend-bytes=8589934592# 8GB,硬性上限 ---auto-compaction-mode=periodic ---auto-compaction-retention=8h ---max-snapshots=5 ---max-wals=5 ---election-timeout=5000 # 默认 1000ms,建议 5000ms ---heartbeat-interval=500 # 默认 100ms,建议 500ms ---snapshot-count=10000 # 默认 100000,频繁写场景可降到 10000
参数解释:
--quota-backend-bytes:后端 DB 硬性大小上限。超过后 etcd 报错 mvcc: database space exceeded,必须 defrag 或扩 quota。
--auto-compaction-mode=periodic + --auto-compaction-retention=8h:每 8 小时做一次 MVCC 压缩,释放历史版本。
--max-snapshots:保存的最大 snapshot 文件数。超过后自动删最老的。
--max-wals:保存的最大 WAL 文件数。超过后自动删最老的。
--election-timeout / --heartbeat-interval:Raft 选举超时和心跳间隔。生产里要拉长,避免网络抖动触发选举。election-timeout 一般是 heartbeat-interval 的 10 倍。
--snapshot-count:触发 snapshot 的写记录数。频繁写场景可降,加快 snapshot 但 IO 开销增加。
数据库大小规划
etcd v3.4 之前的硬上限是 2GB,从 v3.4 开始可以调整 --quota-backend-bytes,生产里建议 8GB。8GB 容量大约能存 1000 万级别 key,超过 8GB 要警惕。
监控命令:
ETCDCTL_API=3 etcdctl endpoint status --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 -w table
输出形如:
+------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | ENDPOINT | ID | VERSION | DB SIZE | IS LEADER | IS LEARNER | RAFT TERM | RAFT INDEX | RAFT APPLIED INDEX | ERRORS | +------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+ | https://10.0.0.1:2379 | 8e9e05c52164694d | 3.5.13 | 134 MB | false | false | 2 | 123456 | 123456 | | | https://10.0.0.2:2379 | 4a1a2b3c4d5e6f7g | 3.5.13 | 134 MB | true | false | 2 | 123456 | 123456 | | | https://10.0.0.3:2379 | 7h8i9j0k1l2m3n4o | 3.5.13 | 134 MB | false | false | 2 | 123456 | 123456 | | +------------------------+------------------+---------+---------+-----------+------------+-----------+------------+--------------------+--------+
DB SIZE 是关键指标,超过 1GB 就要警惕,超过 --quota-backend-bytes 80% 就要 defrag。
碎片化与 defrag
etcd 用 bbolt 存储,删除的 key 不会立即释放磁盘空间。频繁删除 / 压缩的集群,磁盘空间会"碎"。
# 在线 defrag(不需要停 etcd) ETCDCTL_API=3 etcdctl defrag --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 endpoint status --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 -w table
风险提示:defrag 是 IO 密集操作,生产环境应在低峰期执行,每个节点错开时间,每个节点之间至少 5 分钟间隔。
磁盘 IO 优化
etcd 强依赖磁盘 IO。wal_fsync_duration_seconds(WAL 落盘延迟)的 P99 应该小于 10ms,超过 100ms 就要排查。
# 监控磁盘 IO iostat -dx 1 # 看 %util、await、svctm # %util > 80% 表示磁盘饱和 # 查看 IO 调度器 cat /sys/block/vda/queue/scheduler # 期望 none(NVMe)或 deadline(SSD),不要是 cfq # 设置 IO 调度器 echo none > /sys/block/vda/queue/scheduler
SSD 是 etcd 的最低要求。不要用机械盘、不要用 Ceph / NFS 后端(同步 IO 抖动大)。
内存和 CPU
etcd 进程本身内存占用 1~2GB,但 Page Cache 越多越好。etcd 用 mmap 读 bbolt 文件,OS Page Cache 命中率高时延迟极低。
# 检查 page cache cat /proc/meminfo | grep -E "Cached|Buffers"
CPU 不强求多核,但单核性能要好(etcd 写请求是单 Raft leader 处理)。4 核起步,建议 8 核。
网络
etcd peer(2380)节点间 RTT 应小于 10ms。跨机柜、跨机房部署 etcd 会导致 RTT 升高、Raft 选举抖动。强烈建议 etcd 节点部署在同一机柜或同一可用区。
大集群调优
2000 节点的 K8s 集群(e.g. 200 worker + 5 control-plane),etcd 写入量很大,需要重点调优:
--quota-backend-bytes 调到 16GB 或 32GB。
--auto-compaction-retention 调到 4h。
defrag 频率提升到每周一次。
apiserver 端开启 --watch-cache-sizes=... 减少对 etcd 的 watch 压力。
避免大型 CRD 频繁更新。
secret 数量控制在 1 万以下。
实战五:监控
etcd 自带 metrics
etcd 默认在 2381 端口暴露 metrics。K8s 部署下,metrics endpoint 是 --listen-metrics-urls=http://0.0.0.0:2381(需要在 etcd.yaml 里加这个参数)。
- --listen-metrics-urls=http://0.0.0.0:2381
关键指标
| 指标 | 含义 | 告警阈值建议 |
|---|---|---|
| etcd_server_leader_changes_seen_total | leader 切换次数 | 1 分钟内 > 2 |
| etcd_disk_writes_total | 磁盘写入次数 | 持续 > 2000/s 警惕 |
| etcd_disk_wal_fsync_duration_seconds | WAL fsync 延迟 P99 | > 10ms 警惕 |
| etcd_disk_backend_commit_duration_seconds | backend commit 延迟 P99 | > 25ms 警惕 |
| etcd_server_proposals_applied_total | apply 提案速率 | 持续不增长表示卡住 |
| etcd_server_proposals_pending | 待处理提案 | > 0 持续不下降 |
| etcd_server_proposals_failed_total | 失败提案 | > 0 持续 |
| etcd_network_active_peers | 在线 peer 数 | < (N-1)/2 集群不可写 |
| etcd_debugging_mvcc_db_total_size_in_bytes | DB 实际占用 | > quota 80% 警惕 |
| etcd_debugging_mvcc_keys_total | 总 key 数 | 持续 > 800 万警惕 |
| etcd_debugging_mvcc_db_total_size_in_use_in_bytes | 实际使用量 | 同上 |
Prometheus 抓取
# prometheus-servicemonitor.yaml(如果用 Prometheus Operator) apiVersion:monitoring.coreos.com/v1 kind:ServiceMonitor metadata: name:etcd namespace:monitoring labels: app:etcd spec: selector: matchLabels: app:etcd namespaceSelector: matchNames: -kube-system endpoints: -port:metrics interval:30s scheme:http
metrics 这个端口需要 etcd 服务暴露了 2381(按实际部署改)。
告警规则
# prometheus-rule-etcd.yaml
apiVersion:monitoring.coreos.com/v1
kind:PrometheusRule
metadata:
name:etcd-alerts
namespace:monitoring
labels:
app:etcd
spec:
groups:
-name:etcd
rules:
-alert:EtcdInsufficientMembers
expr:count(up{job="etcd"}==0)>0
for:3m
labels:
severity:critical
annotations:
summary:etcdclusterhasinsufficientmembers
description:"{{ $value }} etcd members are down for more than 3 minutes."
-alert:EtcdNoLeader
expr:etcd_server_has_leader==0
for:1m
labels:
severity:critical
annotations:
summary:etcdhasnoleader
description:"etcd cluster has no leader for 1 minute. Writes are blocked."
-alert:EtcdHighFsyncDuration
expr:histogram_quantile(0.99,rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m]))>0.01
for:5m
labels:
severity:warning
annotations:
summary:etcdwalfsyncP99>10ms
description:"High fsync latency will cause election timeouts. Check disk IO."
-alert:EtcdHighBackendCommitDuration
expr:histogram_quantile(0.99,rate(etcd_disk_backend_commit_duration_seconds_bucket[5m]))>0.025
for:5m
labels:
severity:warning
annotations:
summary:etcdbackendcommitP99>25ms
description:"High backend commit latency indicates disk pressure."
-alert:EtcdHighDbSize
expr:etcd_debugging_mvcc_db_total_size_in_bytes/etcd_debugging_mvcc_db_quota_bytes>0.8
for:10m
labels:
severity:warning
annotations:
summary:etcdDBsize>80%ofquota
description:"Run defrag or expand quota."
-alert:EtcdManyLeaderChanges
expr:increase(etcd_server_leader_changes_seen_total[1m])>2
for:0m
labels:
severity:critical
annotations:
summary:etcdleaderchanges>2in1minute
description:"Frequent leader changes indicate network or disk problems."
-alert:EtcdProposalStuck
expr:rate(etcd_server_proposals_applied_total[5m])==0andrate(etcd_server_proposals_committed_total[5m])>0
for:5m
labels:
severity:critical
annotations:
summary:etcdproposalsarestuck
description:"Proposals are committed but not applied. Check apiserver and etcd logs."
告警阈值不绝对,要结合业务基线调整。e.g. 写密集集群 fsync 阈值可以放到 25ms。
Grafana 面板
常见面板布局:
集群拓扑:节点、leader、raft term。
延迟:wal fsync、backend commit、proposal apply。
流量:client、peer、disk、network。
容量:DB size、key 数、WAL 数量。
健康:leader changes、proposals、errors。
Grafana 官方 etcd dashboard ID 3070(社区版)可以直接 import,根据实际指标微调。
实战六:常见故障排查
故障 1:apiserver 启动报 connection refused
现象:kubectl get nodes 报 The connection to the server localhost:8080 was refused - did you specify the right host or port?
排查路径:
# 1. 看 apiserver 日志 sudo journalctl -u kube-apiserver -n 200 # 2. 看错误信息 sudo journalctl -u kube-apiserver | grep -i "etcd|connection" # 3. 测试 etcd 连通性 ETCDCTL_API=3 etcdctl endpoint health --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 # 4. 看 etcd 进程 sudo crictl ps -a | grep etcd sudo crictl logs
常见原因:etcd 还没启动、证书不对、端口未通。
故障 2:mvcc: database space exceeded
现象:所有写操作失败,apiserver 日志报 etcdserver: mvcc: database space exceeded。
# 1. 看 DB 大小 ETCDCTL_API=3 etcdctl endpoint status --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 -w table # 2. 看 quota 配置(通过 /metrics) curl http://127.0.0.1:2381/metrics | grep etcd_debugging_mvcc_db_quota_bytes # 3. 临时扩 quota # 编辑 /etc/kubernetes/manifests/etcd.yaml # --quota-backend-bytes=17179869184 # 16GB # 等待 pod 重启 # 4. 执行 defrag ETCDCTL_API=3 etcdctl defrag --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
风险提示:扩 quota 是临时方案,根本是要么扩磁盘、要么定期 defrag、要么减少 secret 等大对象。
故障 3:选举频繁切换
现象:etcd leader 频繁切换(etcd_server_leader_changes_seen_total 持续增长),集群可用性下降。
# 1. 检查网络延迟 pingmtr # 2. 检查 fsync 延迟 curl -s http://127.0.0.1:2381/metrics | grep wal_fsync_duration_seconds # 3. 检查 CPU / IO top iostat -dx 1 # 4. 看 etcd 日志 sudo crictl logs | grep -i "election|leader|heartbeat"
根因通常是:
节点间 RTT 过大,election-timeout 不够。
磁盘 IO 抖动,fsync 超时。
CPU 资源被其他进程抢占。
网络分区(防火墙配置错误)。
修复:
拉长 --election-timeout 到 5000ms。
升级磁盘到 SSD / NVMe。
排查节点 CPU 抢占。
修复防火墙。
故障 4:snapshot 备份后恢复失败
现象:snapshot restore 报错 wal: file does not exist 或 unexpected page type。
# 1. 验证 snapshot 状态 ETCDCTL_API=3 etcdctl snapshot status /backup/etcd-20260605.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 # 2. 用别的 etcd 版本试 docker run --rm -v /backup:/backup quay.io/coreos/etcd:v3.5.13 etccdctl snapshot status /backup/etcd-20260605.db # 不同 etcd 版本读 snapshot 文件可能成功 # 3. 强校验和 # 如果 snapshot 损坏,只能用上一份备份
常见原因:备份时 etcd 还在写,snapshot 内部不一致;或者备份上传到对象存储后下载不完整。
故障 5:K8s 升级后 etcd 起不来
现象:kubeadm upgrade 失败,etcd 容器重启循环。
# 1. 看 kubeadm 日志 sudo kubeadm upgrade apply v1.30.0 2>&1 | tail -50 # 2. 看 etcd 容器日志 sudo crictl logs--previous # 3. 看 etcd manifest sudo cat /etc/kubernetes/manifests/etcd.yaml
常见原因:etcd 版本和 K8s 版本不兼容、etcd 启动参数在新版本不支持、证书过期。
修复:
回滚到上一个稳定的 K8s 版本(如果有前镜像)。
重新生成 etcd 证书。
手动恢复 etcd 静态 pod。
故障 6:单 etcd 节点 OOM
现象:etcd pod OOMKilled,重启循环。
# 1. 看 OOM 记录 dmesg | grep -i "killed process" sudo crictl logs--previous | tail -50 # 2. 看内存使用 free -h
etcd 进程本身内存不大(~1GB),OOM 通常是 host 节点内存不够或者 cgroup limit 太小。检查 kube-reserved、system-reserved、eviction-thresholds。
实战七:升级 etcd
升级路径
etcd 升级必须逐版本升级,不能跨大版本:
3.3 → 3.4
3.4 → 3.5
同大版本内 minor 升级(3.5.4 → 3.5.13)相对安全,但还是建议先在测试环境跑一遍。
kubeadm 升级流程
# 1. 备份(必须) sudo /opt/scripts/etcd-backup.sh # 2. 升级 kubeadm sudo apt-mark unhold kubeadm && sudo apt-get update && sudo apt-get install -y kubeadm=1.30.x-00 && sudo apt-mark hold kubeadm # 3. 看升级计划 sudo kubeadm upgrade plan # 4. 升级(会拉新 etcd 镜像) sudo kubeadm upgrade apply v1.30.x # 5. 在其他 control-plane 节点执行 sudo kubeadm upgrade node # 6. 升级 kubelet、kubectl sudo apt-mark unhold kubelet kubectl && sudo apt-get install -y kubelet=1.30.x-00 kubectl=1.30.x-00 && sudo apt-mark hold kubelet kubectl sudo systemctl daemon-reload sudo systemctl restart kubelet
升级过程中 etcd 会自动迁移数据格式(3.4 → 3.5)。升级顺序:先升级 control-plane-1 节点,等 etcd 集群稳定后(10 分钟)再升级 control-plane-2,以此类推。
风险提示:升级前必须做好 snapshot 备份;升级期间业务请求可能短暂不可用;升级后必须验证集群状态、apiserver 状态、Pod 状态。
实战八:etcd 加密
K8s 1.13+ 支持 etcd 数据加密(EncryptionConfiguration),secret 在 etcd 中以加密形式存储。
# /etc/kubernetes/etcd-encryption.yaml apiVersion:apiserver.config.k8s.io/v1 kind:EncryptionConfiguration resources: -resources: -secrets providers: -aescbc: keys: -name:key1 secret:-identity:{}
secret 是 32 字节随机密钥的 base64 编码:
# 生成密钥 head -c 32 /dev/urandom | base64
apiserver 启动参数加:
--encryption-provider-config=/etc/kubernetes/etcd-encryption.yaml
风险提示:密钥必须安全保存(KMS、Vault),丢失密钥后 secret 无法解密。加密会影响 etcd 性能(1~5%),但能换来合规要求。
定期轮换密钥:
# 1. 新密钥 head -c 32 /dev/urandom | base64 # 2. 编辑配置,新密钥放第一位,旧密钥放第二位 # 3. 滚动重启 apiserver # 4. 等所有 secret 用新密钥重写 # 5. 把旧密钥从配置中删掉 # 6. 再滚动重启 apiserver
常见问题 FAQ
Q1:etcd 需要多少内存?
A:etcd 进程本身 1~2GB,OS page cache 越多越好。建议控制面节点至少 8GB 内存,etcd 独占 2GB。
Q2:etcd 需要 SSD 吗?
A:强烈建议。NVMe 更佳。机械盘在 fsync 时 P99 延迟会到几十毫秒,触发选举超时。
Q3:3 节点 etcd 挂 1 个能恢复吗?
A:能,剩余 2 节点维持 quorum(多数派)。挂 1 节点时集群可读写,节点修复后自动追平。
Q4:snapshot restore 会丢数据吗?
A:会。restore 是从某时间点的 snapshot 还原,那之后的数据会丢。所以高频备份是减少数据丢失的唯一办法。
Q5:etcd 可以用 NAS / NFS 吗?
A:不建议。NFS 同步写延迟不可控,fsync 抖动会触发选举。etcd 必须是本地 SSD/NVMe。
Q6:apiserver 加密 secret 一定要做吗?
A:根据合规要求。金融、政企场景必须做。普通业务可以不做,但做了能防"etcd 备份泄露导致 secret 泄露"。
Q7:etcd 备份文件怎么加密?
A:上传到 S3/OSS 时用 SSE-KMS,本地备份文件用 openssl / gpg 加密:
gpg --symmetric --cipher-algo AES256 etcd-20260605.db
Q8:跨机房的 K8s 集群,etcd 怎么部署?
A:分两种:单机房多控制面 + 跨机房 worker;多机房多控制面。生产里单机房多控制面更稳,跨机房通过专线 / VPN 同步。etcd 节点必须同机房。
Q9:etcd 性能瓶颈怎么定位?
A:用 etcdctl check perf 跑基准测试:
ETCDCTL_API=3 etcdctl check perf --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 --conn-rate=100 --req-rate=1000
对比官方 baseline:
write 100 keys/sec with 1KB value: < 10ms P99
read 100 keys/sec: < 10ms P99
Q10:etcd 备份恢复后,apiserver 报 storage format error?
A:通常是证书不对。检查 apiserver 用的 etcd 证书和 etcd 服务器的证书是否匹配。
Q11:etcd 节点可以加 CPU 限制吗?
A:可以但不建议。etcd 对延迟敏感,CPU throttling 会触发选举。建议用 cpuset 或者用 Guaranteed QoS Pod。
Q12:怎么减少 etcd 中 key 数量?
A:清理不需要的 Secret、ConfigMap、Pod 日志、CRD 实例。K8s 1.29+ 引入 --watch-cache-sizes,调小能减少 etcd watch 压力。
总结
etcd 是 K8s 的大脑,平时不会出事,但一出事就是大事故。这篇文章把生产里需要的核心能力都覆盖到了:
手动 / 自动备份
多种场景的恢复(单节点故障、全集群故障、CRD 误删、跨集群灾备)
性能调优(参数、磁盘、内存、网络)
监控(Prometheus 指标、关键告警、Grafana 面板)
常见故障的排查路径
升级流程
加密
核心心法:
备份每天做,远端保留 30 天起步。
每月抽一次做 restore 演练,验证备份可恢复。
监控是生命线,fsync 延迟、leader 切换、DB 大小必须告警。
磁盘是 etcd 的命门,SSD/NVMe 是底线,IO 抖动是大忌。
升级前必须做 snapshot,每个节点错开 10 分钟以上。
把这些落到生产流程里,etcd 基本不会出大事故。
附录:命令速查
# 备份 ETCDCTL_API=3 etcdctl snapshot save /path/db --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 验证备份 ETCDCTL_API=3 etcdctl snapshot status /path/db --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 恢复 ETCDCTL_API=3 etcdctl snapshot restore /path/db --name=--initial-cluster= =..., =... --initial-advertise-peer-urls= --data-dir=/var/lib/etcd # 集群健康 ETCDCTL_API=3 etcdctl endpoint health --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 集群状态 ETCDCTL_API=3 etcdctl endpoint status --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... -w table # 集群成员 ETCDCTL_API=3 etcdctl member list --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... -w table # 读取 key ETCDCTL_API=3 etcdctl get /registry --prefix --keys-only --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 写 key ETCDCTL_API=3 etcdctl put /test/foo "bar" --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 删 key ETCDCTL_API=3 etcdctl del /test/foo --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # defrag ETCDCTL_API=3 etcdctl defrag --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 告警列表 ETCDCTL_API=3 etcdctl alarm list --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 解除告警 ETCDCTL_API=3 etcdctl alarm disarm --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 性能测试 ETCDCTL_API=3 etcdctl check perf --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... --conn-rate=100 --req-rate=1000
附录:etcd 启动参数速查
| 参数 | 默认值 | 推荐生产值 | 含义 |
|---|---|---|---|
| --name | 无 | 节点主机名 | etcd 节点名 |
| --data-dir | 无 | /var/lib/etcd | 数据目录 |
| --listen-client-urls | 无 | https://0.0.0.0:2379 | client 监听 |
| --advertise-client-urls | 无 | https://:2379 | client 通告 |
| --listen-peer-urls | 无 | https://0.0.0.0:2380 | peer 监听 |
| --initial-advertise-peer-urls | 无 | https://:2380 | peer 通告 |
| --initial-cluster | 无 | name1=url1,name2=url2 | 初始集群 |
| --quota-backend-bytes | 2GB | 8GB~32GB | DB 大小硬上限 |
| --auto-compaction-mode | periodic | periodic | 压缩模式 |
| --auto-compaction-retention | 0 | 8h | 压缩保留时长 |
| --max-snapshots | 5 | 5 | 最大 snapshot 数 |
| --max-wals | 5 | 5 | 最大 WAL 数 |
| --election-timeout | 1000 | 5000 | 选举超时(ms) |
| --heartbeat-interval | 100 | 500 | 心跳间隔(ms) |
| --snapshot-count | 100000 | 10000 | snapshot 触发写数 |
| --listen-metrics-urls | 无 | http://0.0.0.0:2381 | metrics 监听 |
不同版本默认值可能略有差异,以实际版本为准。
附录:故障排查决策树
下面是一棵精简的决策树,按现象直接定位到检查动作。
现象 1:kubectl 报 "connection refused"
kubectl └─ apiserver 没起来? ├─ journalctl -u kube-apiserver | grep error │ ├─ "connection refused" → etcd 没起来 │ │ └─ crictl logs│ │ ├─ "mvcc: database space exceeded" → defrag / 扩 quota │ │ ├─ "wal: file does not exist" → 从 snapshot 恢复 │ │ ├─ "certificate is valid for ..." → 证书不对应 │ │ └─ "no such file or directory" → data-dir 路径问题 │ ├─ "x509: certificate has expired" → 重新签证书 │ └─ "etcd cluster is unavailable" → etcd 集群挂 └─ apiserver 端口不通? └─ ss -lntp | grep 6443 ├─ 没监听 → apiserver 启动失败 └─ 监听了但访问不到 → 网络 / 防火墙
现象 2:所有写请求 hang 住
kubectl apply -f xx.yaml 卡住 └─ apiserver 日志 └─ "etcdserver: request timed out" ├─ etcd leader 不健康 │ └─ etcdctl endpoint status │ ├─ 多个节点 leader=false → 选举中 │ └─ IS LEADER 列空 → 没有 leader │ └─ 拉长 election-timeout / 检查网络 └─ etcd 磁盘 IO 抖动 └─ iostat -dx 1 └─ await > 50ms → 换 SSD
现象 3:节点 NotReady
kubectl get nodes └─ control-plane 节点 NotReady └─ kubectl describe node└─ "etcd: ..." 字段 └─ apiserver 到 etcd 端口不通 ├─ curl -k https:// :2379/health ├─ telnet 2379 └─ iptables -L -n | grep 2379
附录:etcd 与 kube-apiserver 的关系
很多人以为 kube-apiserver 和 etcd 是一回事,其实 apiserver 是 etcd 的客户端之一。完整调用链:
kubectl → kube-apiserver (--etcd-servers=https://127.0.0.1:2379) → etcd ↓ 通过 client cert 鉴权
apiserver 启动时通过 --etcd-servers 指定 etcd 地址,通过 --etcd-cafile、--etcd-certfile、--etcd-keyfile 指定 etcd 客户端证书。这三个参数必须在所有 control-plane 节点上一致。
# 看 apiserver 实际连接哪些 etcd sudo ps -ef | grep kube-apiserver | grep -o "--etcd-servers=[^ ]*" # 输出形如: # --etcd-servers=https://10.0.0.1:2379,https://10.0.0.2:2379,https://10.0.0.3:2379
apiserver 不会自动发现 etcd 节点变化。如果 etcd 节点 IP 变了,必须重启 apiserver 重新加载 --etcd-servers。
watch 机制:apiserver 启动时对 etcd 建一个长连接 watch 资源变化,etcd 把变更推给 apiserver,apiserver 再推给 kubectl / controller / scheduler。watch 在 etcd 端是 server push,不占 apiserver 端轮询资源。
如果 watch 断开(etcd 重启、apiserver 重启),apiserver 会重新 list + watch,期间有 1~2 秒的事件丢失,controller 会通过对比 list 结果重试。
附录:etcd 集群的健康度评估
定期做以下检查,作为集群健康度的基线。
周检查
# 1. 集群健康
ETCDCTL_API=3 etcdctl endpoint health --cluster
--cacert=... --cert=... --key=...
# 2. 集群状态
ETCDCTL_API=3 etcdctl endpoint status -w table
--cacert=... --cert=... --key=...
# 3. DB 大小趋势
ETCDCTL_API=3 etcdctl endpoint status -w json
--cacert=... --cert=... --key=... |
jq '.[] | {Endpoint, DbSize: .Status.dbSize}'
# 4. Leader 切换次数
curl -s http://127.0.0.1:2381/metrics | grep etcd_server_leader_changes_seen_total
月检查
# 1. 备份恢复演练(关键) # 在测试机 restore 一份最近的 snapshot,验证可读 # 2. 备份保留策略 ls -la /backup/etcd/ | head -20 find /backup/etcd/ -mtime +30 -delete # 3. 证书有效期 sudo kubeadm certs check-expiration # 4. defrag(错开时间,每个节点之间至少 5 分钟) ETCDCTL_API=3 etcdctl defrag --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 5. 磁盘空间 df -h /var/lib/etcd
季度检查
etcd 版本与 K8s 版本对应表。
备份异地存储(S3/OSS)的恢复演练。
监控告警阈值校准。
etcd 节点 OS / 内核版本升级。
附录:iptables / 防火墙规则
etcd 需要以下端口:
# etcd 节点之间(peer) 2380/tcp 允许 10.0.0.0/24 # apiserver 到 etcd(client) 2379/tcp 允许 10.0.0.0/24 # Prometheus 抓 metrics 2381/tcp 允许 10.0.1.0/24 # prometheus 节点
iptables 示例:
# control-plane 节点上,限制 2379 / 2380 仅对内网开放 iptables -A INPUT -p tcp --dport 2379 -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 2379 -j DROP iptables -A INPUT -p tcp --dport 2380 -s 10.0.0.0/8 -j ACCEPT iptables -A INPUT -p tcp --dport 2380 -j DROP
云上安全组示例(阿里云):
| 端口 | 协议 | 来源 | 描述 |
|---|---|---|---|
| 2379 | TCP | 10.0.0.0/24 | etcd client |
| 2380 | TCP | 10.0.0.0/24 | etcd peer |
| 2381 | TCP | 10.0.1.0/24 | etcd metrics |
附录:外置 etcd 集群的部署
在生产里,部分团队会拆 etcd 到独立机器(external topology)。这里给出完整的部署要点。
架构
+-------------+ +-------------+ +-------------+ | etcd-1 | | etcd-2 | | etcd-3 | | 10.0.0.11 | | 10.0.0.12 | | 10.0.0.13 | +------+------+ +------+------+ +------+------+ | | | +-----------------+-----------------+ | +----------+----------+ | kube-apiserver | | --etcd-servers=... | +---------------------+
etcd 机器配置建议:
CPU:8 核
内存:16GB
磁盘:NVMe SSD,RAID1(2 块盘镜像)
网络:万兆,节点间 RTT < 1ms
部署步骤(简化版)
# 1. 在每台 etcd 节点上下载 etcd 二进制 wget https://github.com/etcd-io/etcd/releases/download/v3.5.13/etcd-v3.5.13-linux-amd64.tar.gz tar xf etcd-v3.5.13-linux-amd64.tar.gz sudo cp etcd-v3.5.13-linux-amd64/etcd /usr/local/bin/ sudo cp etcd-v3.5.13-linux-amd64/etcdctl /usr/local/bin/ # 2. 生成证书(用 cfssl) cfssl gencert -initca ca-csr.json | cfssljson -bare ca cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json -hostname=etcd-1,etcd-2,etcd-3,10.0.0.11,10.0.0.12,10.0.0.13 -profile=kubernetes etcd-csr.json | cfssljson -bare etcd # 3. 写 systemd unit cat > /etc/systemd/system/etcd.service <<'EOF' [Unit] Description=etcd After=network-online.target Wants=network-online.target [Service] Type=notify ExecStart=/usr/local/bin/etcd --name=etcd-1 --data-dir=/var/lib/etcd --listen-client-urls=https://10.0.0.11:2379 --advertise-client-urls=https://10.0.0.11:2379 --listen-peer-urls=https://10.0.0.11:2380 --initial-advertise-peer-urls=https://10.0.0.11:2380 --initial-cluster=etcd-1=https://10.0.0.11:2380,etcd-2=https://10.0.0.12:2380,etcd-3=https://10.0.0.13:2380 --initial-cluster-token=etcd-cluster-1 --initial-cluster-state=new --quota-backend-bytes=8589934592 --auto-compaction-mode=periodic --auto-compaction-retention=8h --election-timeout=5000 --heartbeat-interval=500 --cert-file=/etc/etcd/pki/etcd.pem --key-file=/etc/etcd/pki/etcd-key.pem --trusted-ca-file=/etc/etcd/pki/ca.pem --peer-cert-file=/etc/etcd/pki/etcd.pem --peer-key-file=/etc/etcd/pki/etcd-key.pem --peer-trusted-ca-file=/etc/etcd/pki/ca.pem --listen-metrics-urls=http://0.0.0.0:2381 Restart=always RestartSec=5 LimitNOFILE=65536 [Install] WantedBy=multi-user.target EOF # 4. 启动 sudo systemctl daemon-reload sudo systemctl enable --now etcd # 5. 验证 ETCDCTL_API=3 etcdctl member list -w table --endpoints=https://10.0.0.11:2379 --cacert=/etc/etcd/pki/ca.pem --cert=/etc/etcd/pki/etcd.pem --key=/etc/etcd/pki/etcd-key.pem
外置 etcd 的运维基本和 stacked 模式一致,但有几个差异:
备份在 etcd 节点上做,不影响 apiserver。
升级需要单独维护 etcd 节点。
监控需要单独配 Prometheus 抓 etcd 节点。
附录:迁移到 external 拓扑
有些团队中途想从 stacked 改 external,可以按以下步骤:
# 1. 在 control-plane 节点上做 snapshot ETCDCTL_API=3 etcdctl snapshot save /backup/migrate.db --endpoints=https://127.0.0.1:2379 --cacert=... --cert=... --key=... # 2. 在新 external etcd 节点上 restore ETCDCTL_API=3 etcdctl snapshot restore /backup/migrate.db --name=etcd-1 --initial-cluster=etcd-1=https://10.0.0.11:2380,etcd-2=https://10.0.0.12:2380,etcd-3=https://10.0.0.13:2380 --initial-advertise-peer-urls=https://10.0.0.11:2380 --data-dir=/var/lib/etcd # 3. 启动新 etcd 集群 # 4. 改 kubeadm 配置的 etcd-servers sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml # --etcd-servers=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 # apiserver 会自动重启 # 5. 验证 ETCDCTL_API=3 etcdctl endpoint health --cluster --endpoints=https://10.0.0.11:2379 --cacert=... --cert=... --key=... kubectl get nodes kubectl get pods -A
风险提示:迁移期间 apiserver 会短暂不可用(约 1~2 分钟)。建议在业务低峰期做,并提前通知。
附录:etcd v2 和 v3 的关键差异
| 项目 | v2 | v3 |
|---|---|---|
| 数据存储 | 内存 + 文件 | bbolt(持久化) |
| API 协议 | HTTP+JSON | gRPC |
| 事务 | 不支持 | 支持(Txn) |
| Watch | 短轮询 | 长连接 |
| Lease | 不支持 | 支持(租约) |
| 范围查询 | 不支持 | 支持 |
| 数据量上限 | 2GB | 8GB~32GB |
生产里 K8s 1.22+ 全部走 v3,v2 API 已废弃。ETCDCTL_API=3 是必备环境变量。
附录:常见指标 baseline
新集群刚部署完,应该记录以下指标的 baseline,作为后续对比:
# 写延迟 histogram_quantile(0.99, rate(etcd_disk_wal_fsync_duration_seconds_bucket[5m])) histogram_quantile(0.99, rate(etcd_disk_backend_commit_duration_seconds_bucket[5m])) histogram_quantile(0.99, rate(etcd_server_proposal_apply_duration_seconds_bucket[5m])) # 流量 rate(etcd_network_client_grpc_received_bytes_total[5m]) rate(etcd_network_peer_received_bytes_total[5m]) rate(etcd_disk_writes_total[5m]) # 容量 etcd_debugging_mvcc_db_total_size_in_bytes etcd_debugging_mvcc_keys_total # 健康 etcd_server_has_leader etcd_server_leader_changes_seen_total
把这些指标在 24 小时、7 天、30 天的 P50 / P90 / P99 记录下来,写到运维 wiki。每次升级、迁移、调参后做对比,能发现回归问题。
全部0条评论
快来发表一下你的评论吧 !