深度解析K8s etcd运维手册

描述

问题背景

生产里见过几次 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. 检查网络延迟
ping 
mtr 

# 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。每次升级、迁移、调参后做对比,能发现回归问题。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分