资讯动态

Kubernetes高可用:kubeadm join第二个master报错排查与etcd集群扩展实战

发布时间:2026/10/23 6:25:07 来源:尧图企业网站定制
1. 从一次真实的集群扩容翻车说起搞Kubernetes的人几乎都绕不开一个经典场景单master集群跑得好好的突然业务要求高可用得把第二个master加进来。这时候你信心满满地掏出kubeadm join命令结果终端啪地甩回来一堆红字整个人就愣在那里了。我印象特别深有一次帮一个团队做集群扩容第一个master节点是kubeadm init出来的控制面组件跑得挺稳etcd也是单节点模式。按照官方文档的流程先在第一个master上生成证书密钥再生成join命令然后到第二个master上执行。结果报错信息五花八门——有时候是连接被拒绝有时候是证书校验失败有时候是etcd成员添加超时还有一次直接卡在[check-etcd]阶段不动了。这个问题的本质其实不是kubeadm join这个命令本身有多复杂而是第二个master加入集群时涉及的控制面组件比worker节点多得多。worker节点加入只需要kubelet和kube-proxy跟API Server通信就行但master节点加入要处理etcd集群成员扩展、控制面证书分发、静态Pod清单同步、负载均衡端点配置这一整套东西。任何一个环节出问题都会导致join失败。这篇文章我打算把kubeadm join第二个master报错这件事彻底拆开讲。从报错信息的分类、根因定位、逐步排查到最终修复和验证再到一些官方文档里不会写的实操细节全部过一遍。适合正在做Kubernetes高可用改造的运维和开发人员也适合那些第一次接触多master集群、被报错搞得一头雾水的朋友。读完之后你应该能独立定位大部分join第二个master时的常见故障并且知道每一步为什么要这么做。2. 第二个master加入时到底发生了什么2.1 控制面节点加入与worker节点加入的本质区别很多人第一次做多master扩容时会下意识觉得不就是多跑一次join嘛跟加worker节点差不多。这个认知偏差是后面所有困惑的源头。worker节点加入集群做的事情很单纯kubelet启动通过bootstrap token向API Server申请证书然后注册自己kube-proxy建立网络规则。整个过程只跟API Server打交道不涉及etcd不涉及控制面组件的证书体系。但第二个master加入完全是另一回事。它需要把自己作为一个新成员加入etcd集群这意味着etcd的数据目录要初始化并且跟已有etcd成员建立peer连接从第一个master那里获取控制面所需的所有证书和密钥包括CA证书、API Server证书、Service Account密钥等在本地拉起kube-apiserver、kube-controller-manager、kube-scheduler这些静态Pod配置本地的kubelet让它能够管理这些静态Pod如果前面有负载均衡器还需要把新master的API Server地址注册进去你可以这样理解worker节点加入像是新员工入职登记一下就能干活master节点加入像是新合伙人入伙得把公司公章、财务账本、客户合同全部复制一份给他还要在工商登记里加上他的名字。2.2 kubeadm join在master节点上的执行阶段拆解kubeadm join在master节点上执行时内部大致分为这几个阶段每个阶段都可能成为报错点阶段主要动作常见报错preflight环境检查确认端口、目录、依赖端口占用、cgroup驱动不匹配control-plane-prepare下载证书、生成配置证书下载失败、连接超时kubelet-start写入kubelet配置并启动kubelet启动失败、配置冲突etcd-join将本节点加入etcd集群etcd成员添加超时、peer连接失败upload-config上传节点配置到集群API Server不可达mark-control-plane标记节点角色标签冲突其中etcd-join阶段是最容易出问题的因为etcd对网络延迟、时钟同步、数据目录状态都非常敏感。而control-plane-prepare阶段则经常因为证书传输通道不通而失败。2.3 报错信息背后的三类根因我把这些年遇到的报错归为三大类基本上覆盖了90%以上的场景第一类是网络连通性问题。第二个master跟第一个master之间或者跟etcd集群之间网络不通。表现可能是connection refused、i/o timeout、no route to host。这类问题最好排查但前提是你得知道kubeadm join到底需要哪些端口通。第二类是证书与认证问题。证书过期、证书不匹配、token失效、CA不一致。表现可能是x509: certificate signed by unknown authority、token is not valid、certificate has expired。这类问题最隐蔽因为报错信息往往指向一个看起来无关的地方。第三类是etcd集群状态问题。etcd成员已经存在、etcd数据目录非空、etcd集群不健康。表现可能是etcd member already exists、etcd cluster is not healthy、timeout waiting for etcd。这类问题最棘手因为处理不当可能把整个etcd集群搞崩。3. 网络连通性最容易被忽视的排查起点3.1 kubeadm join第二个master需要打通哪些端口很多人排查网络问题时只知道要通API Server的6443端口但实际上master节点加入涉及的端口远不止这一个。下面这张表是我自己整理的最小端口清单源节点目标节点端口协议用途新master已有master6443TCPAPI Server通信新master已有master2379-2380TCPetcd客户端与peer通信新master已有master10250TCPkubelet API新master已有master10259TCPkube-scheduler新master已有master10257TCPkube-controller-manager已有master新master2379-2380TCPetcd反向peer连接已有master新master10250TCPkubelet健康检查注意最后两行etcd的peer通信是双向的。很多人只在新master上测试了到已有master的连通性却忘了已有master也需要能反向连接到新master的2379和2380端口。如果中间有防火墙或者安全组只放行了单向流量etcd集群就永远建不起来。3.2 用telnet和nc做快速连通性验证在正式执行join之前我习惯先在新master上做一轮连通性测试。最直接的方法是用nc# 测试到已有master的API Server端口 nc -zv 192.168.1.10 6443 # 测试到已有master的etcd端口 nc -zv 192.168.1.10 2379 nc -zv 192.168.1.10 2380 # 测试到已有master的kubelet端口 nc -zv 192.168.1.10 10250如果nc不可用用telnet也行telnet 192.168.1.10 6443但这里有个坑telnet和nc只能测试TCP三次握手是否成功不能验证TLS握手是否正常。有时候端口通了但证书校验失败nc是测不出来的。所以连通性测试通过之后还要用curl做一次TLS层面的验证curl -k https://192.168.1.10:6443/healthz如果返回ok说明API Server的TLS握手正常。如果返回证书错误那就是证书体系的问题不是网络问题。3.3 反向连通性为什么已有master也要能连新master这一点特别容易被忽略。etcd集群的peer通信是双向的新master加入时已有etcd成员会主动向新master的2380端口发起连接用于数据同步和心跳检测。我遇到过一次典型故障新master所在的网段出方向允许所有流量但入方向只放行了22端口。结果kubeadm join执行到etcd-join阶段就卡住了日志里显示dial tcp 192.168.1.20:2380: i/o timeout。当时排查了半天最后发现是已有master连不上新master的2380端口。验证反向连通性的方法是在已有master上执行nc -zv 192.168.1.20 2380 nc -zv 192.168.1.20 2379如果这两个端口不通etcd集群永远建不起来。解决方法是调整防火墙规则或安全组策略确保双向流量都放行。提示在生产环境中建议把master节点之间的所有控制面端口全部放行不要试图做精细化控制。控制面内部的通信关系太复杂精细化控制带来的收益远小于排查成本。4. 证书与Token报错信息最会骗人的地方4.1 证书过期导致的join失败及识别方法Kubernetes集群的证书默认有效期是一年。如果集群运行超过一年第一个master上的证书可能已经过期这时候生成join命令虽然能成功但新master在执行join时会因为证书校验失败而报错。识别证书是否过期可以在第一个master上执行kubeadm certs check-expiration输出会列出所有证书的过期时间。如果看到EXPIRED或者剩余时间很短就需要先续期kubeadm certs renew all续期之后需要重启控制面组件让新证书生效。对于静态Pod方式部署的控制面重启方法是删除对应的Podkubelet会自动用新证书重建# 找到控制面Pod kubectl get pods -n kube-system | grep -E kube-apiserver|kube-controller|kube-scheduler|etcd # 删除它们kubelet会自动重建 kubectl delete pod -n kube-system kube-apiserver-node-name这里有个细节续期证书之后之前生成的join命令里的证书哈希会失效。因为join命令里包含了一个--discovery-token-ca-cert-hash参数这个哈希是基于CA证书计算的。CA证书续期后哈希变了旧命令就用不了了。必须重新生成join命令。4.2 bootstrap token失效的典型表现kubeadm join使用bootstrap token来做初始认证。这个token默认有效期是24小时。如果生成join命令之后过了很久才执行token可能已经过期。token过期的报错信息通常是error execution phase preflight: couldnt validate the identity of the API Server: could not find a JWS signature in the cluster-info ConfigMap for token ID xxxxxx或者token is not valid查看当前有效的tokenkubeadm token list如果列表为空或者所有token都过了EXPIRES时间就需要重新创建kubeadm token create --print-join-command这个命令会直接输出一条完整的join命令包含新的token和证书哈希直接复制到新master上执行即可。4.3 证书哈希不匹配的排查与修复证书哈希不匹配的报错信息很有迷惑性通常长这样error execution phase preflight: couldnt validate the identity of the API Server: configmap cluster-info not found或者x509: certificate signed by unknown authority看起来像是API Server连不上实际上是哈希对不上。排查方法是手动计算第一个master上CA证书的哈希跟join命令里的哈希做对比# 在第一个master上计算CA证书哈希 openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ openssl rsa -pubin -outform der 2/dev/null | \ openssl dgst -sha256 -hex | sed s/^.* //把输出跟join命令里的--discovery-token-ca-cert-hash值对比。如果不一致说明join命令是旧的需要重新生成。重新生成join命令的标准流程是# 在第一个master上执行 kubeadm token create --print-join-command这条命令会同时生成新token和正确的证书哈希一步到位。5. etcd集群扩展最容易把集群搞崩的环节5.1 etcd成员添加的完整流程与超时机制当kubeadm join执行到etcd-join阶段时它会做这几件事检查本地etcd数据目录是否为空通过已有etcd集群的API添加一个新成员用新成员的配置启动本地etcd等待新成员跟集群同步完成这个过程中kubeadm会设置一个超时时间默认是40秒左右。如果etcd成员添加后新节点在超时时间内没有完成同步join就会失败。超时失败的报错通常是error execution phase etcd-join: etcd member add timed out或者timeout waiting for etcd cluster to be healthy这时候最危险的操作是直接重新执行join。因为第一次join可能已经往etcd集群里添加了一个成员记录虽然新节点没起来但集群里已经有了这个成员的幽灵记录。再次join时kubeadm会尝试添加同名成员导致冲突。5.2 etcd数据目录非空导致的启动失败如果新master上之前跑过etcd或者之前join失败过/var/lib/etcd目录里可能有残留数据。kubeadm在join时会检查这个目录如果非空就会报错error execution phase etcd-join: etcd data directory is not empty处理方法是清空这个目录# 先确认etcd进程已经停止 systemctl stop etcd 2/dev/null ps aux | grep etcd # 备份并清空数据目录 mv /var/lib/etcd /var/lib/etcd.bak mkdir -p /var/lib/etcd但清空之前一定要确认这个节点上的etcd数据确实不需要了。如果这个节点之前是集群的etcd成员清空数据目录会导致它无法重新加入需要先从集群中移除旧成员。5.3 清理etcd中残留的幽灵成员如果join失败后etcd集群里留下了幽灵成员需要手动清理。首先在任意一个健康的master上查看当前成员列表# 如果etcdctl可用 etcdctl --endpointshttps://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 \ member list输出会列出所有成员包括那些状态为unstarted的幽灵成员。找到幽灵成员的ID然后移除etcdctl --endpointshttps://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 \ member remove member-id如果etcdctl不可用也可以用kubectl直接操作etcd的Podkubectl exec -n kube-system etcd-node-name -- etcdctl \ --endpointshttps://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 \ member list清理完幽灵成员后再重新执行join。注意etcd集群的成员数量最好是奇数。如果你打算加第二个masteretcd集群会从1个成员变成2个这时候集群的容错能力实际上是下降的。因为2个成员的etcd集群任何1个成员故障都会导致集群不可用。所以生产环境建议直接规划3个master而不是从1个慢慢加到2个。6. 一套可复现的完整排查链路6.1 从报错日志定位到具体失败阶段kubeadm join失败时第一件事是看完整日志。默认情况下日志输出到终端但信息可能不全。建议加上--v5参数提高日志级别kubeadm join 192.168.1.10:6443 \ --token xxxxxx.xxxxxxxxxxxxxxxx \ --discovery-token-ca-cert-hash sha256:xxxxxx \ --control-plane \ --v5日志里会显示每个阶段的开始和结束。找到最后一个成功的阶段和第一个失败的阶段就能定位问题范围。如果日志太多可以把输出重定向到文件然后用grep过滤kubeadm join ... --v5 21 | tee join.log grep -E error|failed|timeout join.log6.2 分阶段验证preflight、control-plane-prepare、etcd-join我习惯把排查分成三个阶段来做每个阶段单独验证preflight阶段主要检查环境。如果这个阶段失败通常是端口占用、cgroup驱动不匹配、swap未关闭这类问题。验证方法是手动检查# 检查swap是否关闭 free -h # 检查cgroup驱动 docker info | grep -i cgroup # 检查端口占用 ss -tlnp | grep -E 6443|2379|2380|10250control-plane-prepare阶段主要处理证书。如果这个阶段失败通常是网络不通或者token失效。验证方法是手动测试API Server连通性curl -k https://192.168.1.10:6443/healthzetcd-join阶段主要处理etcd成员添加。如果这个阶段失败通常是etcd集群不健康或者数据目录有问题。验证方法是检查etcd集群状态etcdctl --endpointshttps://192.168.1.10:2379 \ --cacert/etc/kubernetes/pki/etcd/ca.crt \ --cert/etc/kubernetes/pki/etcd/server.crt \ --key/etc/kubernetes/pki/etcd/server.key \ endpoint health6.3 重置节点后重新join的正确姿势如果join失败后状态已经乱了最稳妥的做法是重置节点再重新来。重置命令是kubeadm reset --force但kubeadm reset不会清理etcd数据目录也不会清理CNI网络配置。所以重置之后还需要手动清理# 清理etcd数据目录 rm -rf /var/lib/etcd # 清理CNI配置 rm -rf /etc/cni/net.d # 清理kubelet配置 rm -rf /etc/kubernetes rm -rf ~/.kube清理完成后重启kubelet和容器运行时systemctl restart kubelet systemctl restart docker # 或者containerd然后再重新执行join命令。这里的关键是确保etcd数据目录是干净的否则etcd-join阶段还会失败。7. 那些官方文档不会告诉你的实操细节7.1 时间同步etcd对时钟偏差的容忍度极低etcd集群对节点之间的时钟偏差非常敏感。如果新master跟已有master的时间差超过1秒etcd的选举和心跳机制就可能出问题。表现可能是join过程中etcd成员添加成功但新节点一直无法同步数据最后超时失败。排查方法是检查所有master节点的时间# 在所有master上执行 date timedatectl status如果时间不一致需要配置NTP同步。在大多数Linux发行版上timedatectl set-ntp true或者手动指定NTP服务器timedatectl set-ntp false ntpdate ntp.aliyun.com timedatectl set-ntp true我遇到过好几次join失败最后发现是时间不同步导致的。这个问题特别隐蔽因为报错信息不会直接说时间不同步而是表现为etcd连接超时或者证书校验失败证书校验也依赖时间。7.2 负载均衡器配置join命令里的地址该写谁如果你的集群前面有负载均衡器比如HAProxy、Nginx、Keepalivedjoin命令里的地址应该写负载均衡器的VIP而不是某个具体master的IP。写VIP的好处是join过程中如果某个master挂了kubeadm可以自动切换到其他master。写具体IP的话如果那个master正好在join期间不可用整个join就失败了。但这里有个坑负载均衡器需要同时转发6443端口和2379-2380端口。很多负载均衡配置只转发了6443导致etcd-join阶段失败。正确的负载均衡配置应该包含# HAProxy配置示例 frontend k8s-api bind *:6443 default_backend k8s-api-backend backend k8s-api-backend balance roundrobin server master1 192.168.1.10:6443 check server master2 192.168.1.20:6443 check frontend etcd-client bind *:2379 default_backend etcd-client-backend backend etcd-client-backend balance roundrobin server master1 192.168.1.10:2379 check server master2 192.168.1.20:2379 check7.3 join完成后必须验证的几件事join命令执行成功不代表万事大吉。我习惯在join完成后做这几项验证第一检查节点状态。在任意master上执行kubectl get nodes新master应该显示为Ready状态角色列应该有control-plane,master。第二检查etcd集群健康。在任意master上执行etcdctl --endpointshttps://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 \ endpoint health --cluster所有成员都应该返回healthy。第三检查控制面Pod。在新master上执行kubectl get pods -n kube-system -o wide | grep new-master-name应该能看到kube-apiserver、kube-controller-manager、kube-scheduler、etcd这四个Pod在运行。第四测试API Server高可用。停掉第一个master的kube-apiserver然后执行kubectl get nodes如果还能正常返回说明高可用生效了。7.4 回滚方案join失败后如何安全退出如果join失败并且短时间内无法修复需要把新master从集群中安全移除。步骤是在健康的master上从etcd集群中移除新master的成员记录在健康的master上删除新master的节点记录kubectl delete node new-master-name在新master上执行kubeadm reset --force清理新master上的etcd数据目录和CNI配置这里的关键是先从etcd集群中移除成员再删除节点记录。顺序反了的话etcd集群里会留下幽灵成员影响后续操作。8. 写在最后的一点个人体会kubeadm join第二个master报错这件事表面上看是命令执行失败实际上考验的是对Kubernetes控制面架构的理解程度。你越清楚join过程中每个阶段在做什么、依赖什么、可能在哪里出问题排查起来就越有方向感。我自己的经验是遇到报错先别急着搜解决方案先把--v5的日志完整看一遍找到第一个报错的阶段然后针对那个阶段做针对性排查。大部分问题都集中在网络连通性、证书有效性、etcd状态这三个方面。把这三个方面检查一遍基本能覆盖绝大多数场景。另外生产环境做多master扩容建议直接规划三个master节点而不是从1个加到2个。因为2个master的etcd集群容错能力反而比1个还差而且后续从2个加到3个又要再折腾一次。一步到位虽然前期工作量大一点但长期来看省心得多。最后分享一个小技巧在执行join之前先在第一个master上执行kubeadm token create --print-join-command把输出的命令保存下来。这条命令里的token和证书哈希都是最新的能避免很多因为token过期或哈希不匹配导致的低级错误。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑