☰
Kubernetes外部etcd集群二进制部署与调优实战
2026/10/9 3:23:13 网站建设 项目流程

直接说结论:Kubernetes 集群里 etcd 的地位,相当于人类的大脑加中枢神经。所有 Pod、Service、ConfigMap 的期望状态都存在它肚子里,apiserver 一旦跟它失联,整个集群就是“看得见摸不着”——kubectl 还能用,但调度、创建、删除全部瘫痪。我最早用 kubeadm 搭测试环境时没觉得 etcd 有什么了不起,直到一次磁盘 IO 抖动把 etcd 拖垮,整个集群进入只读模式,排障排到凌晨两点,才真正意识到:想在生产环境里把 Kubernetes 玩明白,外部 etcd 集群的部署和调优是绕不过去的一关。

这篇文章要聊的,就是怎么用纯二进制的方式,把一套多节点 etcd 集群搭起来,再让 kube-apiserver 稳稳地对接上去。它适合三类人:一是已经用 kubeadm 搭过集群、想进一步掌握底层组件的运维工程师;二是正在规划生产环境架构、需要考虑 etcd 独立部署的架构师;三是纯粹想搞懂 apiserver 与 etcd 之间到底是怎么通信的 Kubernetes 学习者。我会把证书签发、集群配置、systemd 托管、健康检查、故障排查这些环节全部拆开讲,包括那些文档里不会写的坑。

先给个全景:一套生产可用的外部 etcd 集群,需要解决三件事——节点间通信的信任关系(证书)、成员发现与 leader 选举(初始配置)、以及稳定性保障(参数调优与监控)。下面逐层拆解。

1. 为什么要把 etcd 挪出 kubeadm 的“默认路径”

1.1 先搞清楚 Kubernetes 与 etcd 的边界

很多人第一次接触 etcd 是在 kubeadm init 的输出里看到的,它把 etcd 做成了静态 Pod 塞进 kube-system 命名空间,看起来人畜无害。但静态 Pod 方式有一个本质问题:apiserver 和 etcd 的故障域没有分离。你执行kubectl drain维护节点时,kubelet 会把该节点上的静态 Pod 一并停掉,如果这台机器同时跑着 apiserver 和 etcd,结果就是管理面和控制面一起挂。

更隐蔽的问题在资源竞争上。kubeadm 默认把 etcd 和 kube-apiserver、kube-controller-manager 等组件塞在同一台机器,etcd 对磁盘 IO 和内存延迟极其敏感,而 apiserver 在业务高峰期会占用大量 CPU。两个敏感组件互相抢资源,表现就是 apiserver 响应变慢、etcd 的 fsync 耗时飙升,最终触发 etcd 的 leader 超时和选举抖动。

把 etcd 独立出来部署,本质上是在做故障域隔离和资源隔离。apiserver 挂了,etcd 还在,数据不丢;etcd 节点要维护,apiserver 还能继续服务存量流量(虽然写不了)。生产环境的可用性设计,基本都是围绕“缩小爆炸半径”展开的,外部 etcd 就是这个思路在存储层的落地。

1.2 kubeadm 静态 Pod 的隐藏风险

kubeadm 的静态 Pod 方案还有个容易被忽略的坑——升级与备份的不便。静态 Pod 的 manifest 文件由 kubeadm 生成,手改之后 kubeadm upgrade 经常会覆盖掉你的自定义参数,导致每次升级都要重新核对配置。etcd 的数据目录如果放在默认位置,做备份时还得先找到容器里挂载的宿主机路径,一旦有人不小心动了 mount 配置,备份脚本直接失效。

此外,把 etcd 运行在容器里意味着它依赖容器运行时。如果 containerd 或 dockerd 本身出问题,etcd 也会跟着挂。我在一个客户现场见过 containerd 的 device-mapper 元数据损坏,导致该节点上所有静态 Pod 起不来,包括 etcd——那真是叫天天不应。二进制部署的 etcd 是直接跑在 systemd 下的进程,跟容器运行时彻底解耦,排障链路少了一层,本质上更可控。

1.3 外部 etcd 的适用场景和收益

当然,不是所有场景都需要把 etcd 拆出来。单机测试、小型开发环境,kubeadm 默认方式反而省事。但以下场景我强烈建议走外部 etcd:

  • 集群规模超过几十个节点,或 etcd 存储的数据量超过 1-2 GB。
  • 需要独立扩缩容 etcd,比如从 3 节点扩到 5 节点。
  • 需要给 etcd 单独配置高性能磁盘(本地 NVMe SSD)和大内存。
  • 有多个 Kubernetes 集群需要共用一套 etcd 基础设施(虽然不太推荐,但确实有人这么干)。
  • 需要通过独立的备份恢复流程来保障数据安全。

收益也很直接:etcd 的故障不再直接拖垮 apiserver,你可以单独对 etcd 做高可用和容量规划,监控件也能分开部署,告警粒度更清晰。

2. 二进制部署的前置准备:方案选型与参数规划

2.1 为什么是二进制而不是容器

有人会问:etcd 装在容器里不是更符合云原生理念吗?客观说,容器化部署 etcd 本身没有错,很多云厂商的托管集群也是这么干的。但二进制方式在自建场景下有不可替代的优势:

  • 依赖最少。etcd 是一个纯 Go 写的静态编译二进制,不需要任何运行时依赖,拷过去就能跑。
  • 问题定位简单。journalctl -u etcd直接看日志,不需要kubectl logs或crictl logs绕一圈。
  • 方便固定版本和参数。容器镜像的 tag 在新版本变化时行为会有差异,比如 3.4 到 3.5 的启动参数就有不兼容的变更,二进制的版本管理更直观。

如果你的环境已经有完善的镜像仓库和镜像扫描流程,容器化也不是不行。但我个人维护生产集群的经验是:像 etcd 这种底层组件,部署方式越简单越好。KISS 原则在基础设施领域永远管用。

2.2 版本选型和拓扑规划

etcd 的版本选择要和 Kubernetes 版本匹配。kubeadm 在 1.22 之前默认用 etcd 3.4,1.22 之后逐步切到 3.5。这里有一个容易踩的坑:etcd 3.4 和 3.5 的 storage 版本不同,如果 kube-apiserver 的版本要求 etcd 3.5,而你在 3.4 上硬跑,会出现 “etcdserver: unsupported storage version” 之类的报错。

拓扑上,生产环境最少 3 节点,条件允许建议 5 节点。3 节点容忍 1 台故障,5 节点容忍 2 台故障。我用表格把关键参数列一下:

参数项推荐值说明
节点数量3 或 5奇数,避免脑裂平票
etcd 版本3.5.x与 K8s 1.22+ 匹配
数据目录独立磁盘建议 NVMe SSD,禁用 RAID 卡写缓存
心跳间隔100ms不要小于 50ms,容易误判
选举超时1000ms建议心跳的 10 倍
快照计数10000默认,触发压缩的阈值
磁盘 IO至少 1000 IOPS4K 随机写,fsync 延迟最好低于 10ms

网络方面,etcd 节点之间建议走独立的内网 VLAN,延迟控制在 5ms 以内。跨可用区部署时,注意 region 间的延迟如果超过 20ms,leader 选举会非常不稳定,这种情况更需要调大心跳和选举超时。

2.3 集群成员的通信参数说明

etcd 集群内部有两种通信:成员间的 peer 通信和客户端访问的 client 通信。peer 通信用于 raft 共识协议的数据同步和 leader 选举,client 通信用于 apiserver 读写数据。两者必须分开监听端口,默认分别是 2380 和 2379。

规划时一个常见误区是把两个端口暴露到同一张网卡。更稳妥的做法是:peer 端口绑定在节点间的内网地址上,client 端口绑定在 apiserver 能访问到的地址上。如果网络策略允许,client 端口也可以绑定在 VIP(虚拟 IP)之后,由负载均衡层转发,这样 apiserver 侧只需要配置一个稳定端点。

集群初始化时的成员发现方式,我建议用initial-cluster静态配置,而不是discovery服务。etcd 官方提供的 public discovery 服务在离线环境不可用,自建 discovery 还要额外维护一套服务,复杂度不划算。静态配置虽然需要手写所有节点地址,但对于 3-5 节点的集群完全够用,而且行为可预期。

3. 证书体系与密钥制作细节

3.1 需要哪几类证书

etcd 集群的 TLS 体系有四个维度:peer 之间的双向认证、client 访问时的认证、以及用于验证 client 身份的 CA。生产环境一定要启用 TLS,不然后果是你无法想象的——我曾经在一个内网环境用明文端口跑 etcd,某个容器因为环境变量配错,直接把整个前缀的数据清掉了,没有 TLS 的审计手段连谁干的都查不到。

具体需要生成的证书包括:

  • CA 根证书:etcd 内部签发的私有 CA,用于签发下面三类证书。
  • peer 证书:每个节点一份,用于节点间通信,必须包含该节点的所有 IP 和主机名。
  • client 证书:用于 apiserver 访问 etcd,给 kube-apiserver 用的,CN 一般设置为kube-apiserver。
  • 可选的 server 证书:如果 client 同时监听在其他地址,需要这个证书覆盖对应 IP。

3.2 自签 CA 与证书的生成实操

我一般用 cfssl 来签发,比 openssl 命令少写很多参数。先初始化 CA:

cat > ca-csr.json <<EOF { "CN": "etcd-ca", "key": { "algo": "rsa", "size": 2048 }, "names": [ { "C": "CN", "ST": "Beijing", "L": "Beijing", "O": "etcd", "OU": "etcd-security" } ] } EOF cfssl gencert -initca ca-csr.json | cfssljson -bare ca

这步生成 ca.pem 和 ca-key.pem。接下来签发每个节点的 peer 证书,注意 hosts 字段要写全该节点的 IP、主机名、以及 127.0.0.1,否则后面启动时经常报 “certificate is valid for xxx, not yyy” 的校验错误。

cat > etcd-peer.json <<EOF { "CN": "etcd-1", "key": { "algo": "rsa", "size": 2048 }, "hosts": [ "10.0.0.11", "etcd-1", "127.0.0.1" ] } EOF cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json \ -profile=peer etcd-peer.json | cfssljson -bare etcd-1-peer

同样的方式给所有节点各生成一份,再把 client 证书生成一份给 apiserver 用:

cat > etcd-client.json <<EOF { "CN": "kube-apiserver", "key": { "algo": "rsa", "size": 2048 } } EOF cfssl gencert -ca=ca.pem -ca-key=ca-key.pem -config=ca-config.json \ -profile=client etcd-client.json | cfssljson -bare etcd-client

每个节点的证书和三份 CA 文件都拷到/etc/etcd/pki/目录,注意ca-key.pem只在签发机上保留,节点上绝对不要放。一旦节点被攻破,攻击者拿到 ca-key 就能签发任意证书,整个集群的信任链就废了。

3.3 证书校验容易踩的三个坑

第一,hosts字段漏掉 IP。etcd 在启动时会对等端证书做 hostname 校验,如果对端连接的地址是 IP 而证书里没这个 IP,握手直接失败,日志显示 “tls: failed to verify certificate: x509: cannot validate certificate for 10.0.0.12 because it doesn't contain any IP SANs”。这种问题不加 IP SAN 根本无法绕开。

第二,cfssl的 profile 配置里usages要区分 peer 和 client。peer 证书必须包含serverAuth和clientAuth,client 证书只需要clientAuth。配置写错,启动后 listener 会报 “unsupported certificate usage”。

第三,证书的CN要和 apiserver 的--etcd-certfile期望一致。apiserver 对接时默认不做 CN 校验,但很多高安全等级环境会开启--etcd-servers-overrides的客户端认证,此时 CN 不匹配会直接认证失败。建议统一用kube-apiserver作为 client 证书的 CN。

4. etcd 二进制部署的完整实操

4.1 安装与目录初始化

先去官网下载 etcd 二进制,我用的是 3.5.13 版本(3.5 系列建议选最新的 patch 版本,很多稳定性修复都在 patch 里)。下载后把 etcd 和 etcdctl 两个二进制放到/usr/local/bin/,然后创建数据目录:

mkdir -p /var/lib/etcd mkdir -p /etc/etcd/pki chmod 700 /var/lib/etcd

数据目录权限一定要设成 700,etcd 会校验目录权限,太开放会拒绝启动。数据目录和生产数据文件最好放在独立的挂载点上,比如/data/etcd,避免根分区和其他日志抢占 IO。

4.2 配置文件与 systemd 单元

etcd 支持用 YAML 或命令行参数配置,我习惯用命令行参数写进 systemd 单元文件,这样systemctl cat etcd一眼就能看到完整参数。下面是单个节点的单元文件,三台节点只有 IP 和节点名不同:

[Unit] Description=etcd key-value store Documentation=https://github.com/etcd-io/etcd After=network.target ConditionPathExists=/var/lib/etcd [Service] Type=notify ExecStart=/usr/local/bin/etcd \ --name=etcd-1 \ --data-dir=/var/lib/etcd \ --initial-advertise-peer-urls=https://10.0.0.11:2380 \ --listen-peer-urls=https://10.0.0.11:2380 \ --advertise-client-urls=https://10.0.0.11:2379 \ --listen-client-urls=https://10.0.0.11:2379,https://127.0.0.1:2379 \ --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-state=new \ --initial-cluster-token=etcd-k8s-cluster \ --cert-file=/etc/etcd/pki/etcd-1.pem \ --key-file=/etc/etcd/pki/etcd-1-key.pem \ --peer-cert-file=/etc/etcd/pki/etcd-1-peer.pem \ --peer-key-file=/etc/etcd/pki/etcd-1-peer-key.pem \ --peer-client-cert-auth=true \ --peer-trusted-ca-file=/etc/etcd/pki/ca.pem \ --client-cert-auth=true \ --trusted-ca-file=/etc/etcd/pki/ca.pem \ --auto-compaction-retention=2 \ --quota-backend-bytes=8589934592 Restart=on-failure RestartSec=5 LimitNOFILE=65536 TimeoutStartSec=0 [Install] WantedBy=multi-user.target

这里逐个解释关键参数:Type=notify让 systemd 等待 etcd 发出 readiness 通知,避免了启动时序误判;initial-cluster-state=new表示这是新集群,如果配置成existing而集群还没初始化,会直接失败;initial-cluster-token是集群的标识,不同集群必须用不同 token,否则节点会尝试加入错误的集群。

关于quota-backend-bytes,这是 etcd 的存储配额,默认 2GB 太低,K8s 集群很容易打满。打满之后 etcd 会拒绝写入,集群直接进只读模式。我一般设置成 8GB(即 8589934592),同时开启auto-compaction-retention=2,让 etcd 每两小时自动压缩历史版本。

4.3 启动与健康检查

三个节点都配置好后,依次启动:

systemctl daemon-reload systemctl enable etcd systemctl start etcd

启动后用etcdctl检查集群健康状态。注意 3.5 版本的 etcdctl 默认走 gRPC,需要显式指定端点:

export ETCDCTL_API=3 export ETCDCTL_ENDPOINTS=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 export ETCDCTL_CACERT=/etc/etcd/pki/ca.pem export ETCDCTL_CERT=/etc/etcd/pki/etcd-client.pem export ETCDCTL_KEY=/etc/etcd/pki/etcd-client-key.pem etcdctl member list etcdctl endpoint health --cluster etcdctl endpoint status --cluster

endpoint status输出的Raft Index三个节点应该接近或一致,代表数据同步正常。如果某个节点的Raft Index落后很多,说明这个节点负载过高或者网络有问题,需要尽早排查。

4.4 从 3 节点扩到 5 节点

集群运行一段时间后想加节点,操作顺序很关键。先加入空节点,再补数据,顺序反了会报成员加入失败。

先在已有节点上执行member add:

export ETCDCTL_ENDPOINTS=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 etcdctl member add etcd-4 --peer-urls=https://10.0.0.14:2380

然后在新节点上配置 systemd,注意initial-cluster-state这个参数要改成existing,并且initial-cluster列表里要把现有三个节点和新增节点都写上。启动后查看成员状态:

etcdctl member list

这里有个细节:新节点加入后数据需要从 leader 全量同步,如果历史数据很大,同步期间新节点的Raft Index会短暂落后,这是正常的。但如果长时间追不上,看下磁盘 IO 是不是被打满,或者 peer 端口是否有丢包。

5. kube-apiserver 对接外部 etcd 的关键配置

5.1 apiserver 侧的证书和参数

etcd 集群起来后,要修改 kube-apiserver 的启动参数。kubeadm 部署的话,编辑/etc/kubernetes/manifests/kube-apiserver.yaml,把原来的 etcd 相关参数替换掉:

spec: containers: - command: - kube-apiserver - --etcd-servers=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 - --etcd-cafile=/etc/kubernetes/pki/etcd/ca.pem - --etcd-certfile=/etc/kubernetes/pki/etcd/client.pem - --etcd-keyfile=/etc/kubernetes/pki/etcd/client-key.pem

用 kubeadm 的证书路径有个好处:目录结构已经固定,替换证书文件时只需要覆盖对应文件。如果你用二进制方式部署 apiserver,把证书放在自己的 pki 目录就行,核心是三个参数:--etcd-servers指定端点列表、--etcd-cafile指定 CA 用于验证 etcd 服务端证书、--etcd-certfile和--etcd-keyfile提供客户端证书完成双向认证。

apiserver 会并发连接所有 etcd 端点,读写请求会自动发到 leader。这里需要注意,apiserver 不会做跨 etcd 集群的负载均衡,它只是按照 etcd 的 leader 重定向机制工作,所以端点列表顺序不重要。

5.2 验证对接是否成功

改完 apiserver 配置后,kubelet 会自动拉起新的 apiserver Pod,观察日志确认无 etcd 连接错误:

kubectl -n kube-system logs kube-apiserver-<node-name> | grep -i etcd

看到etcdserver: request timed out就是连接有问题,看到successfully contacted etcd说明对接成功。再用实际的 K8s 操作验证读写链路:

kubectl create namespace test-etcd kubectl -n test-etcd create cm demo --from-literal=key=value kubectl get cm -n test-etcd

最后到 etcd 侧直接查一下数据,确认 apiserver 的写入真实落到了 etcd:

etcdctl get /registry/namespaces/test-etcd --prefix

如果能返回命名空间对象,说明全链路已打通。这个验证方法我在每次新集群上线时都会跑一遍,它同时验证了 apiserver 到 etcd 的 TLS 链路和数据序列化格式,是 K8s 控制面健康检查里最直接的测试。

6. 线上部署的常见故障与排查实录

6.1 故障速查表

下面这些故障我都在真实环境遇到过,整理成表方便对照排查:

现象可能原因处理方式
etcdserver: request timed out网络延迟高、磁盘 IO 慢检查节点延迟,用etcdctl endpoint status看耗时,必要时调大心跳间隔
failed to connect to peer证书 IP SAN 不匹配或 peer 端口不通用openssl x509 -in peer.pem -text -noout查看证书 SAN,确认防火墙放行 2380
no leader或选举频繁心跳间隔太短、节点间网络抖动增加--heartbeat-interval到 200-300ms,--election-timeout到 2000-3000ms
apply request took too long磁盘 fsync 过慢换 SSD,检查 mount 参数是否带了barrier=1,必要时调整 RAID 策略
mvcc: database space exceeded配额打满立即开启压缩,etcdctl compact,清理不必要的历史数据,调大quota-backend-bytes
etcdserver: invalid auth tokenclient 证书过期重新签发 client 证书并更新 apiserver 文件
failed to load CA certificateCA 文件路径或权限错误确认 etcd 进程用户能读证书文件,目录权限不要用 600 以下

6.2 几个我自己踩过的典型坑

第一个坑是Type=notify配合旧版本 etcd 的兼容问题。etcd 3.4 之前的版本不原生支持 sd_notify,需要额外配置--enable-v2=true之类的参数,否则 systemd 会一直等待通知超时。解决方法要么升到 3.5,要么把Type改成simple并在 ExecStart 前加 sleep。我在 3.4 上吃过这个亏,当时还不明白为什么systemctl start etcd卡住不动,查 journal 才发现是 notify 机制的问题。

第二个坑是--listen-client-urls没有绑定 127.0.0.1。etcdctl 默认连接http://localhost:2379,如果只绑定了内网 IP,本机执行 etcdctl 需要显示指定端点,很多调试脚本因此莫名失败。更稳妥的做法是把 loopback 地址也加进监听列表,既不影响外部访问,本机调试也方便。

第三个坑是压缩参数的设置。--auto-compaction-retention只控制历史版本数据的保留时间,它并不会主动压缩 compact 后的数据文件大小。如果需要彻底释放磁盘空间,还得配合etcdctl defrag或者开启在线碎片整理。我在一个长期运行的集群里发现数据目录虚胖到 20GB,但实际有效数据只有 2GB,就是因为只开了自动压缩没做碎片整理。

第四个坑是关于 v2 API。etcd 3.5 已经默认禁用了 v2 存储和 gRPC gateway 的 v2 接口,如果某些老旧监控组件还在用 v2 协议访问,会直接报404。排查这种问题,先看客户端日志里请求的 URL 是不是/v2/keys/开头,如果是,就得升级监控组件到 v3 协议。

6.3 备份与恢复的兜底方案

部署完集群不等于万事大吉,etcd 的备份必须纳入日常运维。我建议至少每天做一次快照备份,同时保留最近 7 天的备份文件。用 etcdctl 做快照很简单:

etcdctl snapshot save /backup/etcd-snapshot-$(date +%Y%m%d).db

恢复时,先停掉所有 etcd 节点,在单节点模式下恢复,再把其他节点重新加入。详细的恢复流程我就不展开写了,但有一个关键点必须提醒:恢复操作一定要在测试环境先演练一遍,尤其是数据量大的集群,恢复过程可能因为网络或磁盘性能慢得像蜗牛,这种体验只有练过才知道。

7. 生产环境部署的几个额外建议

关于 etcd 集群的监控,我强烈建议把etcd_server_leader_changes_seen_total、etcd_disk_wal_fsync_duration_seconds、etcd_server_slow_apply_total这三个指标纳入重点告警。第一个指标反映 leader 切换频率,第二个反映磁盘同步延迟,第三个反映 apply 耗时。这三个指标同时异常时,etcd 集群的稳定性基本就到头了,必须提前介入。

安全层面,etcd 节点不要暴露公网端口。哪怕是云环境,安全组也只放行来源为 Kubernetes 节点和管理网段的流量。etcd 存储的是集群的全部状态,包括 Secret 对象的明文(只是 base64 编码),一旦泄露,等于把整个集群的凭证都交给别人。有条件的话,还可以给 etcd 启用--auth功能搭配 RBAC,限制不同角色对键空间的访问。

资源规划上,3 节点的 etcd 集群建议每台节点至少 4 核 8GB 内存,独立的数据盘容量按quota-backend-bytes的 3 倍规划。etcd 的内存占用和历史版本数量强相关,如果你的集群变更频繁、历史数据多,内存加到 16GB 也不夸张。观察 etcd 进程的 RSS 如果持续上升且压缩无法回收,重点考虑是不是有组件在频繁列出大对象。

最后再分享一个小技巧:etcd 的日志默认打到 stderr,通过 journald 采集。线上排查时,先执行journalctl -u etcd --since "1 hour ago" -p warning看有没有周期性告警,再做深入分析。很多看似诡异的故障,早期的 warning 日志里其实都写了线索。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询