
做Kubernetes高可用集群的二进制部署我向来主张一个顺序先把证书体系想透再把etcd集群立起来最后才轮到kube-apiserver、kube-controller-manager这些控制面组件。原因很简单etcd是整条控制面的数据底座而PKI证书又是etcd能安全对外提供服务的前提。这个系列前面铺垫完基础概念之后到了真正动手的环节绝大多数人会卡在两条线上一是CFSSL签发证书时的SAN、profile到底怎么填二是三个etcd节点起来之后互相之间握手失败、集群状态异常。这篇文章就把这两块完整拆开把证书生成、分发、etcd三节点部署、验证和常见故障排查一次性讲透适合正在自己动手搭HA集群、或者已经被kubeadm封装习惯导致对底层生疏的读者。1. 方案设计为什么二进制部署先啃PKI和etcd1.1 二进制部署与kubeadm的核心差异用kubeadm初始化集群证书和etcd都是“开箱即用”的kubeadm init会自动生成一套自签CA会在第一个控制面节点拉起一个单实例etcd一切看起来都很顺。但我在实际生产环境里遇到过一个典型的尴尬场景集群跑了一年kube-apiserver要扩一个实例出来做负载均衡结果发现现有证书的SAN里压根没包含新实例的IP和域名只能重新签证书然后控制面所有组件逐个滚动重启。这种折腾次数多了你就会意识到kubeadm帮你做的那些“默认动作”并不适合所有场景。二进制部署的核心优势是每个组件的运行方式、证书路径、监听地址、数据目录都掌握在自己手里。特别是etcd用kubeadm装出来的etcd是static pod方式跑在容器里的日志排查、数据快照、参数调优都要绕一层容器封装而二进制方式直接交给systemd托管一切透明排障路径最短。如果只是搭一套测试环境kubeadm确实省事。但如果目标是长期维护一套生产级HA集群或者需要对控制面做深度定制比如多租户隔离、审计策略、etcd独立部署到专用机器二进制部署几乎是必经之路。1.2 etcd在HA集群里的地位与节点布局etcd在Kubernetes里的角色相当于整个集群的“记忆中枢”。所有API对象——Pod、Service、ConfigMap、Secret、Deployment、Node状态——都以键值对的形式存放在etcd里。kube-apiserver是无状态服务多个实例可以并行处理请求但它们读写的最终一致性完全由etcd保证。所以etcd的高可用设计是整个集群高可用的前提。生产环境一般用3个或5个节点组成raft组3节点允许挂1个5节点允许挂2个。节点数量一定取奇数这是raft多数派投票机制决定的写入必须得到超过半数的节点确认才会返回成功。节点布局上有一个容易被忽视的点etcd最好部署在独立的机器上或者至少和kube-apiserver、kubelet做资源隔离。etcd对磁盘IO延迟极其敏感一次写入需要fsync持久化到WAL日志如果系统盘和应用日志打在同一个繁忙磁盘上写延迟飙升会直接反映为API请求延迟甚至超时。1.3 证书系统的整体规划思路Kubernetes控制面组件之间全是TLS通信证书系统设计的核心原则是“最小权限、最小信任”。根CA只有一个由它签发不同类型的子证书每个证书只服务于特定身份节点身份证书每个节点一个用于kubelet与apiserver双向认证组件客户端证书kube-apiserver访问etcd时使用的etcd client证书服务端证书kube-apiserver对外提供HTTPS服务的证书etcd对外提供服务的证书管理用户证书管理员通过kubectl访问集群时的客户端证书证书签发不是随手openssl req -x509一把梭生产环境需要统一用CA中心集中签发原因在于信任链管理。所有组件校验对方身份时都回溯到同一个根CA根CA签发中间CA中间CA签发具体证书。这样轮换某个组件证书时只需要重新签发那一张不影响整个信任链。实际操作中我习惯把根CA私钥放在独立的安全目录甚至离线保存日常签发使用它的签发型配置文件。这样即使某个节点的证书私钥泄露也可以用CA吊销并重新签发根私钥仍然安全。2. 证书生成实操CFSSL签发全套PKI证书2.1 证书体系角色划分CFSSL是CloudFlare开源的PKI工具集相比直接用openssl命令它的优势是配置文件驱动适合批量生成一致的证书。先规划三台etcd节点的假设信息实际部署请替换为自己的内网规划节点角色主机名内网IPetcd-1k8s-etcd-01192.168.1.11etcd-2k8s-etcd-02192.168.1.12etcd-3k8s-etcd-03192.168.1.13这一阶段要生成的证书分三类etcd server证书etcd节点对外提供客户端访问时使用的服务端证书需要包含三个节点的IP和域名作为SANetcd peer证书etcd节点之间raft通信使用的双向认证证书同样需要包含三个节点的IP和域名etcd client证书供kube-apiserver访问etcd使用的客户端证书注意etcd的server证书和peer证书不能共用同一个CSR随意签。虽然它们的信任链相同但用途不同peer证书要求所有节点都能验证对端身份SAN必须覆盖全部集群成员server证书只要能被客户端通过其访问地址验证。生产我建议分开签后续独立轮换互不影响。2.2 使用CFSSL生成CA与etcd证书CFSSL的安装方式很直接下载对应平台的二进制放到/usr/local/bin即可。这里在专门生成证书的机器上操作可以是独立管理机也可以是其中一台etcd节点。先准备CA配置文件。CA配置决定证书的签名策略和有效期{ signing: { default: { expiry: 87600h }, profiles: { server: { expiry: 87600h, usages: [signing, key encipherment, server auth] }, peer: { expiry: 87600h, usages: [signing, key encipherment, server auth, client auth] }, client: { expiry: 87600h, usages: [signing, key encipherment, client auth] } } } }这里的expiry设置的是10年生产可以按安全策略调整。usages字段决定了证书的扩展用途etcd peer证书需要同时包含server auth和client auth因为peer通信是双向TLS认证。接着生成CA根证书cat ca-csr.json EOF { CN: kubernetes-ca, key: { algo: rsa, size: 2048 }, names: [ { C: CN, ST: BJ, L: BJ, O: kubernetes, OU: kubernetes-cluster } ] } EOF cfssl gencert -initca ca-csr.json | cfssljson -bare ca执行完会得到ca.pem、ca-key.pem、ca.csr三个文件。ca-key.pem就是根CA私钥务必妥善保管。O字段是组织名后面组件校验时可能按组织区分权限角色填一个统一标识即可。生成etcd server证书cat etcd-server-csr.json EOF { CN: etcd-server, hosts: [ 192.168.1.11, 192.168.1.12, 192.168.1.13, 127.0.0.1, etcd-01, etcd-02, etcd-03, kubernetes.default.svc.cluster.local ], key: { algo: rsa, size: 2048 }, names: [ { C: CN, ST: BJ, L: BJ, O: kubernetes, OU: etcd-server } ] } EOF cfssl gencert -caca.pem -ca-keyca-key.pem -configca-config.json -profileserver etcd-server-csr.json | cfssljson -bare etcd-server关键点是hosts字段。这里声明的每个IP和域名都会被写进证书的SANSubject Alternative Name扩展。etcd启动时如果用https://192.168.1.11:2379对外服务客户端连接校验服务端证书时会检查访问地址是否匹配SAN不匹配直接握手失败。生成etcd peer证书注意CN要用node名字profile换成peercat etcd-peer-csr.json EOF { CN: etcd-peer, hosts: [ 192.168.1.11, 192.168.1.12, 192.168.1.13, 127.0.0.1, etcd-01, etcd-02, etcd-03 ], key: { algo: rsa, size: 2048 }, names: [ { C: CN, ST: BJ, L: BJ, O: kubernetes, OU: etcd-peer } ] } EOF cfssl gencert -caca.pem -ca-keyca-key.pem -configca-config.json -profilepeer etcd-peer-csr.json | cfssljson -bare etcd-peer生成etcd client证书cat etcd-client-csr.json EOF { CN: etcd-client, hosts: [], key: { algo: rsa, size: 2048 }, names: [ { C: CN, ST: BJ, L: BJ, O: kubernetes, OU: etcd-client } ] } EOF cfssl gencert -caca.pem -ca-keyca-key.pem -configca-config.json -profileclient etcd-client-csr.json | cfssljson -bare etcd-clientclient证书的hosts留空是正确的因为它是客户端身份凭证不依赖访问地址只需要CN标识身份。2.3 核对证书清单与分发签发完成后/etc/etcd/pki目录下应该有这些文件文件用途分发范围ca.pemCA根证书所有etcd节点etcd-server.pemetcd对外服务证书对应节点etcd-server-key.pem服务证书私钥对应节点etcd-peer.pemraft通信证书所有etcd节点etcd-peer-key.pempeer私钥对应节点etcd-client.pem客户端访问证书kube-apiserver所在节点etcd-client-key.pem客户端私钥kube-apiserver所在节点分发时用scp或配置管理工具推到节点上设置权限600。私钥文件如果权限过宽etcd启动时会直接拒绝加载。验证证书内容有一个实用命令线上排查必备openssl x509 -in etcd-server.pem -noout -text | grep -A 3 Subject Alternative Name这会列出证书实际包含的SAN当出现“证书校验失败”时第一件事就是对比证书SAN和访问地址。3. 实战部署三节点etcd集群二进制安装3.1 安装包准备与目录规划到etcd官方GitHub Release页面下载对应架构的二进制压缩包比如etcd-v3.5.x-linux-amd64.tar.gz。解压后里面包含etcd和etcdctl两个程序前者是服务进程后者是命令行客户端。三台节点都按同一套目录规划执行mkdir -p /opt/etcd/bin mkdir -p /etc/etcd/pki mkdir -p /var/lib/etcd二进制放到/opt/etcd/bin/etcd证书放到/etc/etcd/pki/数据目录在/var/lib/etcd。目录规划的考虑是数据目录单独分区便于后续做磁盘容量管理证书目录独立便于权限控制和备份。3.2 节点配置与systemd托管etcd的配置方式有两种命令行参数和YAML配置文件。命令行参数写起来冗长容易出错我推荐用YAML配置文件清晰且便于版本管理。以etcd-1节点为例配置文件/etc/etcd/etcd.ymlname: etcd-01>[Unit] Descriptionetcd service Afternetwork.target Afternetwork-online.target Wantsnetwork-online.target [Service] Typenotify ExecStart/opt/etcd/bin/etcd --config-file/etc/etcd/etcd.yml Restartalways RestartSec5 LimitNOFILE65536 Useretcd Groupetcd [Install] WantedBymulti-user.targetTypenotify是etcd推荐的启动类型etcd启动完成后会主动通知systemd避免误判启动失败。LimitNOFILE调高文件句柄数上限线上etcd连接数高时默认1024远远不够。这里建议创建一个专用系统用户useradd --system --home /var/lib/etcd --shell /bin/false etcd chown -R etcd:etcd /var/lib/etcd让etcd以非root身份运行数据目录归属etcd用户这是基本的安全加固。如果用root启动后面想加安全策略会很被动。3.3 集群启动、验证与数据读写测试三台节点全部配置就绪后启动顺序建议从第一台开始等它成功加入集群并成为leader后再启动其余节点。虽然etcd支持同时启动但顺序启动更容易定位问题。systemctl daemon-reload systemctl enable --now etcd systemctl status etcd -l在任意一台节点上查看集群成员etcdctl --endpointshttps://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-client.pem \ --key/etc/etcd/pki/etcd-client-key.pem \ member list注意etcdctl连接https端点时必须携带CA证书和客户端证书否则会报证书校验失败。输出结果应该列出三个成员状态均为started。检查整个集群的健康状态etcdctl --endpointshttps://192.168.1.11:2379,https://192.168.1.12:2379,https://192.168.1.13:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-client.pem \ --key/etc/etcd/pki/etcd-client-key.pem \ endpoint health --cluster三条endpoint都返回healthy才算通过。如果有一个节点unhealthy不要急着继续搭上层组件先把etcd集群恢复到健康状态再往下走否则后面kube-apiserver起来会间歇性报错。做一个基本的读写验证etcdctl --endpointshttps://192.168.1.11:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-client.pem \ --key/etc/etcd/pki/etcd-client-key.pem \ put /test/hello world etcdctl --endpointshttps://192.168.1.11:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-client.pem \ --key/etc/etcd/pki/etcd-client-key.pem \ get /test/hello能正常写入和读取说明客户端认证链路、证书信任链、集群数据同步都正常。4. 排查实录证书与etcd常见故障处理4.1 证书类故障证书问题在etcd部署阶段出现频率最高以下是我实测中最常见的几种“x509: cannot validate certificate for 192.168.1.12 because it doesnt contain any IP SANs”这个报错原因很直接证书的SAN里没有包含访问时使用的IP。比如你访问etcd-02时用了https://192.168.1.12:2379但那份证书签发时hosts字段只写了etcd-02主机名没有写IPTLS校验就失败。生产环境几乎都是IP直连因此签发证书时一定要把集群所有成员IP都写进hosts。“remote error: tls: bad certificate”通常是客户端使用了自己不信任的CA签发的证书或者客户端证书类型不对。检查一下客户端访问是否带了正确的--cacert以及证书的usages是否包含client auth。证书过期etcd运行很久后证书到期表现为客户端间歇性报错certificate has expired or is not yet valid。排查命令openssl x509 -in /etc/etcd/pki/etcd-server.pem -noout -dates如果发现即将过期需要重新签发给到节点并重启etcd。我在实际运维中会写一个证书到期检查脚本每周定时巡检输出90天内即将过期的证书列表避免故障发生在半夜。4.2 etcd集群类故障“etcdserver: request timed out”与leader选举异常这个报错常见于磁盘性能差或网络抖动。etcd对写入延时的要求很高leader节点必须把每一次写请求同步到多数节点并持久化后才能返回如果某个节点磁盘fsync慢整个集群写入都会被拖垮。检查思路先看各个节点的etcd日志再观察etcdctl endpoint status返回的Raft Term和Leader是否一致。磁盘性能可以用iostat确认%util是否长期接近100%网络可以用ping看节点间延迟和丢包。成员加入失败data-dir有残留如果重新初始化集群但data-dir里保留了上一次的成员数据节点可能以旧身份加入新集群导致member ID冲突。处理方法是清空data-dir后重新启动。实际操作中我发现很多人忘了这个坑明明是全新部署却不小心把之前测试留下的数据目录当生产数据保留了下来。日志过量增长etcd默认把WAL和快照写在data-dir下长时间运行后磁盘占用会持续增长但不会无限制增长——etcd会定期压缩历史版本并触发快照。如果数据Key数量巨大压缩不及时磁盘可能会被打满。这时考虑使用etcd --auto-compaction-retention1开启自动压缩同时关注data-dir的磁盘水位。4.3 运维侧建议快照、日志与健康检查etcd的数据安全直接影响整个Kubernetes集群备份策略必须在部署阶段就考虑好。最简单的做法是用etcdctl做定时快照etcdctl --endpointshttps://192.168.1.11:2379 \ --cacert/etc/etcd/pki/ca.pem \ --cert/etc/etcd/pki/etcd-client.pem \ --key/etc/etcd/pki/etcd-client-key.pem \ snapshot save /backup/etcd-snapshot-$(date %Y%m%d).db配合crontab每天执行保留最近7天。快照恢复时用etcdctl snapshot restore恢复到指定目录再以initial-cluster-stateexisting启动。注意快照备份最好在集群健康状态下执行。如果集群已经处于leader丢失状态快照可能不完整恢复时也要先停掉所有etcd节点避免旧数据回写覆盖恢复结果。日志方面etcd默认打印到标准输出由systemd的journal收集。长时间运行后journal可能占用较大空间建议配置/etc/systemd/journald.conf里的SystemMaxUse限制日志上限或者给etcd单元增加StandardOutputappend:/var/log/etcd.log把日志重定向到文件便于用logrotate统一处理。另外一个实践心得把etcd的健康检查纳入监控命令就用上面写过的endpoint health --cluster。不要只监控进程是否存在进程活着不代表集群healthy。我经历过一次etcd进程正常、但集群因网络分区失去leader半小时的情况如果只看进程状态完全发现不了。最后说点操作体会三套etcd节点第一次搭的时候我犯过一个印象很深的错三台机器配置都写好之后我图省事同时启动结果状态一直反复横跳日志刷出一堆“leader changed”排查了半天发现是节点间时钟不同步加证书SAN漏了一个IP。从那之后我就坚持顺序启动每启动一台就先看成员列表、看日志、确认健康再启动下一台。这个习惯看着慢但能帮你把每一步的问题都限制在小范围内不至于最后三台一起“炸”了才回头找原因。证书签名和etcd初始化这两关走通后面的kube-apiserver、kube-controller-manager、kube-scheduler部署就顺理成章了。只是提醒一句kube-apiserver访问etcd用的那份client证书CN和O字段后面可能会被用于RBAC权限映射签发时命名规范一些省得将来为证书身份问题返工。