ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

kubeadm搭建Kubernetes一主两从集群:从初始化到排错全记录

kubeadm搭建Kubernetes一主两从集群:从初始化到排错全记录

折腾过 Kubernetes 集群安装的人应该都清楚,手动用二进制把 kube-apiserver、etcd、controller-manager 一个个弄起来有多痛苦。幸好有 kubeadm 这种官方引导工具,它能帮你把证书生成、控制面组件启动、节点间信任关系这些重复劳动自动化,让 Kubernetes 集群安装从地狱级手工活变成 init 和 join 两条主线。这篇文章就是我配合 kubeadm 从零搭建一套一主两从集群的完整记录,覆盖环境初始化、容器运行时配置、控制面初始化、工作节点加入以及排错复盘,适合刚入门容器编排、想亲手搭一套 K8s 环境的新手,也适合被各种残缺教程坑过、想系统梳理一遍的运维朋友。

1. 为什么用 kubeadm:方案对比与背后原理

1.1 三条自建路线,为什么我选 kubeadm

自建 Kubernetes 集群,主流无非三条路:纯手工二进制部署、kubeadm 部署、以及各类封装好的发行版工具。二进制部署最贴近原理,每个组件都要手动下载、生成证书、写 systemd 服务,一套流程走下来能让你把 K8s 内部结构吃透,但对团队和个人的时间成本实在太高,出一次错排查半天,新手很容易在半路放弃。kubeadm 是官方为“引导集群”设计的工具,它自动完成证书签发、kubeconfig 下发、etcd 初始化、token 签发这些一次性工作。另一类封装工具(比如 minikube、kind)更适合本机开发测试,它们一般是在 Docker 里起单节点环境,跟真实服务端集群的差距比较大,不适合作为服务器部署的主方案。

所以我的建议很直接:想学原理,看二进制部署的架构文档就够了,别真去手搓;想在几台服务器上搭一套能长期使用、能按官方流程升级的集群,优先用 kubeadm。

1.2 kubeadm 到底做了什么:静态 Pod 是核心机制

很多人第一次跑完 kubeadm init 会疑惑:控制面组件到底装到哪去了?答案是 /etc/kubernetes/manifests 这个目录。init 之后你会看到 kube-apiserver.yaml、kube-controller-manager.yaml、kube-scheduler.yaml、etcd.yaml 等文件,这是 kubelet 认可的“静态 Pod”清单目录。kubelet 会定时扫描这个目录,把其中的 Pod 定义拉起来,组件挂了还能自动重启。

这种设计的好处非常实际:控制面组件本身也被 K8s 托管,升级时只要替换 manifests 里的镜像版本,组件就完成滚动更替。理解了这条主线,排查问题会顺很多——比如 apiserver 起不来,先去 manifests 目录看 yaml 内容,再去看对应静态 Pod 的日志,而不是盲目重装集群。kubeadm 在 init 阶段做的是“引导”而不是“常驻”,引导结束之后,日常运维还是 kubelet 和 API Server 说了算。

2. 环境准备:硬件规划与基础系统配置

2.1 节点规划与硬件建议

本次演示用三台机器:一台控制面节点,两台工作节点。IP 地址我用的是私有网段示例,你实际部署时换成自己的段就行。

主机名IP角色建议规格
k8s-master01192.168.10.10控制平面2C4G
k8s-node01192.168.10.11工作节点2C4G
k8s-node02192.168.10.12工作节点2C8G

学习环境 2C4G 起步,其中控制面至少要 2 个核,因为 etcd 和 kube-apiserver 都是 CPU 敏感型组件,核数不够会频繁超时。工作节点如果跑真实应用,规格按业务定,内存建议 8G 起,磁盘至少 40G,系统盘和数据盘尽量分开,防止容器镜像把根分区撑爆。

2.2 每台机器都要做的四件套基础配置

不管控制面还是工作节点,下面四件事缺一不可:

第一,设置主机名和 hosts 解析。控制面执行hostnamectl set-hostname k8s-master01,worker 同理。然后在所有机器的 /etc/hosts 里都写上三行解析记录,哪怕集群内部有 DNS 也要写,这是避免 kubelet 因为解析不到 master 而反复报错最简单的办法。

第二,关闭 swap。执行swapoff -a,再编辑 /etc/fstab 把 swap 行注释掉,保证重启后不恢复。原因在于 kubelet 在资源管理上是“严格模式”,一旦节点上报的内存里算上 swap,Pod 的 request 和 limit 就会失真,调度器也会按错误的内存数据规划。K8s 从早期版本开始就要求关闭 swap,开着直接会报错启动失败。

第三,加载内核模块并调整网络参数。执行:

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

overlay 是容器镜像分层存储需要的文件系统驱动,br_netfilter 负责让经过网桥的流量也能被 iptables 处理,K8s 的 Service 转发规则依赖它。ip_forward 是 Pod 与 Pod、Pod 与外部网络通信的前提。这三个参数漏掉任何一个,后面都会出现“网络通一半”的诡异现象。

第四,时间同步。apt install -y chrony && systemctl enable --now chrony。Kubernetes 集群对时间一致性非常敏感,证书校验和 token 过期都依赖时间,节点间时间差太大会直接导致 kubelet 与 apiserver 的 TLS 握手失败。这类报错不仔细看根本想不到是时间问题。

2.3 端口放行清单:别让防火墙把集群拦在门外

安装前先把端口规划好。实验环境我建议直接关闭防火墙省心,生产环境按表放行:

端口服务说明
6443kube-apiserver所有客户端和节点访问 API 的入口
2379-2380etcd server client控制面内部通信
10250kubelet管理面和各节点健康检查
10257kube-controller-manager控制面组件
10259kube-scheduler控制面组件
30000-32767NodePort 服务端口对外暴露应用用
8472flannel VXLAN节点间 Pod 网络隧道(UDP)

如果用 Calico,还需要放行 179 端口的 BGP。防火墙规则务必在所有节点保持一致,不然会出现“从一个节点访问服务没问题,从另一个节点访问就超时”的经典网络事故。

3. 容器运行时与 kubeadm 全家桶安装

3.1 运行时选 containerd 而不是 Docker

K8s 从 1.24 版本开始移除了 dockershim,Docker 不再是默认容器运行时。虽然可以单独装 cri-dockerd 做适配,但主流方向是直接用 containerd,这也是当前云原生生态的事实标准。本篇直接使用 containerd。

安装很简单:apt install -y containerd。装完必须做两步配置:生成默认配置并修改,命令是:

containerd config default | tee /etc/containerd/config.toml

配置文件里需要重点改两个地方。第一个是SystemdCgroup = true。原因是 kubelet 默认的 cgroup 驱动是 systemd,containerd 默认是 cgroupfs,两边不一致会导致 kubelet 无法正确管理和统计 Pod 资源,典型症状就是节点反复 NotReady。统一成 systemd 是官方推荐做法。

第二个是 sandbox_image 字段。pause 镜像是每个 Pod 都有的“占位容器”,负责为 Pod 提供网络和命名空间基础。默认值指向的镜像仓库很多环境访问不稳定,拉不到它,所有 Pod 都会无限期卡在 ContainerCreating。把这个字段改成你能稳定访问的 pause 镜像地址,是新手最容易漏的一步。

改完执行systemctl restart containerd。注意 containerd 和 Docker 如果同时存在,要确认 kubelet 配置指向的是 containerd 的 socket,否则两个运行时端口冲突会直接拉垮节点。

3.2 安装 kubelet、kubeadm、kubectl 并锁定版本

三个二进制的作用分别说一下:kubelet 是每个节点上的核心代理,负责管理本节点的 Pod;kubeadm 是集群引导工具;kubectl 是给管理员用的命令行客户端。三件套必须在所有节点都装,版本必须一致。执行:

curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/deb/Release.key | gpg --dearmor -o /etc/apt/keyrings/kubernetes-apt-keyring.gpg echo 'deb [signed-by=/etc/apt/keyrings/kubernetes-apt-keyring.gpg] https://pkgs.k8s.io/core:/stable:/v1.28/deb/ /' | tee /etc/apt/sources.list.d/kubernetes.list apt-get update apt-cache madison kubeadm

查到具体版本号之后,指定版本安装并锁定:

apt-get install -y kubelet=1.28.2-1.1 kubeadm=1.28.2-1.1 kubectl=1.28.2-1.1 apt-mark hold kubelet kubeadm kubectl

为什么要锁定版本?因为 Kubernetes 有个硬性约定:集群内各组件的版本差异不能超过一个 y 版本。apt 的 update 一旦把某台机器的组件升到新版本,其他节点跟不上,整个控制面就会进入半瘫痪状态。升级要走 kubeadm upgrade 的专项流程,不能交给系统包管理器乱来。这个动作不写进很多教程里,但实际踩坑概率非常高。

3.3 初始化前先预拉镜像

正式 init 之前,强烈建议先跑两条命令:

kubeadm config images list kubeadm config images pull

第一条列出控制面需要的全部镜像,第二条实际拉取。如果默认镜像仓库拉不动,可以在 init 时用--image-repository参数指定一个你可访问的镜像仓库前缀,例如--image-repository=某公共镜像仓库/google_containers。预拉镜像的意义在于:真正 init 时不会因为网络问题卡在拉镜像阶段,问题能提前暴露。实测下来,这一步能帮你把整个安装时间缩短一大半。

4. 控制平面初始化:kubeadm init 与 CNI 网络

4.1 参数逐个讲清楚再执行

在 master 节点上执行初始化,命令如下:

kubeadm init \ --apiserver-advertise-address=192.168.10.10 \ --control-plane-endpoint=192.168.10.10 \ --pod-network-cidr=10.244.0.0/16 \ --kubernetes-version=v1.28.2

四个参数的含义必须清楚。--apiserver-advertise-address是 API Server 对外绑定的地址,写 master 的内网 IP 就行。--control-plane-endpoint是所有节点(包括 worker)访问管理面的地址,也是 kubeconfig 里 server 字段的地址,高可用场景这里要填负载均衡的虚拟 IP。--pod-network-cidr是 Pod 网段,必须和你选的 CNI 插件默认网段对齐——Flannel 默认 10.244.0.0/16,Calico 默认 192.168.0.0/16,选错会导致网络插件完全失效。--kubernetes-version写实际部署的版本,避免默认取值和已安装的 kubeadm 不一致。

init 跑完,终端会输出三块关键信息:kubectl 的 kubeconfig 配置命令、worker 加入集群的 join 命令、token 过期时间。这些输出别关太快,join 命令后面要用。如果丢了,在 master 上用kubeadm token create --print-join-command重新生成。

4.2 配置 kubectl 并验证节点状态

init 完成之后,需要把管理员的 kubeconfig 配置到当前用户下。非 root 用户执行:

mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config

admin.conf 是 init 阶段生成的集群管理员凭证,包含 apiserver 地址和 admin 证书。root 用户直接export KUBECONFIG=/etc/kubernetes/admin.conf也行。配好之后跑kubectl get nodes,能看到 master 处于 NotReady 状态,这是正常的,因为此时还没有网络插件,节点的 Pod 网络还没建立。

4.3 部署 Flannel 网络插件

网络插件这一步最关键,执行:

kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml

Flannel 通过 VXLAN 或 host-gw 模式把各节点的 Pod 网络打通。CNI 插件必须在任何业务负载运行之前装好,因为 Kubernetes 本身不实现 Pod 网络,没有 CNI 的节点永远无法进入 Ready 状态。装完之后用kubectl get pods -A -o wide观察,等 flannel 的 Pod 都进入 Running,再kubectl get nodes就会看到 master 变成 Ready。

关于选型多说一句:我演示用 Flannel 是因为它配置最简单,适合学习和中小规模集群。如果生产环境有多租户隔离、网络策略、高性能转发需求,建议上 Calico 或 Cilium,它们功能更完整,但也更吃资源。

5. 工作节点加入与首个应用验证

5.1 用 kubeadm join 接入 worker

init 输出里给的 join 命令长这样:

kubeadm join 192.168.10.10:6443 --token <token> \ --discovery-token-ca-cert-hash sha256:<hash>

在每台 worker 节点上分别执行。token 是节点加入集群的临时通行证,默认有效期 24 小时。过期之后在 master 上跑kubeadm token create --print-join-command,它会直接输出一条完整的 join 命令,复制到 worker 执行即可。加完节点回 master 上验证:

kubectl get nodes

几台机器全部变为 Ready,集群的基本骨架就算搭完了。从 init 到三节点 Ready,正常情况下十分钟之内能完成。

5.2 部署一个 Nginx 验证功能闭环

集群搭完别急着收工,跑一个应用验证链路是必要的。执行:

kubectl create deployment nginx --image=nginx kubectl expose deployment nginx --port=80 --type=NodePort kubectl get svc nginx

NodePort 类型的 Service 会在所有节点上开放一个 30000-32767 范围内的随机端口。用浏览器访问任意节点 IP 加这个端口,能打开 nginx 首页,说明 Pod 调度、kube-proxy 转发、节点间网络隧道的全链路都是通的。这一步排查的是“集群能建起来不代表能跑业务”的最后一环,很多人建完集群卡在业务不通才开始怀疑网络,不如一开始就做验证。

6. 常见问题与排查技巧实录

6.1 一切问题先看 kubelet 日志

kubelet 是节点上最核心也最爱出问题的组件。遇到节点异常,第一件事永远是:

journalctl -u kubelet -f -n 50

不要靠猜。kubelet 的报错一般很明确,比如 swap 未关闭、cgroup 驱动不匹配、证书路径不正确,日志里都会直接写明原因。我见过太多人绕开日志去重装集群,最后发现只是某台机器配置文件写错一个字母。

6.2 节点 NotReady 的三种典型原因

  • CNI 网络插件未安装:查看kubectl describe node <节点名>,事件里会显示 KubeletNotReady,并且提示没有找到网络插件。解决办法就是补装 Flannel 或对应 CNI 并等待 Pod 就绪。
  • cgroup 驱动不一致:典型报错是 kubelet 的 cgroupfs 和 containerd 的 systemd 不一致。去 containerd 配置里把SystemdCgroup改成 true 并重启,或者给 kubelet 加--cgroup-driver=cgroupfs参数,但前一种才是推荐方向。
  • pause 镜像拉不下来:Pod 全部卡在 ContainerCreating,kubectl describe pod里会指明是 sandbox 镜像拉取失败。改 containerd 的 sandbox_image 配置即可。

6.3 证书和 token 相关坑位

token 过期是最常遇到的,症状是 join 时提示 token 无效。重新用kubeadm token create --print-join-command生成就行,新版 kubeadm 还会提示实际的有效期。

证书过期则是另一个大坑,kubeadm certs check-expiration可以一键查看所有证书的过期时间。控制面证书默认有效期是一年,快到期时执行kubeadm certs renew all再用新的 kubeconfig 替代旧的。另外 join 命令里的--discovery-token-ca-cert-hash是控制面 CA 证书的指纹,填错了会直接报 hash 校验失败,务必用 init 输出或kubeadm token create --print-join-command里给的原值。

6.4 端口和网络转发问题快查表

症状可能原因解决办法
节点间通信超时防火墙未放行 8472/4790 或 179 端口检查所有节点防火墙规则并保持统一
NodePort 从外部访问不通安全组或本地防火墙拦截 30000-32767放行端口段
Pod 间 ping 不通Flannel 隧道接口未建立或 ip_forward 未开启确认 sysctl 配置和 flannel.1 接口存在
Service 访问不通kube-proxy 异常或 iptables 规则被覆盖kubectl get pods -n kube-system检查 kube-proxy 状态

6.5 我踩过最隐蔽的一个坑

这里单独说一下时间同步。有次我把集群搭好,两个 worker 都 Ready,但过了一天后 kubelet 开始频繁报证书校验失败,排查半天才发现是非控制面节点的时钟漂移了将近三分钟。Kubernetes 的证书和 token 机制对时间偏差零容忍,早早在 fstab、swap、sysctl 里都写了,结果忘了时间同步这步。现在只要有新机器要加进集群,我第一件事就是先把 chrony 装好,确认时间源正常,再谈其他。

7. 集群搭完之后,我会接着做这几件事

这套一主两从的集群跑通之后,常用扩展方向有这么几个。想把它变成一台学习机:把控制面节点的污点去掉,就允许普通业务 Pod 调度到 master 上,命令是kubectl taint nodes --all node-role.kubernetes.io/control-plane-,单机集群也能跑完整功能。想升级成高可用:把控制面扩容到三台,前面挂负载均衡,control-plane-endpoint 指向虚拟 IP,这是官方推荐的多管理面方案。不管哪种玩法,都建议从第一天开始给 etcd 做定期快照备份,哪天误删 namespace 或者升级翻车,etcd 快照就是最后的救命稻草。

一路踩坑搭下来,我的体会是:kubeadm 把安装 K8s 的门槛压得很低了,真正考验人的反而是基础环境——内核参数、时间同步、cgroup 驱动、镜像拉取这些“看起来不重要的细节”。把这四样做扎实,集群基本都是十分钟内正常起来;做不扎实,你会在 NotReady 和三联排错里浪费整整一个晚上。希望这篇记录能帮你少走几段我走过的弯路。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进