三台Ubuntu搭建高可用Kubernetes实验集群
三台Ubuntu搭建高可用Kubernetes实验集群
前面几篇文章更多是在拆解 Kubernetes 的资源对象、Pod 生命周期和探针机制,这篇文章换一个角度,记录一次更偏基础设施层面的实验:在三台 Ubuntu 主机上,手动搭建一个带控制平面高可用入口的 Kubernetes 集群。
这次环境本质上是一个实验版高可用方案。它已经具备了控制平面的基本高可用能力:
- 三台控制节点
HAProxy做 API Server 转发Keepalived提供虚拟 IPkubeadm初始化集群Cilium作为网络插件
但它还不是一套完全意义上的生产级“完整高可用 Kubernetes”。原因也很直接:实验资源有限,我这次只完成了控制面的关键链路验证,没有把工作节点、外部 etcd、监控、备份、灾备等能力全部铺满。
所以这篇文章会分成两部分:
- 第一部分:按照这次实验的真实过程,整理出一套可复现的部署步骤
- 第二部分:在文章最后补全一套更完整的高可用 Kubernetes 应该长什么样
一、实验环境说明
这次实验环境一共三台 Ubuntu 主机,全部承担控制平面相关角色:
| 主机名 | CPU / 内存 | IP |
|---|---|---|
master1 |
4 核 / 4GB | 10.102.213.94 |
master2 |
4 核 / 10GB | 10.102.213.185 |
master3 |
4 核 / 10GB | 10.102.213.43 |
另外还规划了一个虚拟 IP:
10.102.213.100
这个 VIP 由 Keepalived 漂移,对外统一提供 Kubernetes API 入口;HAProxy 则监听 16443 端口,把请求转发到三台控制节点的 6443。
也就是说,这次控制平面的访问路径是:
kubeadm / kubectl |
二、部署目标和思路
这次实验要解决的核心问题,不是“单机把 Kubernetes 装起来”,而是下面这几个点:
- 控制平面不能只依赖单台机器
- API Server 入口需要是稳定地址,不能写死某一台 master
- 节点重启后,基础运行时和 kubelet 能自动拉起
- 网络插件要能正常接管 Pod 网络
所以整体思路是:
- 先把三台机器的系统前置条件处理好
- 手动安装
containerd、runc、kubeadm、kubelet、kubectl - 用
HAProxy + Keepalived做控制面的统一入口 - 通过
kubeadm init初始化第一台控制节点 - 再让其他节点加入控制平面
- 安装
Cilium作为 CNI,并替代kube-proxy
三、主机初始化
这部分需要三台机器都执行。
1. 配置 hosts
为了让节点之间的名字解析更直接,先写好 /etc/hosts:
cat >> /etc/hosts << EOF |
这样后面无论是排障还是查看组件状态,都会比只看 IP 清楚很多。
2. 关闭 swap
Kubernetes 默认要求关闭 swap,否则 kubelet 在很多场景下会直接报错或行为不符合预期。
swapoff -a |
这里要注意两点:
swapoff -a只是临时关闭/etc/fstab里的 swap 项如果不去掉,机器重启后还会重新挂载
3. 加载内核模块
容器网络和 Kubernetes 转发依赖一些内核模块,至少要准备:
cat > /etc/modules-load.d/k8s.conf << EOF |
这两个模块的作用可以简单理解为:
overlay:为容器镜像和文件系统能力做准备br_netfilter:让桥接流量进入 iptables / netfilter 处理链
4. 配置网络参数
Kubernetes 常见的几个内核参数也需要提前设置好:
cat > /etc/sysctl.d/99-k8s.conf << EOF |
这里最关键的是:
- 开启 IPv4 转发
- 让桥接流量能被 iptables 处理
如果这一步没做好,后面即使集群能起来,Pod 网络、Service 转发也很容易出现问题。
四、安装容器运行时和 Kubernetes 组件
这次实验采用的是手动安装二进制的方式,而不是直接用系统包管理器。
这种方式的优点是:
- 组件版本更可控
- 适合理解 Kubernetes 各组件到底依赖什么
- 排查问题时更容易看清楚 systemd、二进制路径和配置文件之间的关系
缺点也很明显:
- 步骤更繁琐
- 更容易漏掉 systemd 单元文件
- 后续升级要自己负责
1. 安装 containerd 和 runc
先下载并安装 containerd:
wget https://github.com/containerd/containerd/releases/download/v2.2.2/containerd-2.2.2-linux-amd64.tar.gz |
再安装 runc:
wget https://github.com/opencontainers/runc/releases/download/v1.2.5/runc.amd64 |
2. 下载 kubeadm、kubelet、kubectl
export KUBERNETES_VERSION=$(curl -L -s https://dl.k8s.io/release/stable.txt) |
下载完成后放到可执行路径:
chmod +x kubeadm kubectl kubelet |
五、配置 containerd
containerd 是这次实验里最底层的运行时。它的配置如果不提前理顺,后面的 kubeadm init 往往会踩很多坑。
1. 生成默认配置并切到 systemd cgroup
mkdir -p /etc/containerd |
这里最重要的是 SystemdCgroup = true。
因为当前主流 Linux 发行版上,kubelet + containerd 最稳妥的组合通常就是 systemd cgroup 驱动。如果 kubelet 和容器运行时的 cgroup 驱动不一致,经常会出现节点异常、资源统计不准甚至 kubelet 启动失败的问题。
2. 配置镜像加速
实验环境里,镜像拉取往往是最容易卡住的一步,所以这里额外配置了 containerd 的 registry mirror:
|
这一步的核心不是“必须用哪一家镜像加速”,而是要理解:
containerd可以通过certs.d/<registry>/hosts.toml配置镜像源config.toml里的config_path要正确指向这个目录- 如果这一步没生效,后续拉取控制平面镜像和 CNI 镜像时会非常痛苦
3. 配置 containerd 的 systemd 服务
手动安装二进制时,containerd.service 往往要自己补:
[Unit] |
配置完成后记得:
systemctl daemon-reload |
六、配置 kubelet
如果说手动安装里最容易漏的是哪一步,kubelet 的 systemd 配置基本一定排得上号。
1. 创建 kubelet.service
cat <<EOF > /etc/systemd/system/kubelet.service |
2. 创建 kubeadm 依赖的 drop-in 配置
mkdir -p /etc/systemd/system/kubelet.service.d |
最后开启服务:
systemctl daemon-reload |
这里即使 kubelet 先启动报错也没关系,因为在 kubeadm init 之前,它缺少真正的 bootstrap 配置文件是正常现象。
七、配置控制平面高可用入口
这一步是这次实验的重点。严格来说,三台 master 本身并不自动等于“高可用”,你还需要一个稳定的 API Server 访问入口。
这次实验采用的是:
HAProxy:四层负载均衡Keepalived:VIP 漂移
1. 配置 HAProxy
HAProxy 负责把访问 VIP 的请求转发到三个 API Server:
global |
这里用 mode tcp 很重要,因为 Kubernetes API Server 是 HTTPS/TLS 流量,这里做的是四层转发,不是七层 HTTP 代理。
2. 配置 Keepalived
三台机器都配置 Keepalived,但优先级不同。
master1:
vrrp_script check_haproxy { |
master2:
vrrp_script check_haproxy { |
master3:
vrrp_script check_haproxy { |
然后启动服务:
systemctl enable haproxy keepalived |
这套设计的核心逻辑是:
- 正常情况下 VIP 落在优先级最高的节点上
- 如果
haproxy挂了,优先级会下降 Keepalived会把 VIP 漂移到其他节点
这就让外部始终只需要访问一个固定地址:10.102.213.100:16443。
八、初始化 Kubernetes 控制平面
高可用入口准备好之后,就可以在 master1 上进行首次初始化。
1. 生成 kubeadm 配置
先导出默认配置,再按自己的环境修改:
kubeadm config print init-defaults > kubeadm-config.yaml |
这次实验使用的关键配置如下:
apiVersion: kubeadm.k8s.io/v1beta4 |
这里最关键的几个点分别是:
controlPlaneEndpoint不能写某台 master 的真实 IP,而应该写 VIPimageRepository用镜像加速源,避免拉镜像卡住cgroupDriver与前面的 containerd 保持一致
2. 执行初始化
kubeadm init --config kubeadm-config.yaml --upload-certs --skip-phases=addon/kube-proxy |
这里显式跳过了 kube-proxy,因为后面准备用 Cilium 的 kubeProxyReplacement=true 模式直接接管 Service 转发能力。
初始化完成后,把管理员配置拷到当前用户目录:
mkdir -p $HOME/.kube |
九、安装 Cilium 网络插件
如果不装 CNI,控制平面虽然能初始化成功,但 Pod 网络不会真正可用。
这次选择的是 Cilium,并启用了 kube-proxy replacement:
CILIUM_CLI_VERSION=$(curl -s https://raw.githubusercontent.com/cilium/cilium-cli/main/stable.txt) |
安装命令如下:
cilium install \ |
这里有两个关键点:
k8sServiceHost和k8sServicePort明确指向高可用 API 入口kubeProxyReplacement=true说明 Service 相关能力交给 Cilium 处理,而不是再依赖 kube-proxy
安装完成后,可以继续用下面这些命令确认状态:
kubectl get pods -A |
十、这次实验方案解决了什么问题
虽然这是实验版集群,但它已经把高可用控制面的几个核心问题打通了。
1. API Server 不再依赖单点
如果你把 kubectl、kubeadm join 或其他控制面访问都直接写成某一台 master 的地址,那么这台机器一旦故障,整个控制面入口就会失效。
通过 VIP + HAProxy,现在外部只关心:
10.102.213.100:16443 |
底层到底是哪台节点接管请求,对使用者是透明的。
2. 控制节点可以横向扩展
只要 kubeadm init 完成后生成 join 命令,后续其他 master 节点就可以继续加入控制平面。这样比一开始就把整个集群压在单节点上更稳。
3. 网络层具备继续扩展的基础
Cilium 接管之后,后续再加 worker 节点、再跑业务 Pod,会比单纯靠默认组件更灵活。
十一、这套实验方案还不算完整高可用的地方
这部分也必须说清楚。很多“实验环境里的高可用”其实只是做到了入口高可用或者控制平面多副本,但距离真正生产可用还差一截。
这次实验主要还缺下面这些部分:
- 没有单独规划 worker 节点池
- etcd 没有单独拆成独立集群进行管理说明
- 没有覆盖证书备份、etcd 快照和恢复演练
- 没有补监控、日志、告警体系
- 没有补存储高可用方案
- 没有补应用层的发布、回滚、限流与容灾策略
也就是说,这次更准确的表述应该是:
我完成的是一套控制平面实验级高可用部署,而不是一套生产级全栈高可用 Kubernetes 平台。
这个区别在面试或者实际工作里都非常重要,因为很多故障根本不是 API Server 多副本就能解决的。
十二、完整的高可用 Kubernetes 应该是什么样
如果资源和时间充足,一套更完整、更接近生产的高可用 Kubernetes,通常至少应该补齐下面这些层面。
1. 控制平面高可用
这是你这次实验已经覆盖到的核心部分:
- 至少 3 台 control plane 节点
- API Server 前面有稳定负载均衡入口
- 推荐使用
HAProxy + Keepalived,或者云厂商的 SLB / NLB - 所有节点通过统一
controlPlaneEndpoint接入
这一层解决的是:控制面组件不要因为单机故障就整体不可访问。
2. etcd 高可用和备份能力
真正生产环境里,etcd 是整个集群最关键的数据平面之一。
比较稳妥的做法通常是:
- 使用 3 台或 5 台 etcd 成员
- 与业务流量隔离,尽量不要和高负载业务混布
- 定期做 etcd snapshot
- 验证快照恢复流程,而不是只“以为备份了”
如果只做了 control plane 多副本,却没有 etcd 备份与恢复方案,那么集群实际上还是脆弱的。
3. 工作节点池高可用
完整集群不能只有 master,还应该有独立 worker 节点池:
- 至少 2 到 3 台 worker 起步
- 不同业务按节点池或标签做隔离
- 关键业务尽量跨节点、跨故障域部署
- 配合
taint、toleration、nodeSelector、affinity做调度约束
这层解决的是:业务负载不要和控制面混跑,也不要因为单台 worker 故障就掉服务。
4. 网络高可用
一个完整 HA Kubernetes 不只是“Pod 能互通”,还要考虑网络组件本身的稳定性:
- CNI 插件要支持多节点稳定运行
- CoreDNS 至少双副本
- Ingress Controller 至少双副本
- 南北向流量入口要有负载均衡能力
- 网络策略要能限制横向访问风险
换句话说,网络不仅要通,还要稳、可控、可观测。
5. 存储高可用
如果集群里跑数据库、中间件或者任何需要持久化的数据服务,就必须考虑存储层:
- 动态存储供应器
- 后端共享存储或分布式存储
- 卷的快照和恢复
- 跨节点挂载能力
常见方向包括:
- NFS 作为实验起点
- Ceph / Rook 作为更完整的分布式存储方案
- 云厂商块存储 / 文件存储作为托管方案
没有存储高可用,很多“应用高可用”其实也只是表面高可用。
6. 可观测性和告警
生产环境至少要知道集群现在是不是在出问题,而不是等用户反馈:
Prometheus + AlertmanagerGrafana- 日志采集方案,比如
Loki或EFK - 节点、Pod、容器、控制面组件的关键指标监控
如果没有这一层,集群出故障时你只能靠 kubectl describe 和运气排查。
7. 安全和权限治理
完整高可用不只是“服务别挂”,还包括“别因为权限和安全问题出事故”。
至少要考虑:
- RBAC 最小权限
- Secret 管理
- 审计日志
- 节点基线加固
- 镜像仓库访问控制
- NetworkPolicy
很多时候集群不是因为不可用出问题,而是因为权限过大、配置过宽、边界缺失导致风险扩大。
8. 备份、恢复和灾备
这是很多人最容易忽略、但实际上最接近“真正高可用”的部分。
要补齐这一层,至少要有:
- etcd 备份与恢复演练
- Kubernetes 关键 YAML / Helm values 版本管理
- 镜像仓库可追溯
- 持久化数据备份
- 跨机房或跨区域灾备预案
因为真正的高可用,不是“永远不坏”,而是:
出问题之后,能快速恢复,而且恢复过程是可验证的。
十三、如果让我把这套实验继续补完整,我会怎么扩
如果后面资源允许,我会按下面这个顺序继续往上补:
- 增加独立 worker 节点,把业务负载和 control plane 分开
- 补齐三节点 etcd 的备份、恢复和快照策略
- 部署 Ingress Controller、CoreDNS 双副本和监控体系
- 增加动态存储方案,至少先补一个可用的实验存储类
- 给关键应用补
PDB、反亲和、探针、资源限制和 HPA - 补日志、告警和日常巡检手段
这样整套环境才会从“能搭起来”逐步走向“能长期运行”。
总结
这次实验的价值,不在于把 Kubernetes “装上去”这么简单,而是在有限资源下,把控制平面高可用里最核心的一条链路跑通了:
- 三台控制节点
HAProxy + Keepalived提供统一入口kubeadm负责控制平面初始化Cilium提供网络能力
它已经足够帮助我们理解一件事:Kubernetes 的高可用,不是多加几台机器这么简单,而是要把控制面入口、运行时、网络、数据、监控和恢复能力串成一整套系统。
所以更准确地说,这次做成的是一套实验版高可用 Kubernetes 控制平面;而一套真正完整的高可用 Kubernetes,还应该继续补上 worker、etcd、存储、监控、备份和灾备这些能力。
这也是实验环境和生产环境之间最本质的差别。






