有段时间我一直在用 kubeadm 搭 Kubernetes 集群,stacked etcd 确实省事,但每次上生产环境心里总有点没底。后来我终于把 etcd 单独拆出来,用二进制方式部署了一套三节点的外部 etcd 集群,再让 kube-apiserver 通过 TLS 对接上去。整套控制平面的稳定性和可维护性都明显上了一个台阶。如果你也想搞清 Kubernetes 控制平面的底层数据链路,或者需要一套可以独立扩缩容、独立备份恢复的存储组件,这篇二进制部署指南应该能帮你省下不少试错的时间。
我先把话放前头:外部 etcd 的二进制部署本身并不神秘,真正的难点集中在三块——证书体系的规划、配置文件的理解、以及集群成员关系的维护。把这三块啃下来,剩下的就是顺着步骤往下走。
1. 为什么把 etcd 从 Kubernetes 里拆出来:外部 etcd 的设计价值
1.1 stacked etcd 与 external etcd 怎么选
熟悉 kubeadm 的人都知道,默认装出来的是 stacked etcd,也就是控制平面节点上既跑 kube-apiserver,又跑 etcd 成员。这样的好处是省机器、部署简单,kubeadm 一条龙就能把证书、服务、静态 Pod 全搞定。但生产环境里我会优先把它拆开。
原因其实很直白。第一,故障域隔离。stacked 模式下,控制平面节点一旦出问题,kube-apiserver 和 etcd 是一起挂的,你很难快速判断到底是应用层崩溃还是存储层不可用。第二,升级风险耦合。Kubernetes 版本升级时如果连 etcd 二进制一起动,任何一个兼容性问题都会直接影响控制平面。第三,资源竞争。etcd 是 IO 敏感型组件,和 kube-apiserver 抢 CPU、抢磁盘带宽,时间长了性能抖动很难排查。
外部 etcd 集群把这些麻烦都隔离开了。etcd 跑在独立的机器上,数据目录、系统盘、网络策略都可以单独规划。更关键的是,外部 etcd 可以跨集群复用——同一套 etcd 集群理论上可以被多个 Kubernetes 控制平面共享(实际生产里我不建议这么干,但故障迁移场景下这个能力很值钱)。所以我的结论是:测试环境你用 stacked 没毛病,生产环境、需要长期运维的集群,老老实实上外部 etcd。
1.2 外部 etcd 集群的整体拓扑与组件分工
一套典型的外部 etcd 方案,节点角色大致是这样的:
- 3 台 etcd 节点:跑 etcd 二进制,负责存储 Kubernetes 的集群状态数据,端口 2379 提供客户端访问,端口 2380 做成员间 peer 通信。
- 2 台或 3 台控制平面节点:跑 kube-apiserver、kube-controller-manager、kube-scheduler,通过 2379 端口读写 etcd。
注意,我这里说的控制平面节点是“用二进制或手动部署的”,不是 kubeadm 管理的。如果非要用 kubeadm,也可以用--external-etcd的方式跳过内置 etcd 组件的安装,但证书和配置文件仍然需要你自己准备。本文的方案完全不依赖 kubeadm,apiserver 以二进制或者 systemd 方式托管,适合理解每一层链路的人。
组件之间的数据链路是这样的:kube-apiserver 是唯一直接读写 etcd 的 Kubernetes 组件。它拿到 API 请求之后,会把资源对象序列化后写入 etcd 的/registry路径下。所有 watch、list、create、update 操作,最终都落到 etcd 的事务和线性一致性保证上。所以 etcd 集群的健康状态,直接决定整个控制平面能不能对外提供稳定的 API 服务。
这也是为什么我在部署时格外强调证书、成员关系、磁盘性能和快照备份这四个点。它们不是锦上添花,而是保命用的。
2. 部署前的准备:环境规划与版本选型
2.1 机器规划:三个角色各司其职
先说机器数量。etcd 集群我强烈建议奇数节点,最少 3 个。为什么是奇数?因为 etcd 的 Raft 协议要求多数派存活才能选主和对外提供服务,3 节点集群允许挂 1 个,5 节点集群允许挂 2 个。你用偶数节点,比如 4 个,故障容忍上限和 3 个一样,还白白多维护一台机器,没有任何好处。
以 3 节点为例,我给一个可以直接抄的规划:
| 主机名 | IP | 角色 |
|---|---|---|
| k8s-etcd-1 | 10.0.0.11 | etcd member,数据目录独立磁盘 |
| k8s-etcd-2 | 10.0.0.12 | etcd member,数据目录独立磁盘 |
| k8s-etcd-3 | 10.0.0.13 | etcd member,数据目录独立磁盘 |
| k8s-master-1 | 10.0.1.11 | kube-apiserver、controller-manager、scheduler |
| k8s-master-2 | 10.0.1.12 | kube-apiserver、controller-manager、scheduler |
这里有个容易忽略的点:etcd 机器不要复用成 Kubernetes 的工作节点。很多人图省事,把 etcd 塞到已有的 worker 上,结果 Pod 频繁重建导致磁盘 IO 抖动,直接拖垮整个控制平面的写入延迟。等你在生产环境被坑一次就会记住:存储组件一定要有自己的地盘。
操作系统层面我用的是 CentOS 7.9 和 Ubuntu 20.04 都跑过,本文命令以 CentOS 系为主,Ubuntu 上把包管理器换成 apt 即可。数据盘建议单独挂载,文件系统用 xfs 或者 ext4 都行,但务必打开noatime挂载参数,减少不必要的元数据写入。
2.2 基础环境优化:时间、DNS、内核参数与磁盘
etcd 的 Raft 协议对时钟偏差非常敏感,节点间时间差太大会导致选举异常。所以第一步就是统一时间同步。我用 chrony 比较多,配置也很简单:
yum install -y chrony systemctl enable --now chronyd chronyc sources -v确认chronyc能看到^*开头的源,就说明已经同步上了。三台 etcd 节点建议都指向同一个时间源,别让它们各自漂移。
接着是 hosts 解析。etcd 配置文件里可以写 IP,也可以写主机名,但成员通信和证书 SAN 校验环节经常会用到主机名,所以我建议三台机器彼此把 hosts 写全:
cat >> /etc/hosts <<EOF 10.0.0.11 k8s-etcd-1 10.0.0.12 k8s-etcd-2 10.0.0.13 k8s-etcd-3 EOF内核参数这块,我实测下来影响最大的是文件句柄和 TCP 保活。etcd 在大量 watch 连接下会占用非常多 fd,必须调大:
cat >> /etc/sysctl.conf <<EOF fs.file-max = 2097152 net.ipv4.tcp_keepalive_time = 600 net.core.somaxconn = 32768 vm.swappiness = 10 EOF sysctl -pvm.swappiness调低是为了避免内存回收时把 etd 的热数据换到 swap,虽然生产服务器一般不开 swap,但万一开了,这个参数能救命。磁盘方面,有条件上 NVMe SSD 就别用机械盘。数据目录所在磁盘的 IO 调度器可以调整为 none 或 noop,对 SSD 来说能降低延迟抖动。确认调度器的方法:
cat /sys/block/sdb/queue/scheduler如果是mq-deadline,可以通过内核启动参数或者运行时修改,但运行时修改重启会失效,所以我通常直接在 grub 里设置,一劳永逸。
2.3 版本选型:etcd 版本与 Kubernetes 怎么匹配
版本匹配是个容易被忽略的大坑。etcd 的 API 版本和 Kubernetes 控制平面的兼容性是有边界的,你拿一个特别老的 etcd 配全新的 Kubernetes,大概率会出现请求报错或者 watch 异常。我的经验是,以 Kubernetes 官方文档里标注的 etcd 版本为准,不要随意升级。
以我常用的环境为例:Kubernetes 1.28 系列对应 etcd 3.4.28 以上,3.5.x 也完全没问题;Kubernetes 1.24 到 1.27 用 etcd 3.5.x 是主流选择。本文操作以etcd v3.5.10为例,如果你用的是 3.4.x,配置文件语法基本一致,但部分命令参数有细微差别。
回到二进制部署之前,一定要先在官网的 GitHub Releases 页面下载对应版本的压缩包。etcd 的发布包命名很规律,etcd-v3.5.10-linux-amd64.tar.gz,里面包含etcd和etcdctl两个二进制,没有别的花头。下载时可以顺手把 sha256 校验也做了,防止下载损坏。
3. 证书体系设计:二进制部署绕不过的第一道坎
3.1 证书角色与信任关系设计
外部 etcd 的二进制部署没有 kubeadm 帮你自动签证书,所有 TLS 证书都得自己规划。我把涉及的证书角色理清楚:
- CA 证书:一个自签的根 CA,etcd 内部所有证书都信任它。
- etcd server 证书:给 etcd 的 2379 客户端端口用,kube-apiserver 访问 etcd 时要校验这个证书的有效性。
- etcd peer 证书:给 etcd 的 2380 成员间通信用,节点之间互相验证身份。
- kube-apiserver 访问 etcd 的客户端证书:kube-apiserver 作为客户端连接 etcd 时出示的证书。
这几个角色缺一不可。证书签发时有一个非常关键的细节:etcd server 证书和 peer 证书的 SAN(Subject Alternative Name)必须包含所有 etcd 节点的 IP 和主机名。因为 etcd 节点间的 peer 通信是全互联的,任何一台都可能作为客户端去访问另一台的 2379 或 2380,SAN 不全就会在 TLS 握手阶段被拒。
我见过太多人在这里翻车:证书里只写了本机 IP,结果节点 A 访问节点 B 时报x509: certificate is valid for 10.0.0.11, not 10.0.0.12。所以规划 SAN 时,直接把三台机器的 IP、hostname、127.0.0.1、localhost全部写进去,宁可多不要少。
3.2 使用 OpenSSL 签发 CA 与 etcd 证书
证书签发我习惯用 OpenSSL。先建一个工作目录,比如/opt/certs,所有操作都在里面完成。
第一步,生成 CA 私钥和自签 CA 证书:
mkdir -p /opt/certs && cd /opt/certs openssl genrsa -out etcd-ca.key 2048 openssl req -x509 -new -nodes -key etcd-ca.key \ -subj "/CN=kubernetes-etcd-ca" \ -days 3650 \ -out etcd-ca.crtCA 证书的有效期我习惯给 10 年,etcd 节点证书给 2 年,到期前再批量轮换。轮换是个麻烦事,所以不建议给节点证书也签 10 年,缩短周期可以减少私钥泄露后的影响范围。
第二步,生成 etcd server 证书的私钥和 CSR。先创建 OpenSSL 扩展文件,把 SAN 写进去:
cat > etcd-server-openssl.cnf <<EOF [ req ] req_extensions = v3_req distinguished_name = req_distinguished_name [ req_distinguished_name ] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @alt_names [ alt_names ] IP.1 = 10.0.0.11 IP.2 = 10.0.0.12 IP.3 = 10.0.0.13 IP.4 = 127.0.0.1 DNS.1 = k8s-etcd-1 DNS.2 = k8s-etcd-2 DNS.3 = k8s-etcd-3 DNS.4 = localhost EOF这里我同时加上了serverAuth和clientAuth的 extendedKeyUsage,因为 etcd 的 server 证书也会被拿去作为客户端连接其他成员,双用途能省一张证书。然后签发:
openssl genrsa -out etcd-server.key 2048 openssl req -new -key etcd-server.key \ -subj "/CN=etcd-server" \ -out etcd-server.csr openssl x509 -req -in etcd-server.csr \ -CA etcd-ca.crt -CAkey etcd-ca.key -CAcreateserial \ -out etcd-server.crt -days 730 \ -extfile etcd-server-openssl.cnf -extensions v3_req第三步,签发 peer 证书,流程几乎一样,只是 CN 和文件名改一下。peer 证书的 SAN 同样要覆盖全部节点 IP 和主机名:
cat > etcd-peer-openssl.cnf <<EOF [ req ] req_extensions = v3_req distinguished_name = req_distinguished_name [ req_distinguished_name ] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = serverAuth, clientAuth subjectAltName = @alt_names [ alt_names ] IP.1 = 10.0.0.11 IP.2 = 10.0.0.12 IP.3 = 10.0.0.13 IP.4 = 127.0.0.1 DNS.1 = k8s-etcd-1 DNS.2 = k8s-etcd-2 DNS.3 = k8s-etcd-3 DNS.4 = localhost EOF openssl genrsa -out etcd-peer.key 2048 openssl req -new -key etcd-peer.key \ -subj "/CN=etcd-peer" \ -out etcd-peer.csr openssl x509 -req -in etcd-peer.csr \ -CA etcd-ca.crt -CAkey etcd-ca.key -CAcreateserial \ -out etcd-peer.crt -days 730 \ -extfile etcd-peer-openssl.cnf -extensions v3_req签发完记得校验一下证书内容:
openssl x509 -in etcd-server.crt -text -noout | grep -A5 "Subject Alternative Name"确认 SAN 里有全部 IP 再往下走。这一步不缺,后面能省一整晚的排障时间。
3.3 为 kube-apiserver 签发访问 etcd 的客户端证书
apiserver 访问 etcd 时,需要一个专门的客户端证书。这个证书的 CN 一般写成kube-apiserver或者其他能标识身份的名字,extendedKeyUsage 只需要clientAuth。签发命令:
cat > etcd-client-openssl.cnf <<EOF [ req ] req_extensions = v3_req distinguished_name = req_distinguished_name [ req_distinguished_name ] [ v3_req ] basicConstraints = CA:FALSE keyUsage = nonRepudiation, digitalSignature, keyEncipherment extendedKeyUsage = clientAuth EOF openssl genrsa -out etcd-client.key 2048 openssl req -new -key etcd-client.key \ -subj "/CN=kube-apiserver" \ -out etcd-client.csr openssl x509 -req -in etcd-client.csr \ -CA etcd-ca.crt -CAkey etcd-ca.key -CAcreateserial \ -out etcd-client.crt -days 730 \ -extfile etcd-client-openssl.cnf -extensions v3_req注意,etcd 服务端配置了client-cert-auth: true之后,握手时不仅要求客户端出示证书,还会校验这个证书是不是由受信任的 CA 签发的。所以 kube-apiserver 那边必须同时提供--etcd-certfile和--etcd-keyfile,并且把 CA 证书通过--etcd-cafile传过去。三者缺一不可,只传证书不传 CA 或者只传 CA 不传客户端证书,都会导致连接失败。
3.4 证书分发与安全加固
证书签发完成后,分发路径我建议这样规划:
- 每台 etcd 节点需要:
etcd-ca.crt、etcd-server.crt、etcd-server.key、etcd-peer.crt、etcd-peer.key。 - 每台 kube-apiserver 节点需要:
etcd-ca.crt、etcd-client.crt、etcd-client.key。
我把证书统一放在各节点的/etc/etcd/pki目录下,etcd 服务运行用户是etcd,所以私钥文件权限设置为600,属主为etcd。证书文件可以是644。分发用 scp 或者 ansible 都行,但传完之后一定要做三件事:校验文件完整性、检查属主属组、确认私钥权限。
chown -R etcd:etcd /etc/etcd chmod 600 /etc/etcd/pki/*.key chmod 644 /etc/etcd/pki/*.crt另外强烈建议把 CA 私钥etcd-ca.key离线保存,或者至少单独放一台不对外提供服务的机器上。CA 私钥泄露等于整个 etcd 集群的 TLS 信任链崩溃,攻击者可以伪造任意成员证书,后果非常严重。生产环境里这个私钥我甚至会用加密介质保存,签发证书时才取出来。
4. etcd 二进制部署全过程:从下载到集群健康
4.1 下载并安装 etcd 二进制
三台 etcd 节点都要安装二进制。先把压缩包解压到/opt,再把可执行文件放到/usr/local/bin:
cd /opt wget https://github.com/etcd-io/etcd/releases/download/v3.5.10/etcd-v3.5.10-linux-amd64.tar.gz tar -xzvf etcd-v3.5.10-linux-amd64.tar.gz cp etcd-v3.5.10-linux-amd64/etcd /usr/local/bin/etcd cp etcd-v3.5.10-linux-amd64/etcdctl /usr/local/bin/etcdctl chmod +x /usr/local/bin/etcd /usr/local/bin/etcdctl etcd --versionetcd --version能正常打印版本号,说明二进制没问题。接着创建 etcd 运行用户和数据目录:
useradd -r -s /sbin/nologin etcd mkdir -p /var/lib/etcd chown -R etcd:etcd /var/lib/etcd数据目录这里有个实用经验:先不要急着挂大容量磁盘,etcd 的数据增长虽然不快,但快照和 WAL 文件会持续占用空间。我习惯在部署时就把数据盘挂到/var/lib/etcd下,并且用du定期观察增长趋势。默认 2GB 的历史 compaction 保留策略下,一个中等规模的 Kubernetes 集群一年大概能涨几个 GB 到几十 GB,量级取决于资源数量变化频率。
4.2 编写 etcd 配置文件
etcd 支持命令行参数和配置文件两种方式。配置文件更清晰、更好维护,我强烈推荐。在每台节点上写/etc/etcd/etcd.conf.yml,以节点 1 为例:
# 节点 1 配置 name: etcd-1>[Unit] Description=etcd service After=network-online.target Wants=network-online.target [Service] Type=notify User=etcd Group=etcd ExecStart=/usr/local/bin/etcd --config-file=/etc/etcd/etcd.conf.yml Restart=on-failure RestartSec=5 LimitNOFILE=65536 TimeoutStartSec=0 [Install] WantedBy=multi-user.target注意Type=notify,这个配置要求 etcd 启动完成后通过 sd_notify 通知 systemd,而不是让 systemd 自己判断进程存活。etcd 3.4 以上版本支持这个模式,好处是 systemd 能拿到 etcd 真正 ready 的信号,避免出现"进程起来了但集群还没就绪"的间隙。
systemctl daemon-reload systemctl enable etcd systemctl start etcd systemctl status etcd -lLimitNOFILE必须调大,原因前面说过,etcd 在大量客户端连接和 watch 场景下文件描述符消耗非常快。默认的 1024 根本不够用,压测时经常会看到too many open files报错。
4.4 逐个启动成员并验证集群状态
这里是我踩过最深的坑之一:不要三台同时启动。虽然 etcd 设计上支持同时启动时的自动选主,但实际操作中同时启动容易受到证书、网络、系统初始化速度不一致的影响,导致选举超时或成员注册失败。
我的做法是:先启动第一台,确认它自己能成为 leader(单节点时必然选主成功),再启动第二台,等它加入集群,再启动第三台。每次启动观察日志和集群成员状态:
systemctl start etcd journalctl -u etcd -f第一台起来后,在任意节点上用 etcdctl 查看成员列表。注意 etcdctl 访问需要指定证书:
export ETCDCTL_API=3 etcdctl --endpoints=https://10.0.0.11:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ member list -w table如果配置正确,你会看到一个成员处于unstarted状态,这是正常的。等第二台、第三台启动后,再执行member list,应该能看到三个成员,状态都是started。
集群健康检查用这个命令:
etcdctl --endpoints=https://10.0.0.11:2379,https://10.0.0.12:2379,https://10.0.0.13:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ endpoint health --cluster -w table输出里每个 endpoint 一行,healthy表示正常,unhealthy表示有问题。看到三行healthy的时候,etcd 集群这层就算立住了。
5. 让 kube-apiserver 对接外部 etcd
5.1 kube-apiserver 对接 etcd 的关键参数
etcd 集群就绪后,接下来就是把 kube-apiserver 指到它上面。不管你是直接用二进制启动 kube-apiserver,还是用 kubeadm 的配置文件生成静态 Pod,真正核心的参数是这四个:
--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.crt --etcd-certfile=/etc/kubernetes/pki/etcd-client.crt --etcd-keyfile=/etc/kubernetes/pki/etcd-client.key--etcd-servers指定 etcd endpoints,多个地址用逗号分隔。apiserver 会自己负载均衡读请求,写请求只会发给当前 leader,不需要你干预。--etcd-cafile指向 CA 证书,用来校验 etcd 服务端证书;--etcd-certfile和--etcd-keyfile就是前面签发的客户端证书和私钥。
如果你用的是 systemd 直接托管 kube-apiserver,ExecStart 里加这些参数即可。如果是 kubeadm 方式,需要在kubeadm-config里指定etcd.external配置块,然后把四个参数填进去。kubeadm 检测到etcd.external时,就不会再生成内置 etcd 的静态 Pod。
还有两个和 etcd 相关的参数经常被忽略。--etcd-prefix默认是/registry,一般不用动。--storage-backend默认走 etcd3,如果你的 apiserver 版本很老,可能还需要显式指定,新版本已经默认了。我从 1.20 之后就没再手动设置过这个参数。
5.2 启动 kube-apiserver 并验证数据读写
kube-apiserver 启动后,可以通过日志确认它已经连上 etcd。启动时日志里如果出现etcdserver: request timed out或者context deadline exceeded,说明网络或证书还有问题。如果顺利,你会看到 apiserver 开始正常处理/healthz请求。
验证数据读写最直接的办法是往 Kubernetes 里创建一个资源,然后去 etcd 里看数据是否落盘。比如创建 namespace:
kubectl create namespace demo然后在 etcd 节点上读取对应 key:
etcdctl --endpoints=https://10.0.0.11:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ get /registry/namespaces/demo --prefix -w json能查到demo这个 namespace 对象,说明 apiserver 到 etcd 的写入链路已经通了。再执行一次kubectl get ns demo,如果 apiserver 状态正常,读取链路也没问题。
这里我要提醒一个容易混淆的点:etcdctl 里用的证书不一定是 kube-apiserver 的客户端证书。上面命令里我用的是 etcd server 证书,因为它同时具备 clientAuth 用途,所以也能作为客户端证书用。但你如果想要更干净的权限隔离,可以专门用 etcd-client 那套证书来执行运维操作。关键是任何访问 etcd 的客户端都必须能通过client-cert-auth校验。
6. 常见问题与排查技巧实录
6.1 集群成员状态异常与选举问题
先说成员状态异常。执行member list看到某个成员unstarted,最常见的原因是initial-cluster里的地址和实际监听地址不一致。比如你配置里写的是https://k8s-etcd-1:2380,但 hosts 解析没配好,DNS 解析失败,成员间就永远无法连通。
排查思路是从日志入手,直接看 etcd 的日志输出。journalctl -u etcd -f里出现failed to connect to peer或者dial tcp相关的警告,基本可以锁定网络层问题。先用telnet 10.0.0.12 2380测试连通性,再用openssl s_client -connect 10.0.0.12:2380测试 TLS 握手,一层层剥。
选举问题最常见的表象是集群一直无 leader,表现为etcdserver: no leader报错。这时候先确认节点数是不是偶数,再确认是否有节点因为网络分区被隔离。Raft 需要多数派存活,如果你 3 个节点里有两个挂在一个交换机下,第三个节点在另一个网络区域,就可能出现活着的节点凑不齐多数派的情况。这种网络分区问题,要在交换机侧做冗余,etcd 层面除了日志排查没有太多可做的。
还有一个经验:三节点集群里有一台机器被强制关机后再启动,经常会在日志里看到raft: became candidate然后反复选主失败。这种时候不要慌,先检查另外两台是否健康。如果另外两台正常,重启异常节点后 etcd 会自动重新加入集群并同步数据。如果反复失败,考虑是不是数据目录损坏,必要时用快照恢复。
6.2 TLS 证书报错与访问失败
证书问题是外部 etcd 部署里出现频率最高的故障。我列几个最常见的报错和对应解决办法:
| 报错信息 | 原因 | 解决办法 |
|---|---|---|
x509: certificate is valid for 10.0.0.12, not 10.0.0.13 | SAN 缺少目标 IP | 重新签发证书,SAN 加入全部节点 IP 和主机名 |
certificate signed by unknown authority | 对端不信任本端 CA,或 CA 文件传错 | 检查trusted-ca-file和--etcd-cafile是否指向同一份 CA |
client certificate is not trusted | 客户端证书不是由 CA 签发,或client-cert-auth开启后客户端未传证书 | 用 CA 重新签发客户端证书,或补全--etcd-certfile、--etcd-keyfile |
tls: first record does not look like a TLS handshake | 端口没错但协议不对,比如用 HTTP 访问 HTTPS 端口 | 确认 URL 前缀是https://,检查listen-client-urls |
证书排障时,我强烈建议用openssl s_client做握手验证,它能告诉你证书链到底断在哪:
openssl s_client -connect 10.0.0.11:2379 \ -CAfile /etc/etcd/pki/etcd-ca.crt \ -cert /etc/etcd/pki/etcd-client.crt \ -key /etc/etcd/pki/etcd-client.key如果握手成功,会输出SSL-Session和Verify return code: 0。如果看到Verify return code: 20或19,说明信任链或证书不匹配。这个工具比 etcdctl 的报错信息更底层、更直观。
6.3 数据目录膨胀与性能退化
etcd 在长时间运行后,数据目录会越来越大,但真正占空间的不一定是有效数据,而是历史版本和碎片。Kubernetes 的控制面对象变化非常频繁,每次变更都会产生一个新的 etcd revision。如果不对历史版本做压缩(compact),数据目录会呈线性甚至超线性增长。
我的例行维护操作是:
# 查看当前最新 revision etcdctl --endpoints=https://10.0.0.11:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ endpoint status --cluster -w table # 压缩到最新 revision etcdctl --endpoints=https://10.0.0.11:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ compact <latest_revision> # 碎片整理 etcdctl --endpoints=https://10.0.0.11:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ defrag注意 defrag 操作会阻塞所在 etcd 成员,生产环境不建议一次性对三个节点同时执行。我的习惯是逐个节点操作:先对该节点执行etcdctl defrag,等它完成并恢复健康,再操作下一个。因为 defrag 期间该成员的读写会短暂暂停,如果同时做三个,整个集群会出现明显抖动。
性能退化也很隐蔽。如果发现 apiserver 的 API 请求延迟升高,先看 etcd 的监控指标,重点关注etcd_server_leader_changes_seen_total和etcd_disk_wal_fsync_duration_seconds。前者如果频繁增长,说明 leader 在不停切换,大概率是磁盘 IO 抖动或网络问题;后者如果远超 10ms,说明 WAL 落盘太慢,该换磁盘或者检查文件系统参数了。
6.4 备份、恢复与故障演练
最后必须说的是备份。etcd 的数据是 Kubernetes 集群的命根子,apiserver 里的所有资源对象最终都存在这里。没有备份的 Kubernetes 集群,出了故障就只能重建,这是不可接受的。
etcd 官方提供了基于 etcdctl 的快照备份方式:
etcdctl --endpoints=https://10.0.0.11:2379 \ --cacert=/etc/etcd/pki/etcd-ca.crt \ --cert=/etc/etcd/pki/etcd-server.crt \ --key=/etc/etcd/pki/etcd-server.key \ snapshot save /backup/etcd-snapshot-$(date +%Y%m%d).db备份文件拿到之后,恢复思路是:把快照文件恢复到某个节点的数据目录,然后以initial-cluster-state=existing的方式启动该节点,再逐步加入其他节点。恢复命令:
etcdctl snapshot restore /backup/etcd-snapshot-20240101.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-cluster-token etcd-k8s \ --initial-advertise-peer-urls https://10.0.0.11:2380 \ --data-dir /var/lib/etcd-restore恢复出来的数据目录是全新的,不影响原数据。确认无误后再把原数据目录改名,把恢复目录挪到/var/lib/etcd,启动 etcd。整个过程我在下线维护窗口里做过不止一次,核心经验就一条:恢复前先把证书和配置文件备份一份,别在恢复过程中又把证书弄丢。
备份策略层面,我建议每天都做一次快照,保留最近 7 天,每周再做一次归档。快照文件不要放在 etcd 本机,要同步到独立的存储或对象存储。否则 etcd 机器磁盘坏了,快照也跟着没,等于白备份。
最后留给你的几个实践建议
上面这套流程走完之后,我自己的体会是:外部 etcd 真正考验人的不是命令记不记得住,而是对 Raft 成员关系和 TLS 信任链有没有直觉。成员关系错了,集群会一直处于 unhealthy 状态;证书 SAN 缺了一个 IP,apiserver 连上来就是x509报错。所以强烈建议你实际操作时,把每台节点的配置文件和证书清单都打印出来,对照着检查一遍再启动。
另外一个小技巧:部署完成后,给 etcd 集群做一次"断电演练"。手动停掉一个节点,观察另外两个是否正常提供服务,再停掉第二个,确认集群进入不可用状态。然后按顺序恢复,看能否自动选主并重新同步数据。这样做一次,你对这套集群的真实信心会比看十篇文档都强。祝你在生产环境里少踩坑,etcd 稳如老狗。