K8s高可用集群:Keepalived+HAProxy与首个Master初始化
先交代一个事实如果你已经跟着这个系列把操作系统、Containerd 和基础网络都调好了那这一篇要解决的问题就是整个高可用集群里最容易出隐性故障的一层——控制平面入口的负载均衡以及第一个 master 节点的初始化。Kubernetes 1.32 的 kubeadm 本身已经非常成熟但高可用部署依然卡在“入口怎么搭”“第一个节点怎么起”这两个环节上。这篇就是专门啃这两块硬骨头的。很多人在单机环境里装 Kubernetes 很顺利一到高可用就翻车原因往往不是 kubeadm 不会用而是没有真正理解高可用控制平面的工作方式apiserver 是无状态的可以横向扩但它的前面必须有一个稳定的统一入口controller-manager 和 scheduler 是有状态的但它们的“有状态”是靠选主机制解决的etcd 则要求奇数节点才能保证多数派存活。这三件事捋顺了整个架构图就清晰了后面的配置都是水到渠成的事。我会把这一篇的重点放在三块负载均衡层的方案选型和部署、第一个控制平面节点的初始化与证书处理、以及我在实际部署中反复踩过的坑。如果你准备部署生产环境这一篇可以直接当操作手册用。1. 系列三在整个高可用集群里的位置1.1 这一篇解决了什么问题高可用 Kubernetes 集群说白了就是要做到“任何一台机器挂掉集群对外服务不中断”。控制平面有三类关键服务apiserver、controller-manager/scheduler、etcd。其中 apiserver 是唯一对外提供 API 的组件kubectl、kubelet、kube-proxy、scheduler、controller-manager 全部要通过它来通信。所以 apiserver 的入口必须高可用。但 apiserver 本身是无状态的它不存数据所有数据都在 etcd 里。也就是说你起三个 apiserver它们之间不需要互相通信谁收到请求都能去查 etcd 然后返回结果。这样问题就简化了只需要在三个 apiserver 前面放一个负载均衡器把流量均匀分给它们即可。controller-manager 和 scheduler 不太一样它们是有状态的“选主”模式。虽然你可以在每个 master 上都跑一个但同一时刻只有一个实例在真正工作其他实例处于 standby 状态靠 lease 机制抢占领导权。这个机制 kubeadm 默认已经配置好了不需要手动干预。所以整个高可用控制平面的架构就是负载均衡器 三个 apiserver 三个 etcd 成员堆叠模式 选主模式的 controller-manager/scheduler。而系列三要做的就是前两项。1.2 为什么高可用必须有一个统一入口有人可能会问kubeconfig 里不是可以配置多个 apiserver 地址吗为什么不直接在客户端配三个地址让它自动切换理论上可以但实际上非常不推荐。kubeadm 生成的组件配置比如 kubelet.conf、controller-manager.conf都只支持写一个 server 地址。如果你用第三方工具或者手工写配置强行塞多个地址也不是不行但这样所有组件都要自己处理故障转移逻辑非常容易出问题。更关键的是etcd 的 client 地址、kube-proxy 的 apiserver 地址都是写死的。所以业界通行做法是给 apiserver 前面加一个 VIP所有组件都指向这个 VIP。负载均衡器负责把流量转发到后端的多个 apiserver某个 apiserver 挂了它自动摘除。这样对客户端来说永远只有一个稳定的入口地址。这也是 kubeadm 里controlPlaneEndpoint这个配置项存在的意义。1.3 前置条件假设你已经完成了这些由于这是系列文章第三篇前两篇讲的环境准备工作我就默认你已经做完了。这里简单列一下清单方便你自查三台控制平面节点操作系统 Ubuntu 22.04/24.04 或 Rocky Linux 9内核 4.19 以上所有节点已安装 Containerd并配置了 systemd cgroup driver节点之间时间同步正常如果存在多网卡确认每台机器的主网卡和 IP/etc/hosts里已经写好了三台 master 的域名解析或者你准备了内网 DNS必要的内核模块overlay、br_netfilter和系统参数net.ipv4.ip_forward已生效防火墙规则留出了 Kubernetes 和负载均衡所需的端口如果你发现自己还没做上面任何一项建议先回去补课。否则后面的操作会因为网络、cgroup 这类基础问题反复折腾排查起来非常痛苦。2. 负载均衡层搭建Keepalived HAProxy 方案取舍与核心原理2.1 三种负载均衡方案为什么我选 Keepalived HAProxy自建 Kubernetes 高可用集群负载均衡层通常有三条路可以走。第一种是用云厂商的负载均衡服务。前提是你的机器在云上直接用 SLB/ALB 挂在三个 master 前面。优点是不用管运维缺点是每年的费用不低而且如果你的环境是本地机房或者内网隔离环境根本用不了。第二种是单独搞两台机器跑 HAProxy 或者 Nginx再配合 Keepalived 做一个 VIP。这种方案的好处是职责清晰负载均衡器和控制平面节点互不影响坏处是需要额外两台机器而且这两台机器的资源利用率其实很低纯粹为了一个 6443 端口转发而存在。第三种就是我在这个系列里用的方案在三个控制平面节点上直接部署 Keepalived HAProxy三个节点共同拥有一个 VIP。这样不需要额外机器负载均衡层和控制平面天然共享同一套高可用机制——某个 master 挂了VIP 漂移到其他 masterhaproxy 也跟着过去因为每台机器上都跑了一套完整的 haproxy。我选第三种的核心原因其实就一个字省。省机器、省维护成本、省网络复杂度。代价是 haproxy 和 keepalived 会占用一点点 CPU 和内存但对控制平面节点来说完全可以忽略。你要明白一个事实控制平面节点的负载通常不像业务节点那么高apiserver 的端口转发也就每秒钟几十上百个连接ha proxy 处理这点流量绰绰有余。2.2 Keepalived 的 VIP 切换机制以及两个必须注意的细节Keepalived 的核心是 VRRP 协议。简单理解就是多个节点组成一个虚拟路由器组选出一个 Master 持有 VIP其他节点作为 Backup 监听 Master 的心跳。Master 挂了Backup 在几秒内接管 VIP。对客户端来说它感知不到 IP 地址的变化。但是在控制平面节点上跑 Keepalived 有两个细节必须注意。第一个细节是 vrrp_script。默认情况下 Keepalived 只检测节点本身的存活状态不会检测 haproxy 进程是否健康。如果 haproxy 进程挂了但节点的网络和系统都正常Keepalived 认为 Master 还活着VIP 就不会漂移这时候整个集群的入口就断了。解法是在 Keepalived 配置里加一个vrrp_script定期检查 haproxy 进程一旦检测到 haproxy 挂了就降低当前节点的优先级触发 VIP 漂移到其他节点。这个细节我几乎每次都会被问到因为默认配置完全不包含它。第二个细节是网络层面的放行。VRRP 协议使用 IP 协议号 112不是 TCP 也不是 UDP。很多人在防火墙规则里开了 TCP 端口但 keepalived 之间依然无法通信VIP 始终飘不起来大概率就是 IP 协议 112 被防火墙拦截了。2.3 HAProxy 的转发配置用 TCP 四层不要加 HTTP 健康检查HAProxy 在这个场景下的作用很简单把发到 VIP:8443 的流量转发到三个 master 的 6443 端口。配置层面最核心的就是 mode 选 tcp 而不是 http。因为 Kubernetes apiserver 走的是 TLS 加密连接HAProxy 不需要也不能解密直接四层转发把 TLS 流量原样透传给后端 apiserver 即可。需要特别提醒的是健康检查这一块。很多人习惯在网络设备里配 HTTP 健康检查比如请求/healthz然后判断返回码。但在 HAProxy 里对 apiserver 做 HTTP 健康检查有个坑apiserver 的/healthz接口在未认证的情况下可能返回 403因为 Kubernetes 1.20 之后默认启用了匿名认证限制。如果你把expect status 200写进去HAProxy 会认为后端 apiserver 不健康导致所有节点被标记为 DOWN。最稳妥的做法就是只用 TCP 端口检查默认check参数会定期尝试连接后端的 6443 端口能连上就代表 apiserver 端口还在监听连不上就摘掉。TCP 检查可能无法感知 apiserver 内部的死锁或假死但 apiserver 真出现这种状态时更大概率是机器级别的问题VIP 漂移会兜底。前面说的 vrrp_script 端口检查结合已经是自建方案里性价比最高的组合了。3. 实操在三个控制节点上部署 VIP 与负载均衡3.1 安装与网络准备假设你的三个控制平面节点分别是节点IPk8s-master1192.168.1.11k8s-master2192.168.1.12k8s-master3192.168.1.13VIP192.168.1.100三台机器都执行安装命令。Ubuntu/Debian 系用 aptRocky/CentOS 用 yum。我这里以 Ubuntu 为例sudo apt update sudo apt install -y keepalived haproxy安装完成先别急着启动服务先检查配置文件路径和语法检查工具是否就绪。HAProxy 自带的语法检查命令是haproxy -c -fKeepalived 的语法检查是keepalived -t -f。这两个命令在后面改配置时非常有用避免因为配置格式错误导致服务起不来还找不到原因。3.2 编写 keepalived.conf优先级、脚本检查、VIP 声明每台机器上的配置结构基本一致只有 state、priority 和 interface 需要调整。master1 的完整配置如下global_defs { router_id k8s_lb_1 enable_script_security } vrrp_script check_haproxy { script /usr/bin/killall -0 haproxy interval 2 weight -20 } vrrp_instance k8s_lb { state MASTER interface eth0 virtual_router_id 51 priority 120 advert_int 1 authentication { auth_type PASS auth_pass k8s_lb_pass } virtual_ipaddress { 192.168.1.100/24 dev eth0 } track_script { check_haproxy } }master2 和 master3 只需要改三处router_id改成k8s_lb_2、k8s_lb_3state改成BACKUPpriority改成 110 和 100。virtual_router_id必须一致否则它们不认为自己在同一个虚拟路由器组里。auth_pass也要一致VRRP 报文认证失败会导致节点之间互不认账。配置写好后先执行语法检查sudo keepalived -t -f /etc/keepalived/keepalived.conf然后启用服务sudo systemctl enable --now keepalived启动后立即在 master1 上查看 VIP 是否挂载ip addr show eth0 | grep 192.168.1.100正常情况下 VIP 会出现在 master1 上。你可以手动停掉 master1 的 keepalived 测一下漂移但注意现在 haproxy 还没配置好漂移测出来的结果可能不准。建议先把 haproxy 配置完再统一验证。3.3 编写 haproxy.cfg监听 8443转发到三个 masterhaproxy 的配置文件是/etc/haproxy/haproxy.cfg直接覆盖写global log /dev/log local0 info maxconn 50000 user haproxy group haproxy defaults mode tcp timeout connect 5s timeout client 30s timeout server 30s log global frontend k8s_apiserver bind *:8443 default_backend k8s_apiserver_backend backend k8s_apiserver_backend balance roundrobin server k8s-master1 192.168.1.11:6443 check inter 3s fall 3 rise 2 server k8s-master2 192.168.1.12:6443 check inter 3s fall 3 rise 2 server k8s-master3 192.168.1.13:6443 check inter 3s fall 3 rise 2这里有几个点需要说明。bind *:8443是 haproxy 的对外监听端口为什么不直接绑 6443因为每个 master 节点自己的 apiserver 已经监听了 6443如果 haproxy 也绑 6443 会和本机 apiserver 冲突。8443 只是一个转发入口后面会在 kubeadm 的controlPlaneEndpoint里统一用VIP:8443。后端三个 server 写的是各节点的真实 IP 加 6443 端口不是 VIP。如果这里写成 VIP就会出现 haproxy 转发流量又回到自己身上的环路问题务必注意。check inter 3s表示每 3 秒检查一次后端可用性fall 3表示连续失败 3 次标记为不可用rise 2表示连续成功 2 次恢复可用。配置好之后执行sudo haproxy -c -f /etc/haproxy/haproxy.cfg sudo systemctl enable --now haproxy启动后用ss -lntp | grep 8443确认 haproxy 在监听再用nc -vz 192.168.1.11 6443确认从本机能连通三个 master 的 apiserver 端口。注意现在只有 master1 上还没有跑 apiserver所以nc测 6443 大概率失败这是正常的。你只需要确认 8443 端口本身在监听即可。3.4 验证 VIP 和转发链路三台机器都配置好 keepalived 和 haproxy 之后做一轮完整验证。先看一下 VIP 当前落在哪台机器上ip addr show | grep 192.168.1.100然后在任意一台机器上执行telnet 192.168.1.100 8443由于此时还没有 apiserver这个端口转发到后端时会被拒绝但你至少要看到 TCP 连接能够建立或者 haproxy 返回 503。这说明 VIP 到 haproxy 的链路是通的。更彻底的测试是直接在 master1 上临时起一个测试服务监听 6443看能不能通过VIP:8443访问到但这样比较麻烦。一个更实用的测试方法是在 master1 上先手动启动一个临时的 apiserver 监听 6443 不太现实所以这一段验证可以留到 4.2 节 kubeadm init 之后再补。实际经验告诉我负载均衡层真正能验收到什么程度取决于后端有没有真实服务。所以别急着断言链路不通等第一个 master 初始化完再回来测一遍。4. 实操初始化第一个控制平面节点并用 VIP 访问4.1 生成 kubeadm 配置controlPlaneEndpoint 是核心certSANs 决定成败在 master1 上执行sudo kubeadm config new kubeadm-config.yamlkubeadm config new是 Kubernetes 1.31 开始推荐使用的命令取代了之前的kubeadm config print init-defaults。生成出来的配置文件包含很多字段我们需要修改其中几个关键的。编辑kubeadm-config.yaml对应修改如下apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.11 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock name: k8s-master1 --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: v1.32.0 controlPlaneEndpoint: 192.168.1.100:8443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 etcd: local: dataDir: /var/lib/etcd apiServer: certSANs: - 192.168.1.100 - 192.168.1.11 - 192.168.1.12 - 192.168.1.13 - k8s-master1 - k8s-master2 - k8s-master3 - kubernetes.default.svccontrolPlaneEndpoint是整个高可用部署的灵魂配置。它告诉 kubeadm集群里所有组件访问 apiserver 时都用这个地址。这个地址必须是 VIP 加端口端口就是 haproxy 监听的 8443。这样 kubelet、controller-manager、scheduler 生成的 kubeconfig 里server 字段就自动变成https://192.168.1.100:8443而不是某个具体节点的 IP。certSANs是另一个容易踩坑的配置。apiserver 的证书里必须包含所有你可能会用它来访问集群的地址。如果你漏了 VIP那么 kubectl 通过 VIP 访问时就会报 x509 证书校验失败。这里我把三台 master 的 IP、域名以及 VIP 全部写进去了后续不管是直连某台机器还是走 VIP证书都能通过校验。podSubnet配置为10.244.0.0/16这里要和后面要装的 CNI 插件保持一致。不同 CNI 默认网段不同Calico 默认是192.168.0.0/16Flannel 默认是10.244.0.0/16。如果你选了 Calico记得后面在 custom-resources 里改这里先用哪个网段其实都行关键是两边要对上。4.2 执行 kubeadm init并分析输出配置改好后执行初始化命令sudo kubeadm init --configkubeadm-config.yaml --upload-certs--upload-certs这个参数只对高可用部署有意义。它的作用是把控制平面的证书比如 apiserver 的 CA 证书加密后上传到集群里这样后续 master2、master3 加入时不需要手动拷贝证书文件直接用一个 certificate-key 就能拉取证书。如果没有这个参数你后面加入其他 master 时就要手工把/etc/kubernetes/pki目录里的证书拷到新节点上容易遗漏文件。初始化过程大概持续一两分钟。日志最后会显示三条关键信息一是 kubectl 的配置命令二是 worker 节点的 join 命令三是 control-plane 节点的 join 命令和 certificate-key。这三段信息务必保存到本地文件里后面加入节点时要用。尤其是 certificate-key这个 key 只显示一次忘了的话需要重新执行sudo kubeadm init phase upload-certs --upload-certs获取新的。初始化完成后按提示配置 kubectlmkdir -p $HOME/.kube sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config然后用kubectl get nodes查看集群状态这时候你应该会看到 master1 的状态是 NotReady。这是正常的因为还没有安装 CNI 网络插件后续装完才会变成 Ready。4.3 安装 CNI 网络插件CNI 插件负责给 Pod 分配 IP、配置网络路由。没有它Pod 可以创建但无法拿到网络地址kubelet 会一直等 CNI 就绪节点也就永远处于 NotReady。我用 Calico 做示例版本选择 v3.29 系列兼容 Kubernetes 1.32kubectl create -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/tigera-operator.yaml然后下载 custom-resources 文件curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.29.2/manifests/custom-resources.yaml打开custom-resources.yaml把 default IPPool 的 cidr 改成10.244.0.0/16与 kubeadm 配置一致spec: calicoNetwork: ipPools: - cidr: 10.244.0.0/16 encapsulation: VXLAN改好后执行kubectl create -f custom-resources.yaml等待片刻后查看节点状态kubectl get nodesmaster1 会从 NotReady 变为 Ready。如果一直卡在 NotReady去kube-system命名空间里看 calico 相关 Pod 是否正常常见原因是镜像拉取失败或者 network 配置冲突。4.4 用 VIP 访问 apiserver验证负载均衡链路在 master1 上执行curl -k https://192.168.1.100:8443/version返回正常的 Kubernetes 版本信息说明整条链路已经通了客户端请求 VIP 的 8443 端口haproxy 转发到 master1 的 6443 端口apiserver 正常响应。再验证一下证书 SAN 里是否包含了 VIPopenssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text | grep -A 2 Subject Alternative Name输出里能看到IP Address:192.168.1.100以及三个 master 的 IP这就说明证书没问题。另外注意一个细节初始化后master1 上/etc/kubernetes/controller-manager.conf和/etc/kubernetes/scheduler.conf里的 server 地址kubeadm 会自动写成https://192.168.1.100:8443。可以检查确认一下这个地址正确说明 controlPlaneEndpoint 配置已经生效。5. 踩坑记录与问题排查速查表5.1 我在实际部署中踩过的坑这一节我想把过程中真正让我头疼过的问题写出来。有些坑我当时查资料查了很久甚至翻到社区的 issue 评论区才找到原因。第一个坑是 certSANs 漏了 VIP。有一次我部署测试环境VIP 配好了haproxy 也通了kubeadm init 也很顺利但 kubectl 用 VIP 访问时一直报x509: certificate is valid for 192.168.1.11, not for 192.168.1.100。原因是当时我认为 haproxy 只是转发层证书校验是通过转发到真实节点 IP 来完成的。但实际 kubeconfig 里配置的 server 地址是 VIPapiserver 返回的证书里必须包含 VIP 这个地址否则客户端校验直接失败。后来重新生成了 apiserver 证书才解决。所以certSANs必须是规划阶段就想清楚写进去初始化之后再改非常麻烦。第二个坑是 keepalived 没有配 vrrp_script。最初我为了图省事keepalived 配置里只有 virtual_ipaddress也加了 authetication但没跟踪 haproxy 进程。后来模拟故障时把 master1 的 haproxy 停掉发现 VIP 纹丝不动地留在 master1 上整个集群入口完全不可用。原因就是 keepalived 认为节点活着就不会触发漂移。从那以后我每次部署都会加上track_script这也成了我检查别人 keepalived 配置时必看的一项。第三个坑是网络方案互相干扰。我在另一套环境里kubeadm 的 podSubnet 用了默认的10.96.0.0/12而 Calico 的默认网段192.168.0.0/16又和公司办公网冲突了导致 Pod 跨节点访问时路由错乱表现为部分服务时而能通时而不通。最后统一改成10.244.0.0/16才稳定。这类问题定位起来非常耗时因为表面症状是 Pod 之间网络不通实际原因是网段冲突。5.2 常见错误对照表症状原因解决方法kubectl 报Unable to connect to the server: dial tcp ... connection refusedVIP 没有挂载或 haproxy 未监听 8443检查 keepalived 和 haproxy 状态确认 VIP 是否在某节点上kubectl 报 x509 证书错误提示证书不包含 VIP 地址certSANs缺少 VIP 或相关 IP更新certSANs后重新生成证书或重新初始化节点状态一直是 NotReadyCNI 网络插件未安装或网段不一致安装 CNI确认 podSubnet 和 CNI 的 IPPool 配置一致VIP 永远不漂移即使 haproxy 已停止缺少track_script或 VRRP 协议被防火墙拦截添加track_script放行 IP 协议 112两个节点同时出现 VIP 的脑裂情况VRRP 通信故障节点互相看不到对方心跳检查防火墙和交换机放行规则必要时改用单播模式kubeadm join 报 token 过期或校验失败初始化后等待过久token/证书密钥已更新重新执行kubeadm token create --print-join-commandhaproxy 后端全部显示 DOWN使用了 HTTP 健康检查apiserver 返回 403去掉option httpchk只保留 TCPcheck5.3 如何安全地回滚重来说实话第一次部署高可用集群一次成功的概率很低。不是 kubeadm 的问题而是中间环节太多很容易在某个配置上踩坑。如果 kubeadm init 之后发现无法挽救不要慌直接重置sudo kubeadm reset -f sudo rm -rf /etc/kubernetes /var/lib/etcd /etc/cni/net.d执行完这几条命令这台节点上的集群残留就清理干净了。注意kubeadm reset会重置 Containerd 的网络配置如果你发现重启之后容器运行不正常检查一下/etc/cni/net.d是否被清干净了。有一个细节如果你是三个 master 都想重来最好三台全部执行上面的清理命令。尤其要记得/var/lib/etcd这个目录里面存着 etcd 的数据文件。如果你只清一台另外两台还残留着旧的 etcd 成员信息后面重新 init 时会报etcd cluster is not healthy之类的错误。keepalived 和 haproxy 的配置不需要重来只要 VIP 和转发端口不变它们可以继续用。这样重置的成本其实很低重点是把 kubeadm 相关的配置和证书清干净。最后再分享一点实际经验如果你已经完成了上面所有步骤现在的集群状态应该是master1 已 ReadyVIP 能正常访问 apiserver流量经过 haproxy 转发到 master1 的 6443 端口。这时候我建议你停下来别急着加第二个 master。先做一个简单的故障演练把 master1 上的 haproxy 进程停掉观察 VIP 是否在几秒内漂移到 master2。如果漂过去了再把 haproxy 拉起来等整个服务恢复。这个练习虽然简单但能立刻暴露 keepalived 配置里的问题而这些问题在真正生产故障发生前发现成本是最低的。我在多个项目里都是靠这一招提前排查掉了隐患建议你一定要在加节点之前做一次。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →