kubeadm部署多master高可用集群这件事,我相信很多朋友跟我一样,最早都是从单master起步的。单master在测试环境完全够用,可真到了准备上生产、或者需要对外提供稳定服务的时候,单控制面就成了所有人心里最不踏实的那根刺:apiserver一挂,整个集群的调度、心跳、控制面全部瘫痪,kubectl都不好使,运维只能干瞪眼。更别提如果只是etcd单点存储,真到宕机恢复的时候,数据安全也是大问题。
这篇文章我用kubeadm完整走一遍1.2x版本的多master高可用部署,覆盖整体架构设计、基础环境准备、haproxy+keepalived外部负载均衡层、控制面节点初始化与扩容、worker接入、以及最后的高可用故障演练。整篇适合两类人:一类是已经在单master上跑过一段时间、想补齐高可用能力的运维开发;另一类是刚接触k8s、希望直接按生产标准搭一套集群的新手。过程中我会把我实际踩过的坑和官方文档不会写清楚的地方都标出来,尽量让你照着做就能复现。
1. 高可用的本质:单控制面为什么不够,整体架构怎么搭
1.1 单master集群的三个致命场景
先说说为什么单master撑不住生产。Kubernetes控制面核心组件包括kube-apiserver、kube-controller-manager、kube-scheduler,以及存储状态的etcd。kube-apiserver是整个集群的唯一入口,所有kubectl请求、worker节点kubelet的心跳、控制器和调度器的读写,全都走它。一旦这台机器宕机或者apiserver进程异常,整个集群就进入了“有node但没人管”的状态:
- kubectl完全连不上,无法做任何变更、排查、发布;
- worker节点上的kubelet发不出心跳,控制器也没法正常调度Pod;
- 如果etcd也在同一台机器上并且磁盘损坏,那集群数据恢复起来就更头疼了,不只是“短暂不可用”,而是“数据可能没了一半”。
在生产环境里,这种控制面不可用的时间窗口越长,业务影响就越大。所以我们要做的并不是“多复制几台机器”,而是要保证控制面任何一台节点失效时,集群仍然能对外提供完整的读写能力,同时数据层面有可靠的一致性保证。
1.2 多master架构里的两个关键机制
第一个关键机制是etcd的Raft多数派协议。kubeadm在1.2x版本里默认采用的是堆叠式(stacked)etcd,也就是每一台master节点上都会跑一个etcd实例,三个master就是三个etcd副本。Raft协议要求写入必须得到多数派(也就是3节点中的至少2台)确认,所以只要不是同时挂掉两台master,etcd都能继续正常工作,不会出现数据脑裂或者写入冲突。
第二个关键机制是外部负载均衡层。虽然三台master都各自运行了kube-apiserver,但kubectl、kubelet、scheduler、controller-manager这些客户端不能“各连各的”,否则每个客户端视角中的master列表不一致,就会出现某些组件只跟某一台master通信的情况。所以必须引入一个统一的入口,也就是通过一个虚拟IP(VIP)或者内部域名,把请求分发到三台apiserver上。这个入口自身也要高可用,不然它挂了,所有流量还是进不去。
我这里用的是自建方式:两台haproxy做四层负载均衡,配合keepalived提供VIP漂移能力。如果你们环境里本身有云负载均衡产品,也可以把这一层完全托管掉,架构思路是一样的。小规模环境如果不要求LB本身高可用,只搭一台haproxy也能用,但这篇文章按生产标准,写成两台。
1.3 节点规划与版本选型
我建议的最小高可用规模是3台master加2台worker,再加2台LB机器。master数量选择3而不是2,原因很直接:etcd需要多数派,2台master中挂掉1台就只剩1台,无法形成多数派;3台master即使挂1台,剩2台仍然可以正常工作。这也是生产环境最常见、性价比最高的配比。
| 节点角色 | 主机名 | IP地址 | 建议配置 |
|---|---|---|---|
| master-01 | k8s-master-01 | 192.168.10.11 | 4C / 8G / 80G |
| master-02 | k8s-master-02 | 192.168.10.12 | 4C / 8G / 80G |
| master-03 | k8s-master-03 | 192.168.10.13 | 4C / 8G / 80G |
| worker-01 | k8s-worker-01 | 192.168.10.21 | 4C / 8G / 100G |
| worker-02 | k8s-worker-02 | 192.168.10.22 | 4C / 8G / 100G |
| lb-01 | k8s-lb-01 | 192.168.10.30 | 2C / 4G / 40G |
| lb-02 | k8s-lb-02 | 192.168.10.31 | 2C / 4G / 40G |
虚拟IP(VIP)我规划为192.168.10.100,后续所有kubectl、kubelet都通过这个地址访问apiserver。
版本选型方面,文章以Kubernetes v1.27.3为例,整个1.24到1.29系列都适用。需要注意几个配套组件的版本保持一致:kubeadm、kubelet、kubectl必须是同一个版本,不能混着用;containerd使用1.7.x系列;网络插件选calico,它在多master场景下最省心。操作系统我以Ubuntu 22.04为例,CentOS上的思路完全一样,只有包管理命令和部分配置文件路径不同。
2. 基础环境准备:网络、内核、容器运行时,每一样都别想跳过
2.1 hosts、内核模块和系统参数
这一步看起来基础,但实际部署中大量问题都出在这里。首先在所有节点上把每个节点的IP和主机名写进/etc/hosts,确保任何节点之间都能通过主机名互相访问,后面kubeadm初始化、join节点的时候会频繁使用主机名通信。
192.168.10.11 k8s-master-01 192.168.10.12 k8s-master-02 192.168.10.13 k8s-master-03 192.168.10.21 k8s-worker-01 192.168.10.22 k8s-worker-02 192.168.10.30 k8s-lb-01 192.168.10.31 k8s-lb-02然后开启Kubernetes需要的内核模块和系统参数。
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter cat <<EOF | sudo 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 sudo sysctl --system这里的net.bridge.bridge-nf-call-iptables如果不开,Kubernetes里ClusterIP类型的Service在跨节点访问时会出现网络不通的诡异问题,而且很难排查。net.ipv4.ip_forward则是Pod流量转发、NodePort访问的必备前提。如果你发现modprobe之后/proc/sys/net/bridge下看不到相关文件,可以先modprobe br_netfilter再看。
关闭swap也是必须的。kubelet对swap的依赖是它无法正确计算Node资源、QoS分类也会出问题,所以从1.2x开始kubelet默认不允许swap开启。执行下面命令,并把/etc/fstab里的swap行注释掉,确保重启之后也不会自动挂载。
sudo swapoff -a sudo sed -i '/ swap / s/^/#/' /etc/fstab2.2 容器运行时containerd的安装与配置
Kubernetes从1.24版本开始彻底不再支持docker作为运行时,而是通过CRI接口对接containerd。我们直接使用containerd就对了。这里我以Ubuntu上通过apt安装containerd.io为例,如果系统仓库里没有或版本太旧,也可以直接从容器运行时官方仓库下载对应版本的二进制包解压到/usr/local。
装好之后,需要生成一份默认配置文件:
sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml然后要改两个关键位置。第一个是cgroup驱动,Kubernetes节点默认的cgroup驱动是systemd,containerd默认配置是cgroupfs,如果不改,之后kubelet启动会不断报错,Pod也起不来。找到SystemdCgroup那一行,改成true。
[plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = true第二个是沙箱镜像pause。containerd创建每一个Pod之前都会先启动一个pause容器,默认使用的是registry.k8s.io/pause:3.9这类地址,如果你的服务器拉不了这个镜像,后面所有Pod都会卡在ContainerCreating。建议直接改成你自己环境能访问的镜像仓库里的pause镜像版本。
[plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.example.com/pause:3.9"改完执行sudo systemctl restart containerd,然后sudo systemctl status containerd确认状态是active。这一步很多人跳过,结果后面Pod全部起不来,排查半天才定位到是sandbox_image的问题。
2.3 安装kubeadm、kubelet、kubectl
kubeadm、kubelet、kubectl三个包在Debian系通过apt安装,CentOS系通过yum安装。国内服务器通常会把官方源替换成镜像站地址,这里要注意三个包必须指定同一个版本号,防止apt自动升级时把kubelet和kubeadm升到不同版本。
我以Ubuntu 22.04为例,先安装apt源需要的工具,然后添加Kubernetes的apt仓库。仓库地址你按自己实际能访问到的镜像站替换。
sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://mirrors.example.com/kubernetes/apt/doc/apt-key.gpg \ | sudo gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo "deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://mirrors.example.com/kubernetes/apt/ kubernetes-xenial main" \ | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet=1.27.3-00 kubeadm=1.27.3-00 kubectl=1.27.3-00 sudo apt-mark hold kubelet kubeadm kubectl最后一行apt-mark hold很关键,防止后续系统升级时把集群组件版本搞乱。安装完成后把kubelet设置成开机自启,比如sudo systemctl enable --now kubelet,但此时先不用启动它,kubelet会等kubeadm初始化完成之后拿到配置再正常工作。
2.4 提前拉取必要镜像
kubeadm init的时候会拉取一堆组件镜像,包括kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、pause等。如果你的服务器网络环境访问registry.k8s.io不顺畅,强烈建议先在第一个master节点上把镜像列表打出来,然后通过kubeadm配置文件里的imageRepository字段指向国内镜像站。
kubeadm config images list --kubernetes-version v1.27.3比如你用某个镜像站的google_containers项目,那在之后的kubeadm配置里就写imageRepository: registry.example.com/google_containers。为了验证仓库地址是否可用,可以先手动拉一遍:
kubeadm config images pull --image-repository registry.example.com/google_containers如果这条命令没有报错,说明镜像站和版本是可用的。这一步值得花时间做,后面init会顺畅很多。
3. 前置控制面入口:haproxy和keepalived的落地细节
3.1 为什么这一层不能直接省掉
有人可能会问:三台master上都装了apiserver,为什么我不直接在kubectl里配置三个地址,而要额外搭一套负载均衡?这里有个关键原因:Kubernetes的kubelet、controller-manager、scheduler以及各种客户端都需要通过一个稳定的地址访问apiserver,如果这个地址一会指向这个master、一会指向那个master,集群内的证书和连接都容易混乱。而且一旦某个master故障,客户端并不会自动切换,必须有负载均衡器来做健康检查和流量分发。
外部LB层的作用就是:对外暴露一个稳定的VIP,内部把流量转发到三台master的6443端口。keepalived负责VIP的高可用,haproxy负责后端节点的健康检查和负载均衡。当某台master挂了,haproxy会自动把它摘掉;当某台haproxy所在的LB节点挂了,keepalived会把VIP漂移到另一台LB节点。这样一个两层高可用机制,保证了控制面入口不会成为新的单点。
3.2 haproxy配置要点
两台LB节点上都需要安装haproxy,安装命令Ubuntu用apt install haproxy,CentOS用yum install haproxy。然后写配置文件/etc/haproxy/haproxy.cfg。这是一个最小可用的四层TCP负载均衡配置:
global log /dev/log local0 maxconn 4096 user haproxy group haproxy daemon defaults log global mode tcp option tcplog retries 3 timeout connect 5s timeout client 30s timeout server 30s frontend k8s_api bind *:6443 default_backend k8s_masters backend k8s_masters balance roundrobin option tcp-check server master-01 192.168.10.11:6443 check fall 3 rise 2 server master-02 192.168.10.12:6443 check fall 3 rise 2 server master-03 192.168.10.13:6443 check fall 3 rise 2这里有几个细节。mode tcp而不是mode http,因为apiserver的流量是HTTPS加密的四层流量,不需要也不应该在haproxy这一层做七层解析。option tcp-check表示用TCP层面的连通性来检测后端健康状态,比单纯依赖check更可靠。fall 3 rise 2表示连续3次失败才判定后端不可用,连续2次成功才重新启用,避免apiserver短暂重启时被频繁摘掉和加回。
配置好之后用haproxy -c -f /etc/haproxy/haproxy.cfg检查语法,然后systemctl restart haproxy,确认服务正常监听6443端口。
3.3 keepalived VIP配置
安装keepalived之后,在两台LB节点上分别配置keepalived。第一台LB节点设置为MASTER状态,第二台设置为BACKUP。注意两台机器的配置文件必须高度一致,否则VRRP协议会出问题。
LB-01节点的/etc/keepalived/keepalived.conf:
vrrp_script chk_haproxy { script "/usr/bin/killall -0 haproxy" interval 2 weight 2 } vrrp_instance k8s_vip { state MASTER interface eth0 virtual_router_id 51 priority 101 advert_int 1 authentication { auth_type PASS auth_pass K8S8char } virtual_ipaddress { 192.168.10.100/24 dev eth0 label eth0:vip } track_script { chk_haproxy } }LB-02节点把state改成BACKUP,priority改成100,其他内容保持一致。
这里最需要关注的是track_script。keepalived如果只绑定网卡状态,那么haproxy进程挂掉、但机器本身还活着的时候,VIP不会漂移,用户请求就会全部打到这台haproxy已经宕机的LB上。所以必须通过脚本检测haproxy进程,一旦进程消失,立即降低本机优先级,触发VIP切换到备用节点。
auth_pass长度不能超过8个字符,这是keepalived的硬性限制。另外如果两台LB之间的网络有交换机或者防火墙管控,要确保VRRP协议报文能正常通过,云主机上还经常碰到安全组必须显式放行VRRP协议的情况。
3.4 验证VIP与负载均衡状态
两台LB配置完成后,重启keepalived服务,然后从master或任意一台机器上验证。首先检查VIP是否已经在一台LB节点上出现:
ip addr show eth0正常情况下第一台LB节点上会多出一个eth0:vip的虚拟接口。然后验证通过VIP访问apiserver健康检查接口:
curl -k https://192.168.10.100:6443/healthz能看到ok就是正常的。这一步验证通过之后,再做一个简单的故障切换测试:在LB-01上停掉haproxy服务,观察VIP是否在几秒内漂移到LB-02上。
4. 第一个控制面节点:kubeadm配置与初始化的每一步
4.1 kubeadm配置文件详解
在所有准备工作完成之后,进入关键步骤:在master-01上初始化第一个控制面节点。我不推荐直接敲一串长长的kubeadm init命令行,而是建议把所有配置写进一个YAML文件,这样以后集群重建、二次部署都可以复用,别人review你的配置也一目了然。
在master-01上创建kubeadm-config.yaml:
apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.11 bindPort: 6443 nodeRegistration: criSocket: unix:///var/run/containerd/containerd.sock name: k8s-master-01 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.27.3 imageRepository: registry.example.com/google_containers controlPlaneEndpoint: 192.168.10.100:6443 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 apiServer: certSANs: - "192.168.10.11" - "192.168.10.12" - "192.168.10.13" - "192.168.10.100" - "k8s-master-01" - "k8s-master-02" - "k8s-master-03"几个字段必须认真对待。
controlPlaneEndpoint必须写成VIP地址加端口,也就是192.168.10.100:6443,而不是master-01自己的IP。后续所有节点、所有客户端拿到这个配置后,都会通过VIP访问控制面。
certSANs是很多人会踩坑的地方。apiserver作为服务端,它签发的TLS证书里必须包含客户端可能访问到的所有地址,否则客户端用VIP访问apiserver时会直接报certificate验证失败。所以这里除了三个master的IP和主机名之外,一定要把VIP也加进去。如果你后面还想用域名访问集群,再把域名加进这个列表。
podSubnet我用的是calico默认支持的10.244.0.0/16,后面装calico时记得保持一致。如果你环境里这个网段和现有网络冲突,换个网段后要同时改这里和calico的配置。
4.2 执行init并理解关键输出
配置文件准备好之后,在master-01上执行初始化:
sudo kubeadm init --config kubeadm-config.yaml --upload-certs--upload-certs这个参数不是可选项,对多master集群至关重要。它会把控制面节点之间通信所需的证书和密钥上传到集群的Secret中,并在输出里生成一个certificate key。后面第二、第三个master加入时,必须带上这个key才能从集群里获取这些证书,从而无缝加入控制面。
初始化过程一般会持续几分钟,期间主要在拉取镜像、生成证书、启动apiserver/controller-manager/scheduler的静态Pod。命令执行成功之后,末尾会输出三块内容:
- 配置kubectl所需的几条cp命令;
- 让worker节点加入集群的kubeadm join命令(包含token);
- 让其他master节点加入控制面的kubeadm join命令(包含token和certificate key)。
这三块内容最好都保存下来,特别是kubeadm join命令。如果当时忘记保存,后面仍可以通过命令重新生成token获取join命令。
4.3 kubectl配置与单机验证
初始化完成后,在master-01上执行:
mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config然后kubectl get nodes,应该能看到master-01状态是NotReady,页面显示为NotReady是正常的,因为网络插件还没装。此时还要检查一下核心组件Pod是否正常运行:
kubectl get pods -n kube-system如果一切正常,你会看到kube-apiserver、etcd、kube-controller-manager、kube-scheduler以静态Pod方式运行在master-01上,状态为Running或ContainerCreating。
4.4 安装calico网络插件
没有网络插件之前,控制面虽然起来了,但集群内Pod之间、Pod与Service之间是彻底不通的,所以NotReady状态会一直存在。这里使用calico网络插件。先获取calico的manifest文件:
curl -O https://raw.githubusercontent.com/projectcalico/calico/v3.26/manifests/calico.yaml我一般会在这个文件里做两处调整。第一处是把Pod网段改成和kubeadm配置里的podSubnet一致,找到CALICO_IPV4POOL_CIDR环境变量,确认值是10.244.0.0/16。第二处是设置网卡自动探测方法。如果服务器只有一张内网网卡,可能不需要改;但如果有多个网卡,就得显式指定用哪块网卡,否则calico可能选错网卡导致跨节点通信失败。
在calico.yaml的env配置里加入:
- name: IP_AUTODETECTION_METHOD value: "interface=eth0"修改之后执行:
kubectl apply -f calico.yaml等待几分钟,kubectl get pods -n kube-system里所有calico相关的Pod应该会慢慢变成Running,然后kubectl get nodes应该会看到master-01的状态变成Ready。
到这里,第一个控制面节点已经就绪,但它还是孤单的。集群的高可用能力,要靠下一节把master-02、master-03拉进来才能真正建立起来。
5. 控制面扩容与worker接入:从join到全部Ready
5.1 重新生成join命令
如果刚才初始化时保存了join命令,可以直接用。如果像我一样经常忘记保存,也没关系,在master-01上重新生成即可。
给worker节点生成join命令:
kubeadm token create --print-join-command给其他master节点生成包含证书key的join命令:
kubeadm init phase upload-certs --upload-certs这条命令会输出一个新的certificate key,然后把它和token拼到一起,就是完整的master join命令。需要注意,如果已经有一堆master节点加入过了,再重新upload-certs会重新生成一套证书密钥。这个操作在控制面扩容前做是没有问题的。
5.2 第二个、第三个master加入控制面
在master-02上提前完成基础环境配置(同第2节的hosts、内核参数、containerd、kubeadm安装),然后执行master join命令。典型的命令长这样:
sudo kubeadm join 192.168.10.100:6443 --token <token> \ --discovery-token-ca-cert-hash sha256:<hash> \ --control-plane --certificate-key <key>注意到我已经把join地址写成了VIP地址192.168.10.100:6443,而不是master-01的IP,这样新master才能通过统一的控制面入口完成接入。
--control-plane参数告诉kubeadm这是控制面节点,它会在本机拉起来etcd和apiserver的静态Pod,并从集群Secret中拉取证书。master-02和master-03都执行同样的命令,执行顺序没有要求,但不建议同时在两台机器上join,前面先等完成了再处理下一台,方便观察日志定位问题。
master-02加入完成之后,回到master-01上执行:
kubectl get nodes正常情况下会看到三个master节点,并且都处于Ready状态。再看一下etcd集群状态:由于三台master上都在跑etcd,它们会自动通过Raft组成一个三节点的etcd集群。你可以随时kubectl get pods -n kube-system | grep etcd确认三个etcd Pod都是Running。
这里有一个很常见的坑:master-02的join命令执行完之后,如果长时间卡在等待control-plane,不要急着Ctrl+C。第一件事是kubectl get pods -A看看master-02上的apiserver和etcd状态;第二件事是journalctl -u kubelet -f看kubelet日志。多数情况下问题出在镜像拉取、证书key不对、或者防火墙没放行端口。
5.3 worker节点加入
worker节点的加入比master简单得多,只需要token,不需要--control-plane和--certificate-key。在worker-01和worker-02上执行:
sudo kubeadm join 192.168.10.100:6443 --token <token> \ --discovery-token-ca-cert-hash sha256:<hash>worker节点join成功后会作为普通计算节点注册到集群中。回到master-01执行kubectl get nodes,所有节点都应变成Ready。此时可以用一个调度测试验证集群的完整性:
kubectl run test-nginx --image=nginx --replicas=2 kubectl get pods -o wide看到两个Pod分别调度到不同node上并且Running,说明整个集群已经从“多点控制面”到“Pod调度”全链路打通了。
5.4 节点角色与标签确认
多master集群起来之后,可以用kubectl get nodes -o custom-columns=NAME:.metadata.name,ROLES:.metadata.labels,STATUS:.status.conditions查看节点角色。master节点上默认带着node-role.kubernetes.io/control-plane污点,普通业务Pod不会调度到master上。如果你希望某些管理类组件或少量测试Pod也能跑在master节点上,需要给这些Pod单独加容忍,但生产环境不建议这样做。
另外我建议尽早给worker节点打上用途标签,比如zone、disk类型、GPU等,为后续工作负载的亲和性调度做准备。这些标签在部署有状态服务时价值很大。
6. 高可用验证与故障演练:模拟真实宕机后的表现
6.1 演练一:master节点宕机
部署完成不等于高可用,只有真正故障演练验证过之后,你才能对这套集群放心。我最推荐做的第一个演练,就是把其中一台master直接关停,观察集群是否仍然可用。
先选择一台master,比如master-01,在它上面执行:
sudo systemctl stop kubelet停掉kubelet之后,这台master上的apiserver、etcd静态Pod也会被停止或进入短暂异常状态。此时你从另一台机器上执行:
kubectl get nodes只要apiserver仍然正常响应,并且node列表里其余节点显示Ready,就说明控制面已经由另外两台master接管了。继续kubectl run test-nginx --image=nginx,Pod应该能正常创建和调度,这说明调度器和控制器也在正常工作。
需要注意的一点是:三台master中挂掉一台时,etcd仍然满足多数派(2/3),所以集群完全可用。但如果同时挂掉两台master,etcd就无法满足多数派,整个控制面会进入只读或不可用状态。因此生产环境里至少保持3台master是有硬道理的。
恢复方式很简单:
sudo systemctl start kubeletkubelet会自动重新拉起master-01上的静态Pod,等Pod恢复后kubectl get nodes会看到master-01回到Ready,etcd也会重新加入集群。整个过程不需要手动干预,这也是kubeadm堆叠架构比较省心的地方。
6.2 演练二:负载均衡节点故障
第二部分故障发生在LB层。在LB-01上停掉haproxy:
sudo systemctl stop haproxy注意此时LB-01机器本身还活着,但haproxy进程没了。keepalived的track_script脚本大约两秒后会探测到进程异常,从而降低本机优先级,VIP会漂移到LB-02。你可以先在LB-02上执行ip addr show eth0确认VIP已挂上来,然后再次通过VIP访问apiserver健康检查:
curl -k https://192.168.10.100:6443/healthz如果返回ok,说明控制面的外部入口切换是成功的。这个演练对客户端完全无感,因为客户端访问的始终是同一个VIP。
恢复haproxy之后,如果你用的是上面示例中的抢占模式配置,VIP可能会自动漂回LB-01;如果你在生产环境里希望漂移后尽量不抖动,可以考虑使用nopreempt非抢占模式,具体取舍取决于你们的网络场景。这里我想特别提醒:VIP漂移后如果业务侧有长连接,可能会短暂中断,但在Kubernetes场景下,kubectl和组件重试机制比较完善,影响通常可控。
6.3 常见问题对照表
下面这个对照表是我在多次部署过程中整理出来的,供你在排错时快速定位。能提前看一遍,很多问题就能在前面规避掉。
| 症状 | 可能原因 | 排查方向 |
|---|---|---|
| kubeadm init卡在等待apiserver | 镜像拉取失败、cgroup驱动不对 | 看kubectl get pods -n kube-system,journalctl -u kubelet -f |
| 通过VIP访问apiserver报证书错误 | apiServer.certSANs里漏掉了VIP | 检查kubeadm配置文件certSANs,重新初始化或更新证书 |
| join时报sha256校验不一致 | token过期或哈希拼写错误 | 在master上重新执行kubeadm token create --print-join-command |
| 节点一直NotReady | 网络插件未安装或选错网卡 | 检查calico相关Pod日志,确认IP_AUTODETECTION_METHOD |
| Pod一直ContainerCreating | sandbox_image里的pause镜像拉不到 | 修改containerd配置里的sandbox_image并重启containerd |
| containerd报cgroup错误 | SystemdCgroup未设为true | 改/etc/containerd/config.toml,重启containerd |
| VIP ping不通 | keepalived配置不匹配、VRRP被安全组拦截 | systemctl status keepalived,检查日志和防火墙 |
| 两台master同时宕机后集群不可用 | etcd失去多数派 | 生产环境尽量保持3台master,不要少于3台 |
另外一个值得关注的点是证书续期。从1.2x版本开始,kubeadm默认对kubelet、admin.conf等证书做自动续期,正常轮转不需要人为干预。但如果你部署完后把集群放到一边不管超过一年,又突然发现kubectl证书过期,可以用kubeadm certs renew all手工续期,然后在kubectl需要重新更新~/.kube/config。
6.4 日常运维中的几个经验习惯
多master集群跑起来之后,日常运维和单master有些区别。我自己的习惯是:
第一,所有面向集群的操作都通过VIP或域名进行,不要直接连某台master的IP,这样既能利用负载均衡的故障转移能力,也能让kubeconfig里的服务器地址始终保持稳定。
第二,监控维度要比单master集群多一层。除了看kube-apiserver、etcd本身的状态,还要关注haproxy后端健康检查和keepalived的角色状态,这两个指标直接决定了控制面外部入口是否可靠。etcd的磁盘IOPS和延迟也要盯紧,etcd对磁盘延时的容忍度比你想象中低得多。
第三,在做任何控制面节点的维护操作前,先确认至少还有两台master在线,避免无意中把集群降级到“只剩一台控制面”的危险状态。尤其在做etcd备份、节点重启、内核升级这类操作前,记得先确认集群健康状况。
最后再分享一个我自己踩过不少次的细节:如果你在云主机上部署,VIP之外一定要检查安全组对VRRP协议的放行情况。keepalived的配置看起来完全没问题,但VIP就是起不来,多半是VRRP报文被安全策略拦掉了。这类问题排查成本很高,建议在规划阶段就把网络策略列表写清楚。
多master高可用集群搭完之后,后面要做的就是把生产环境里的Ingress、监控、日志、证书管理这些组件逐步铺上去。但地基打得稳不稳,就看控制面这一层的高可用是否经得起故障考验。希望这篇文章能帮你把这一步走扎实。