高可用集群部署这件事,圈子里一直有个误区:一提 kubeadm 就想当然地认为它只能用来搭测试环境。实际上,kubeadm 是 Kubernetes 官方维护的部署工具,V1.32 时代的生产级高可用集群,完全可以只用 kubeadm 完成。我最近就在负责一套三 master 控制平面的环境升级,从负载均衡到证书、容器运行时、网络插件一路踩过来,发现真正的难点反而不在 kubeadm 本身,而在它周围的这些生态组件和细节决策。这篇文章就把我这次用 kubeadm 部署 Kubernetes V1.32 高可用集群的完整过程、参数取舍和踩坑记录写清楚,给准备在生产环境动手的人一个可复用的参考。
1. 高可用集群的架构决策:先理清负载均衡方案
动手敲命令之前,架构方案必须先定下来。很多人上来就 kubeadm init,结果到后来发现 master 节点之间没有统一入口、证书无法下发、流量没法分发,整个集群看起来是多个节点,实际上没有高可用可言。
1.1 为什么选 kubeadm,而不是二进制或发行版套件
业界部署 Kubernetes 的主流路径大致有三条:二进制手工部署、发行版套件(比如 RKE、Kubespray)、kubeadm。二进制部署能让你对每个组件都了如指掌,但维护成本极高,升级一个 kubelet 都要手动替换二进制、处理证书轮换。发行版套件自动化程度高,但问题在于它们常常封装了太多自己的逻辑,出问题时排查链路很长。kubeadm 恰好处在中间位置,它把 kube-apiserver、kube-controller-manager、kube-scheduler、kubelet、kube-proxy 这些核心组件的配置都纳入了标准化的管理方式,生成的静态 Pod 清单都在 /etc/kubernetes/manifests 下,任何时候你都可以直接查看和修改。
V1.32 里 kubeadm 对 etcd 的管理也更加成熟,stacked etcd 模式下 etcd 作为静态 Pod 运行在控制平面节点上,kubeadm 会自动处理成员加入和健康检查。这套机制保证了“部署完成之后,你知道每一个文件在哪里、每一个组件是怎么被拉起来的”。这对于长期运维来说太重要了。
1.2 三种高可用方案的对比与选择
控制平面高可用的核心,是让 kube-apiserver 有一个统一入口,并且这个入口本身不能被一台机器绑定死。常见的方案有三种:
| 方案 | 组件 | 适用场景 | 优缺点 |
|---|---|---|---|
| VIP + HAProxy | Keepalived、HAProxy 或 Nginx | 裸金属、自建机房 | 可控性强,无额外厂商绑定;需要维护两套组件 |
| 云厂商负载均衡 | SLB/ALB | 公有云环境 | 配置简单,自带健康检查;有云厂商费用和绑定 |
| kube-vip | kube-vip 静态 Pod | 追求极简 | 直接在 master 上跑 VIP,免去单独 LB;依赖 ARP/BGP 环境 |
我这次用的是“Keepalived + HAProxy 叠加在控制平面节点”的方式,也就是每个 master 节点上同时跑 HAProxy 和 Keepalived。这样做的好处是省掉了独立的负载均衡机器,三台 master 互为冗余,VIP 在它们之间漂移。如果你用的是公有云,我更推荐直接用云的负载均衡产品,把后端指向三个 master 的 6443 端口,省心很多。kube-vip 是新潮的方案,如果你对它的 ARP 模式有把握,也可以一试,但从运维熟悉度来说,Keepalived 依然是大多数自建机房的首选。
1.3 节点规划与网络拓扑
我这次的环境是三台 master、三台 worker,外加一个规划好的 VIP。具体地址规划如下:
| 角色 | 主机名 | IP 地址 |
|---|---|---|
| Master-1 | k8s-master01 | 192.168.10.11 |
| Master-2 | k8s-master02 | 192.168.10.12 |
| Master-3 | k8s-master03 | 192.168.10.13 |
| Worker-1 | k8s-node01 | 192.168.10.21 |
| Worker-2 | k8s-node02 | 192.168.10.22 |
| Worker-3 | k8s-node03 | 192.168.10.23 |
| VIP | - | 192.168.10.100 |
Pod 网段我规划为 172.16.0.0/16,Service 网段 10.96.0.0/12。这里必须提醒一句:Pod 网段千万不要和公司现有的内网网段重叠,否则后续路由和网段冲突排查会让你怀疑人生。
硬件方面,控制平面节点建议至少 4C8G,worker 节点根据业务负载决定,但最低也不要低于 2C4G。磁盘优先使用 SSD,特别是 etcd 所在的路径,IO 延迟直接影响 apiserver 响应。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 系统初始化里那些影响成败的隐藏项
很多部署失败的案例,问题根本不是出在 kubeadm 命令上,而是系统层没准备好。这些隐藏项不处理好,后面要么容器运行时报错,要么网络不通,要么证书异常。
2.1 系统版本与基础配置
操作系统我选用 Rocky Linux 9.3,内核版本 5.14,兼容性和稳定性都经过了充分验证。Ubuntu 22.04/24.04 也完全可以,步骤大同小异,这里以 Rocky/CentOS 系为例。
所有节点先做几件基础事:
- 配置静态 IP,不要用 DHCP,避免重启后节点 IP 变化导致集群失联。
- 关闭防火墙或者放行必要端口。生产环境建议放行端口而不是直接 systemctl stop firewalld。
- 禁用 SELinux,或者设置为 permissive 模式。Kubernetes 和容器运行时对 SELinux 的支持虽然已有进展,但在生产环境中仍然建议关闭以避免不可预见的权限问题。
bash复制sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config
setenforce 0
systemctl stop firewalld && systemctl disable firewalld
注意,每台机器的主机名必须唯一,并且保证节点之间可以通过主机名互相解析。如果没有内部 DNS,就在 /etc/hosts 里把六台机器的解析都配上。
2.2 内核模块、系统参数与时间同步
容器运行时要使用 overlay 文件系统,网络流量转发需要开启 bridge netfilter 相关参数。这些不是 Kubernetes 自动帮你完成的,必须手动配置:
bash复制cat <<EOF | tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
modprobe overlay
modprobe br_netfilter
cat <<EOF | tee /etc/sysctl.d/k8s.conf
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
EOF
sysctl --system
有两个容易忽略的关键点。第一,net.ipv4.ip_forward = 1 必须显式开启,否则 Pod 跨节点通信和 Service 的流量转发都会出问题。第二,swapoff -a 之后,要记得把 /etc/fstab 里的 swap 行注释掉,否则重启后系统又挂载 swap,kubelet 会直接报错拒绝启动。
时间同步也是必选项。etcd 对时钟偏移极其敏感,集群节点间时间差异太大会导致选举异常、心跳超时。安装 chrony 并启用:
bash复制yum install -y chrony
systemctl enable chronyd --now
chronyc sources -v
2.3 containerd 的安装和 Cgroup 驱动配置
V1.32 已经彻底移除了 dockershim,容器运行时事实上只有 containerd 这一条主路径。安装 containerd 直接用官方源即可,安装完成后需要修改配置,这一步是新手最容易踩坑的地方。
bash复制yum install -y yum-utils
yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
yum install -y containerd.io
mkdir -p /etc/containerd
containerd config default | tee /etc/containerd/config.toml
配置好后,最关键的一处修改:把 SystemdCgroup 设为 true。
bash复制sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml
为什么必须改这一项?Kubernetes 和 containerd 的 Cgroup 驱动必须一致。kubelet 默认使用 systemd 作为 Cgroup 驱动,如果 containerd 还停留在 cgroupfs,两者之间会产生资源统计和隔离不一致的问题,最直接的表现是节点上的 Pod 被反复杀死重启。很多 kubelet 一直报 Ready 状态异常的情况,追根溯源都是这个不匹配。
另一个需要处理的是 sandbox 镜像。containerd 默认配置里的 sandbox_image 地址在部分地区可能拉取困难,建议显式改为你自己选用的镜像仓库,后续在初始化阶段可以统一通过 --image-repository 参数控制。
改完配置后重启 containerd:
bash复制systemctl daemon-reload
systemctl restart containerd
systemctl status containerd
2.4 安装 kubeadm、kubelet、kubectl 并锁定版本
Kubernetes 官方为 RHEL 系专门提供了 1.32 的软件源,安装方式如下:
bash复制cat <<EOF | tee /etc/yum.repos.d/kubernetes.repo
[kubernetes]
name=Kubernetes
baseurl=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/
enabled=1
gpgcheck=1
gpgkey=https://pkgs.k8s.io/core:/stable:/v1.32/rpm/repodata/repomd.xml.key
EOF
yum install -y kubelet kubeadm kubectl
systemctl enable --now kubelet
这里我特别强调一个操作习惯:安装完成后立刻用 yum hold 锁住版本:
bash复制yum install -y yum-plugin-versionlock
yum versionlock kubelet kubeadm kubectl
原因很简单,Kubernetes 集群升级必须遵循版本控制策略,同一集群中的 kubeadm、kubelet、kubectl 版本要尽量保持一致。如果某天不小心执行了 yum update 导致这三个组件版本突变,轻则 API 兼容性问题,重则整个集群失联。锁定版本是生产环境的基本习惯。
3. 负载均衡层实操:HAProxy + Keepalived 部署
负载均衡是控制平面高可用的核心,但也是很多人最糊里糊涂的部分。我在这里把每个配置文件的作用都拆开讲清楚。
3.1 HAProxy 配置详解
在三台 master 节点上都安装 HAProxy:
bash复制yum install -y haproxy
HAProxy 配置文件 /etc/haproxy/haproxy.cfg 如下:
code复制global
log /dev/log local0
maxconn 4096
defaults
log global
mode tcp
option tcplog
retries 3
timeout connect 5s
timeout client 50s
timeout server 50s
frontend k8s-api
bind *:6443
default_backend k8s-masters
backend k8s-masters
balance roundrobin
server master01 192.168.10.11:6443 check
server master02 192.168.10.12:6443 check
server master03 192.168.10.13:6443 check
注意几个细节。第一,mode tcp 必须使用四层转发,因为 kube-apiserver 的流量是 TLS 加密的,七层模式会破坏 TLS 握手。第二,check 参数启用健康检查,HAProxy 会定期探测后端端口是否存活,如果某个 master 的 APIServer 挂了,流量会自动切换到其他节点。第三,三个节点的配置文件内容完全一致,后端都指向三台 master,这样无论 VIP 漂移到哪台机器上,流量都能被正确转发。
启动 HAProxy:
bash复制systemctl enable haproxy --now
systemctl status haproxy
3.2 Keepalived 抢 VIP 与健康检查脚本
Keepalived 的作用是为三个 HAProxy 提供一个漂移的 VIP。配置之前先安装:
bash复制yum install -y keepalived
以 master01 为例,/etc/keepalived/keepalived.conf 配置如下:
code复制vrrp_script chk_haproxy {
script "/etc/keepalived/check_haproxy.sh"
interval 2
weight -2
fall 3
rise 2
}
vrrp_instance k8s_lb {
state MASTER
interface ens18
virtual_router_id 51
priority 100
advert_int 1
authentication {
auth_type PASS
auth_pass k8s_lb_pass
}
virtual_ipaddress {
192.168.10.100/24 dev ens18
}
track_script {
chk_haproxy
}
}
master02 的配置相同,但 state 改成 BACKUP,priority 改成 90。master03 同样为 BACKUP,优先级 80。这样正常情况下 VIP 会落在 master01 上,当 master01 的 HAProxy 或整机故障时,VIP 漂移到 master02 或 master03。
健康检查脚本 /etc/keepalived/check_haproxy.sh 是防止脑裂的关键:
bash复制#!/bin/bash
if [ "$(ss -ln | grep -c ':6443')" -ge 1 ]; then
exit 0
else
exit 1
fi
这个脚本每 2 秒执行一次,如果本机没有进程监听 6443 端口,Keepalived 就把本节点的优先级降低 2,其他节点会立刻抢占 VIP。注意脚本必须添加执行权限:
bash复制chmod +x /etc/keepalived/check_haproxy.sh
systemctl enable keepalived --now
3.3 验证负载均衡效果
配置完成后,在任意一台机器上执行:
bash复制ip addr show | grep 192.168.10.100
正常情况下 VIP 会出现在 master01 的网卡上。然后用 VIP 测试连接:
bash复制curl -k https://192.168.10.100:6443/version
如果返回 JSON 格式的版本信息,说明负载均衡层已经打通。验证高可用时,可以手动停掉 master01 上的 HAProxy:
bash复制systemctl stop haproxy
观察 2 到 3 秒后,VIP 应该漂移到 master02。此时再执行一次上面的 curl,如果仍然正常返回,说明 HAProxy 和 Keepalived 配合正常。
4. 第一台控制平面节点初始化
负载均衡就绪后,开始初始化第一台 master。这一步骤是整个集群成败的胜负手,参数写错,后面全部白搭。
4.1 kubeadm init 各参数的含义
在 master01 上执行:
bash复制kubeadm init \
--control-plane-endpoint="192.168.10.100:6443" \
--kubernetes-version=v1.32.0 \
--apiserver-advertise-address=192.168.10.11 \
--pod-network-cidr=172.16.0.0/16 \
--service-cidr=10.96.0.0/12 \
--image-repository=registry.aliyuncs.com/google_containers \
--upload-certs
逐项拆解这些参数的含义:
--control-plane-endpoint:指向 VIP 地址。这是高可用集群区别于单节点集群最核心的参数。所有 kubelet、kubectl、kube-proxy 都通过这个地址访问 APIServer,不直接连接某一台 master。--apiserver-advertise-address:指定本机 APIServer 对外通告的 IP。如果不指定,kubeadm 会默认使用默认路由对应的网卡 IP,在多网卡环境下很容易选错。--pod-network-cidr:Pod 网段,必须和后续的 CNI 插件配置保持一致。--service-cidr:Service 网段,按需规划即可。--image-repository:指定镜像仓库。这个参数在当前网络环境下几乎是必需的,否则 kubeadm 会从registry.k8s.io拉取镜像,超时概率极高。--upload-certs:将控制平面证书上传到集群,便于后续其他 master 节点加入时自动拉取。这一点非常关键,如果不加这个参数,其他 master 加入时会面临证书不一致的问题,只能手动拷贝证书文件。
执行命令后如果一切正常,最后会输出一段 join 指令。务必把这个输出完整保存下来,后面加入 master 和 worker 节点都用得上。
4.2 kubectl 配置与 kubeconfig 文件说明
初始化完成后,按照提示配置 kubectl:
bash复制mkdir -p $HOME/.kube
cp /etc/kubernetes/admin.conf $HOME/.kube/config
这里解释一下 kubeconfig 的机制。/etc/kubernetes/ 目录下会生成很多关键文件:admin.conf 是管理员操作集群用的配置文件,kubelet.conf 供节点上的 kubelet 使用,controller-manager.conf 和 scheduler.conf 则分别对应这两个管理组件。它们本质上都是 kubeconfig 文件,里面包含了服务器地址、客户端证书、CA 证书等信息。你只需要管理员权限的 admin.conf 就够了。
执行:
bash复制kubectl get nodes
此时 master01 应该显示 NotReady,这是正常的,因为网络插件还没安装。
4.3 网络插件 Calico 安装与 Pod 网段规划
我这次选择了 Calico 作为 CNI 插件,原因很简单:它性能好、支持网络策略、安装文档详尽,生产环境验证最多。安装方式采用官方清单文件:
bash复制kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml
Calico 默认使用 BGP 模式,Pod 网段默认是 192.168.0.0/16。由于我规划了 172.16.0.0/16,所以必须修改 calico.yaml 中 CALICO_IPV4POOL_CIDR 这个环境变量的值。
具体操作:先把清单文件下载到本地,修改后再 apply。
bash复制curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.29.1/manifests/calico.yaml
sed -i 's/192.168.0.0\/16/172.16.0.0\/16/' calico.yaml
kubectl apply -f calico.yaml
注意一个细节:如果 Pod 网段和节点所在的内网网段冲突,Calico 会陷入反复路由计算的循环,表现为 Pod 间通信时好时坏。所以网段规划一定要提前想清楚。
安装完成后,观察 calico-system 命名空间下的 Pod 是否都处于 Running 状态:
bash复制kubectl get pods -n calico-system -w
等所有 Pod Running 后,再执行:
bash复制kubectl get nodes
此时 master01 的状态应该变为 Ready。这里有个常见的等待周期问题:从安装 Calico 到节点 Ready,通常需要 1 到 3 分钟,不是秒级生效,稍微有点耐心。
5. 扩展控制平面节点和工作节点
第一台 master 就绪后,集群还是单点。接下来把另外两台 master 加入,形成真正的控制平面高可用。
5.1 用 upload-certs 加入其他 master
回到 kubeadm init 的输出,找到类似下面这样的 join 命令:
bash复制kubeadm join 192.168.10.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash> \
--control-plane \
--certificate-key <key>
在 master02 和 master03 上分别执行这条命令。--control-plane 参数告诉 kubeadm 这是控制平面节点加入,会自动部署 APIServer、Controller-Manager、Scheduler 和 etcd 的静态 Pod。
如果当时没保存输出或者 join token 过期了,可以用以下命令重新获取:
bash复制kubeadm token create --print-join-command
kubeadm init phase upload-certs --upload-certs
第一条命令会生成新的 join token 和完整 join 命令,第二条命令会重新生成并上传证书 key。注意,证书 key 的有效期也是有限的,加入节点前需要确认它还没过期。
join 完成后,在 master01 上执行:
bash复制kubectl get nodes
应该能看到三台 master,全部处于 Ready 状态。再检查控制平面组件的健康状况:
bash复制kubectl get pods -n kube-system
三个节点的 APIServer、Controller-Manager、Scheduler、etcd 都会以静态 Pod 的形式运行。还要确认一下 etcd 集群成员:
bash复制kubectl get --raw='/healthz?verbose' | grep etcd
如果一切正常,说明控制平面已经形成了三节点的高可用组。
5.2 加入 worker 节点
worker 节点的加入命令更简单,不需要 --control-plane 和 --certificate-key,只需要前面输出的普通 join 命令。如果 token 过期,同样可以重新创建。
在三台 worker 节点上依次执行:
bash复制kubeadm join 192.168.10.100:6443 \
--token <token> \
--discovery-token-ca-cert-hash sha256:<hash>
join 完成后,在 master01 上再次执行 kubectl get nodes,六台节点都应该处于 Ready 状态。
5.3 集群健康状态验收与故障演练
部署完成的标志不是所有节点 Ready,而是核心服务都经受住了故障演练的检验。我的验收清单如下:
- 运行
kubectl get pods -A,确认所有系统 Pod 都 Running,没有 CrashLoopBackOff。 - 运行
kubectl get --raw='/healthz',应该返回ok。 - 运行
kubectl get cs,检查 controller-manager 和 scheduler 的状态。 - 创建一个测试 Deployment 并暴露 Service,验证 Pod 间通信和 NodePort 访问是否正常。
故障演练这部分容易被忽略,但对生产环境至关重要。我建议至少做三个场景的演练:
场景一:强制停止 master01 上的 kubelet。
bash复制systemctl stop kubelet
观察 VIP 是否漂移、kubectl 是否仍然可用。正常情况下,几分钟后所有依赖 APIServer 的操作都能继续,不受影响。
场景二:同时重启一台 master 节点的网络服务,观察 etcd 的 leader 是否自动切换。etcd 本身具备选举机制,只要不是同时挂掉两节点,集群都能自愈。
场景三:拔掉一台 worker 节点的网络,观察该节点上的 Pod 是否被调度到其他节点重建。这依赖于 PodDisruptionBudget 和节点故障检测的时间机制,不会立刻生效,但验证了故障自愈的完整链路。
6. 生产环境必须注意的坑
这一节的内容基本上是我在这次部署过程中真实踩过的坑,每一条都对应着实际运维中可能遇到的具体问题。
6.1 镜像拉取与国内源
kubeadm 默认从 registry.k8s.io 拉取镜像,这个地址在部分环境里拉取非常慢甚至超时。我使用 --image-repository=registry.aliyuncs.com/google_containers 解决了这个问题。但要注意,这只是 kubeadm 初始化时拉取系统组件的镜像,Calico 的镜像在 docker.io 下,如果服务器无法直连 Docker Hub,需要额外配置镜像加速器。
另外,如果使用 containerd,可以在 /etc/containerd/config.toml 中配置镜像加速:
toml复制[plugins."io.containerd.grpc.v1.cri".registry.mirrors]
[plugins."io.containerd.grpc.v1.cri".registry.mirrors."docker.io"]
endpoint = ["https://docker.mirrors.ustc.edu.cn"]
配置完记得重启 containerd。这条经验很重要:kubeadm init 之前的镜像问题好解决,因为参数里就能指定仓库;但 CNI 插件和其他自定义组件的镜像问题,必须靠 containerd 的镜像加速解决。
6.2 证书有效期管理
kubeadm 默认签发的证书有效期是 1 年。对于一个长期运行的生产集群,这意味着一件事:一年之后,你的集群会突然出现证书过期导致的访问异常。
证书到期前必须续期。查看当前证书有效期:
bash复制kubeadm certs check-expiration
续期所有证书:
bash复制kubeadm certs renew all
续期之后需要重启相关组件让新证书生效。控制平面组件都是静态 Pod,最简单的办法是重启 kubelet:
bash复制systemctl restart kubelet
但要注意,admin.conf 的证书需要额外处理。这个文件不在 /etc/kubernetes/pki 目录下,续期后需要重新拉取:
bash复制kubeadm init phase kubeconfig admin --output-dir /root
cp /root/admin.conf /etc/kubernetes/admin.conf
这步操作经常被遗漏,导致续期后 kubectl 仍然报证书过期。建议把证书续期做成定时任务或者至少写进运维手册,每年执行一次。
6.3 常见故障排查的层次化思路
生产环境的故障排查,最忌讳的就是东一榔头西一棒子。我习惯按照从下到上的层次来定位问题:
第一层,先看节点和容器运行时的状态。用 crictl ps -a 查看容器状态,用 journalctl -u kubelet -f 跟踪 kubelet 日志。很多 Pod 异常的原因在 kubelet 日志里一目了然。
第二层,看集群资源状态。kubectl describe pod <pod-name> 可以查看事件,kubectl logs <pod-name> 可以查看容器标准输出。如果 Pod 一直处于 ContainerCreating,重点关注镜像拉取、存储卷挂载和 CNI 网络配置。
第三层,看网络层。Pod 间通信异常时,检查 Calico 的 felix 日志,检查节点上的路由表:
bash复制ip route | grep 172.16
如果路由缺失,说明 Calico 的 BGP 邻居出了问题,需要检查 179 端口是否畅通、节点之间的网络策略是否放行了 BGP 流量。
第四层,看 API Server 层。如果 kubectl get nodes 超时或报连接拒绝,先用 curl -k https://VIP:6443/healthz 测试 APIServer 本身是否健康。如果健康,再检查 kubelet 连接 APIServer 的配置是否正确。
按这个顺序排查,大部分问题都能在 30 分钟内定位到根因。
我个人在实际操作中的体会是,kubeadm 部署高可用集群这件事,真正的门槛不在于 kubeadm 本身的用法,而在于对整体架构的理解和对细节的把控。负载均衡怎么做、证书怎么管理、容器运行时怎么配置、网络插件怎么选,每一个决策点都需要冷静分析。把这套集群部署完并成功通过故障演练之后,你不仅拿到了一套生产可用的环境,更会对 Kubernetes 控制平面的整个工作机制有更深的领悟。后续如果再往集群上扩展 worker 节点、部署存储方案、接入监控告警,都会顺畅很多。
