拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

kubeadm部署Kubernetes集群实战:从初始化到节点接入全流程

kubeadm部署Kubernetes集群实战:从初始化到节点接入全流程

kubeadm 是 Kubernetes 官方提供的集群初始化工具,名字里就有画面感:kube-adm,adm 是 administrator 的缩写,意思是让管理员用命令把一个多节点集群从零拉起来。相比二进制方式逐个部署 kube-apiserver、kube-controller-manager、kube-scheduler、etcd,kubeadm 把所有最繁琐的环节都自动化了:控制平面组件的静态 Pod 清单、证书签发、kubeconfig 生成、bootstrap token 分发。这篇文章我以 v1.26.0 作为示例版本,从环境规划开始,带你一步步把一套"1 个控制平面节点 + 2 个工作节点"的集群搭起来,再把实际部署中踩过的坑和排查思路一并说透。适合刚接触 Kubernetes、想在自己机器上复现一套可用集群的读者,也适合已经用其他方式装过集群、想对比 kubeadm 思路的人。

1. 部署前的整体规划:先把选型和版本搞清楚

动手敲命令之前,我强烈建议你先花十分钟把"用什么工具、装什么版本、分几台机器"这三个问题想清楚。很多人部署翻车,不是命令敲错了,而是环境前提没想明白,装到一半才发现版本不匹配或者网络插件选型冲突。

1.1 为什么选 kubeadm:官方出品的“半自动”方案

先把 kubeadm 和其他方式做个对比。二进制部署是最原始的方式,你需要自己下载各个组件的二进制包,自己写 systemd unit 文件,自己生成证书,自己拼 kubeconfig,一套流程走完基本要半天,而且极易出错。kubeadm 相当于官方把这一套流程固化成了一组命令,但它又不是一键傻瓜式——它只负责"拉起控制平面 + 引导节点接入",网络插件、存储、负载均衡这些组件仍然需要你自己装。

这正是 kubeadm 的价值:它是可控性和便捷性的平衡点。你在初始化时能看到每一步的输出([init]、[preflight]、[certs]、[control-plane] 这些分节的日志),知道它到底做了什么,而不是像某些脚本一样一把梭。出了问题也能从日志定位到具体阶段。对于学习 Kubernetes 内部结构、搭建测试环境、甚至生产环境起步,kubeadm 都是最稳的选择。

kind 和 minikube 我也用过,它们更适合单机快速体验,核心是帮你省掉多节点成本,但和真实集群的网络模型、调度逻辑有差异。如果想练手生产级多节点集群,kubeadm 依然是首选。

1.2 版本选型与兼容性关系:不只是版本号好看

这里有个关键认知:Kubernetes 集群不是一个单体软件,而是 kubelet、kubeadm、kubectl、容器运行时、CNI 插件的组合。我这个环境用的版本组合是:

  • Kubernetes v1.26.0
  • containerd 1.6.x(作为 CRI 容器运行时)
  • Flannel 作为 CNI 网络插件
  • Ubuntu 22.04 操作系统

为什么选 1.26.0?因为这个版本是一个相对成熟稳定的版本,同时它正式移除了 dockershim,意味着 containerd 这类 CRI 运行时成为绝对主流。kubeadm 在初始化时输出的第一行日志就是[init] using kubernetes version: v1.26.0,它要求 kubelet、kubeadm、kubectl 三个组件的版本保持严格一致,这是 kubeadm 的一个硬约束。

实际操作中,我建议你用一个公式:先定 Kubernetes 版本,再看 containerd 兼容版本,最后看 CNI 插件的参考文档。kubeadm 自己的依赖很小,真正容易出问题的是 containerd 和 CNI。containerd 至少需要 1.6.x 才能完整支持 1.26 的 CRI 接口,如果用太老的 1.4/1.5,kubelet 会直接报"unable to connect to runtime"。

1.3 节点规划与网络规划:给集群留好“床位”

节点规划我习惯用一张表先列明白,避免中途改架构。

角色主机名IP 地址配置建议操作系统
control-planek8s-master192.168.10.102C4G 起步Ubuntu 22.04
workerk8s-node1192.168.10.112C2GUbuntu 22.04
workerk8s-node2192.168.10.122C2GUbuntu 22.04

控制平面节点建议内存不低于 2GB,因为 etcd 和 apiserver 都比较吃内存,kubeadm preflight 检查也要求可用内存至少 1700MB,不满足会直接报 Error。工作节点 2C2G 跑普通测试应用足够了。

网络规划里最容易埋雷的是Pod CIDR。Kubernetes 的 Pod 网段和 Service 网段是两个独立的虚拟网段,不能和物理机网段重叠。Flannel 默认使用 10.244.0.0/16,所以在 kubeadm init 时要通过--pod-network-cidr=10.244.0.0/16显式声明。如果你用的是 Calico,默认 Pod 网段是 192.168.0.0/16,这个值对不上,网络插件和 kube-proxy 会处于"各说各话"的状态,后面所有 Pod 都无法互相通信。我见过不少人在这里栽跟头,建议你把这张 IP 规划表贴在终端旁边。

2. 环境初始化:把三台机器调教成合格宿主

Kubernetes 对宿主机有基础要求,这些不处理好,后面会以各种奇怪方式报错。这一节的操作要在三台机器上全部执行。

2.1 第一刀:swap、内核模块与 sysctl 参数

首先是关闭 swap。为什么?因为 Kubelet 默认的节点资源管理模型不支持 swap 参与调度,开着 swap 会让 Pod 的内存限额失去意义,kubelet 会反复报running with swap on is not supported。这一步执行完要顺手把 /etc/fstab 里的 swap 行注释掉,否则重启后 swap 又回来了:

swapoff -a sed -i '/ swap / s/^/#/' /etc/fstab

接着加载两个内核模块:overlay 提供容器镜像分层挂载能力,br_netfilter 让 Linux 网桥设备上的流量也能被 iptables 规则过滤,这是实现 Pod 网络隔离和 Service 转发的前提:

cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter sudo sysctl --system

再配一组 sysctl 参数写入 /etc/sysctl.d/k8s.conf:

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 sysctl --system

net.ipv4.ip_forward = 1尤其重要,它决定节点是否愿意转发数据包。Kubernetes 的 Service 流量、Pod 之间的跨节点流量都要经过三层转发,这个值不开,kube-proxy 的转发规则再正确也传不过去。

2.2 容器运行时 containerd:装对不难,配好才见功力

v1.26 已经移除了 dockershim,在系统里装好 containerd 并且让它能以 CRI 方式被 kubelet 调用,是必须的一步。Ubuntu 22.04 的 apt 源里自带 containerd,直接装即可:

sudo apt-get update sudo apt-get install -y containerd

装完不要急着用,先生成一份默认配置。containerd 的默认配置生成命令是containerd config default,生成的配置里有两个关键坑:

第一个坑是SystemdCgroup。默认配置文件里SystemdCgroup = false,意味着运行时使用 cgroupfs 作为 cgroup 驱动。而 kubeadm 默认让 kubelet 使用 systemd 作为 cgroup 驱动,两者不一致时,kubelet 启动后 Pod 创建会失败,日志里反复出现 cgroup 相关的报错。必须把这一项改成 true:

sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/g' /etc/containerd/config.toml sudo systemctl restart containerd

为什么统一用 systemd?在 cgroup v2 的现代 Linux 发行版下,systemd 作为 cgroup 驱动是官方推荐的方案,和 systemd 的资源管理模型天然一致。这个坑如果你不主动改,遇到报错再去翻日志,往往要折腾好几个小时。

第二个坑是sandbox_image。配置文件里的sandbox_image默认值是registry.k8s.io/pause:3.9(1.26 系列实际拉取版本以kubeadm config images list的输出为准)。如果你的网络环境拉取 registry.k8s.io 速度不理想(国内云服务器很常见),最好提前把它替换成你云厂商提供镜像加速地址里对应的 pause 镜像。这个操作要趁早做,否则kubeadm init执行到一半会因为拉不到镜像超时退出。

2.3 安装 kubeadm、kubelet、kubectl:版本粒度要锁死

三个二进制包都要从 Kubernetes 官方仓库装,并且指定精确到 patch 的版本。我用的是 pkgs.k8s.io 的 apt 源:

sudo apt-get update && sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.26/deb/Release.key | sudo 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.26/deb/ /' | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update sudo apt-get install -y kubelet=1.26.0-00 kubeadm=1.26.0-00 kubectl=1.26.0-00 sudo apt-mark hold kubelet kubeadm kubectl

最后一步apt-mark hold是我强烈建议加的。apt 的自动升级策略会不定期把 kubelet 升级到新版本,导致集群里节点版本不一致,轻则 kubelet 和 apiserver 之间出现 REST 兼容问题,重则整个节点登出集群。锁死版本意味着"只有你明确操作时才升级",这是生产环境的基本素养。

安装完成后可以验证一下版本:

kubeadm version kubelet --version kubectl version --client

三条命令输出的版本应该都是 v1.26.0。如果哪个不对,现在回头修还来得及,别等到集群初始化再碰运气。

3. 控制平面初始化与节点接入全流程

基础环境就绪后,真正的高潮来了。我会在控制平面节点上操作,工作节点先待命。

3.1 kubeadm init 参数解析:每一行都有讲究

在控制平面节点上执行。先看一眼即将拉取的镜像清单,确认网络能通:

kubeadm config images list --kubernetes-version v1.26.0

这个命令会把 init 阶段需要拉取的镜像全部列出来,包括 kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd、pause、coredns。建议先执行kubeadm config images pull --kubernetes-version v1.26.0把镜像提前拉好,避免 init 过程中因为某个镜像超时而中断。

然后正式初始化:

sudo kubeadm init \ --apiserver-advertise-address=192.168.10.10 \ --pod-network-cidr=10.244.0.0/16 \ --kubernetes-version=v1.26.0

三个参数分别解释一下。--apiserver-advertise-address指定 apiserver 对外通告的地址,这里就是控制平面节点的内网 IP。如果不写,kubeadm 会取默认路由出去的 IP,在多网卡机器上经常取错,所以我习惯显式指定。--pod-network-cidr必须和后面 CNI 插件默认网段保持一致,我选 Flannel,所以用 10.244.0.0/16。--kubernetes-version锁死版本,避免 kubeadm 去联网探测最新版。

执行后你会看到类似这样的日志(这就是网上经常见到的[init] using kubernetes version: v1.26.0和[preflight] running pre-flight checks):

[init] Using Kubernetes version: v1.26.0 [preflight] Running pre-flight checks [WARNING Service-Kubelet]: kubelet service is not enabled... [preflight] Pulling images required for setting up a Kubernetes cluster [preflight] This might take a minute or two... [certs] Generating certificates and keys... [control-plane] Creating static Pod manifest for "kube-apiserver" [control-plane] Creating static Pod manifest for "kube-controller-manager" [control-plane] Creating static Pod manifest for "kube-scheduler" [etcd] Creating static Pod manifest for local etcd [init] Waiting for the kubelet to boot up... [kubelet] Writing kubelet configuration [kubelet] Starting the kubelet [kubelet-check] Initial timeout of 40s passed... [addons] Applied essential addon: CoreDNS [addons] Applied essential addon: kube-proxy Your Kubernetes control-plane has been initialized successfully!

3.2 控制面组件落地检查:从 preflight 到 etcd

看到 "control-plane has been initialized successfully" 并不代表万事大吉,它只说明 kubeadm 把静态 Pod 清单写进了 /etc/kubernetes/manifests 目录。真正拉起这些组件的,是节点的 kubelet。静态 Pod 是 kubelet 直接监听的目录,apiserver、etcd、controller-manager、scheduler 都以这种方式运行,所以这些组件不归 kubelet 以外的任何进程管理。

等 30 秒到一分钟,然后检查核心容器状态:

sudo crictl ps -a kubectl get pods -A

正常情况下 kube-system 命名空间里应该看到 apiserver、etcd、controller-manager、scheduler 都是 Running。如果哪个容器反复重启,基本都在 preflight 或者 cgroup 驱动上出了岔子,按第 4 节的思路去查。

顺带说一下[preflight]都查什么:交换分区是否关闭、端口 6443/10250 是否被占用、内核版本是否过老、内存是否足够、主机名是否规范、cgroup 驱动是否一致。这些检查项全是 Kubernetes 运行的硬前提,任何一个错误都会直接中断初始化。看到[WARNING]可以继续,看到[Error]就必须停下。

3.3 kubectl 配置与 Flannel 网络插件安装

init 成功后会输出一个 kubeconfig 配置指令,官方建议用普通用户操作 kubectl:

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 里的内容本质上是 apiserver 地址、客户端 CA 证书、访问权限的集合。没有这个文件,kubectl 根本不知道往哪里发请求。配置完以后kubectl get nodes应该能看到控制面节点处于 NotReady,这很正常,因为网络插件还没装,节点无法确认 Pod IP 路由能正常工作。

安装 Flannel 网络插件:

kubectl apply -f https://github.com/flannel-io/flannel/releases/latest/download/kube-flannel.yml

Flannel 的工作方式是给每个节点分配一个子网,然后通过 VXLAN 隧道在节点之间转发 Pod 流量,它默认使用的 Pod 网段就是 10.244.0.0/16,和 init 时声明的网段一致。装完等待 Flannel Pod 全部 Running,再执行:

kubectl get nodes -o wide

控制平面节点应该变为 Ready。如果一直 NotReady,优先去看 kube-system 里的 Flannel Pod 日志,第 4 节会详细展开。

3.4 工作节点 join:token 与证书指纹一起用

控制平面现身后,工作节点登场。kubeadm init 成功输出的末尾,已经给了一条 join 命令,形如:

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

这条命令有两个核心信息:token 是节点的准入凭证,有效期为 24 小时;--discovery-token-ca-cert-hash是控制面 CA 证书的 SHA256 指纹,用于防止节点连到仿冒的 apiserver。两者缺一不可。

把这条命令在两台工作节点上依次执行。如果你已经错过 init 输出的命令窗口期,不用慌,在控制面上随时可以重新生成一条:

kubeadm token create --print-join-command

join 成功后,在两个节点各自执行:

kubectl get nodes -o wide

看到 node1、node2 都是 Ready,这套"1 主 2 从"的集群就算立起来了。Ready 状态的背后是 kubelet、CNI、kube-proxy 全部就绪的联合确认。

3.5 集群可用性验证:跑一个小应用确认整条链路

节点全 Ready 后,我习惯用一个最小应用验证完整链路的可用性,用 nginx 是最直接的:

kubectl create deployment nginx-demo --image=nginx:1.25 --replicas=2 kubectl expose deployment nginx-demo --port=80 --target-port=80 --type=NodePort kubectl get svc nginx-demo

Nginx 能被调度到两个不同节点,说明调度器工作正常;两个副本能互相通信,说明 Flannel 的隧道转发正常;Service 分配出 NodePort 端口并能在任意节点上用curl http://192.168.10.11:<nodeport>访问到,说明 kube-proxy 的 iptables 规则生效了。这一条链路跑通,集群最核心的能力就算验证完毕。

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

这部分是我最想分享的内容。kubeadm 部署本身不复杂,但真遇到问题,网上答案碎片化很严重。我把实际部署中最高频的几类问题整理成了排查清单。

4.1 节点 NotReady 与 kubelet CrashLoopBackOff

节点一直 NotReady,第一反应不要去看 kubectl 的状态,kubectl 在大部分情况下帮不上忙,它只能告诉你结果不对,原因还要去节点上挖。定位思路分三步:

第一步,看 kubelet 是否存活。kubelet 是节点的核心代理,它挂了节点必掉线:

systemctl status kubelet journalctl -u kubelet -f --no-pager

第二步,如果 kubelet 处于 active 但反复退出,最常见的原因是 cgroup 驱动不一致。日志会明确写 kubelet 的 cgroup driver 和运行时的不匹配。排查命令:

cat /etc/containerd/config.toml | grep SystemdCgroup

看输出是不是 true,不是就改回来重启 containerd。

第三步,检查 CNI 插件的二进制和配置目录。Flannel 或 Calico 的 Pod 会调用宿主机上的 /opt/cni/bin 下的插件,如果这个目录为空或权限不对,Pod 的 sandbox 建不出来,节点也会 NotReady。这个问题在手动安装二进制时特别容易踩,我用 kubeadm 部署因为都会通过 manifest 拉起,基本不会碰这个,但如果你改过 CNI 插件路径就要注意。

4.2 Pod 卡在 Pending / ContainerCreating 的归因

卡在 Pending,先查是否有节点能调度,再查是否有污点。控制平面节点默认有污点node-role.kubernetes.io/control-plane:NoSchedule,普通 Pod 不会被调度上去,属正常现象。你部署的东西如果在工作节点上也 Pending,先看:

kubectl describe pod <pod-name>

Events 字段会告诉你调度失败的具体原因,比如资源不足、节点亲和不满足、或者持久化卷没准备好。Pending 的常见原因是内存不足,我之前有台 1C2G 的工作节点,跑三个 Pod 就触发资源不足。

卡在 ContainerCreating,重点查两件事:镜像拉不下来,或者挂载卷失败。镜像问题用kubectl describe pod和kubectl logs看,日志里有 ImagePullBackOff 或 ErrImagePull 就直接定位到镜像地址错误或镜像仓库不可达。挂载问题看宿主机 /var/log 下的 kubelet 日志。

4.3 token 过期与证书更新的处理

join 命令里的 token 默认 24 小时过期,这是很多人隔天再用旧命令 join 失败的直接原因。处理非常简单,控制面上重新生成即可:

kubeadm token create --print-join-command

证书的问题更隐蔽。kubeadm 签发的 CA 证书有效期默认是 10 年,但 apiserver、kubelet 之间的服务证书只有 1 年。一年后你会突然发现 kubectl 连不上 apiserver 了,报证书过期或未知颁发机构。检查命令:

kubeadm certs check-expiration

过期后用以下命令续期,并重启掉相关的静态 Pod 让新证书生效:

sudo kubeadm certs renew all

续期后还要记得更新 kubeconfig 里的客户端证书:

sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config

4.4 重建环境:kubeadm reset 的正确姿势

这个坑我提一句能帮不少人省时间。集群实验坏了,或者想重新初始化,很多人直接重装系统。其实不用,kubeadm 提供了标准拆卸流程:

sudo kubeadm reset -f sudo rm -rf /etc/cni/net.d $HOME/.kube

之后重新走 init、join 流程即可。注意kubeadm reset会清掉 kubelet 配置、etcd 本地数据、iptables 规则,但 CNI 的残留配置有时不会删干净,所以手动补一刀rm -rf /etc/cni/net.d,否则残留的网段配置可能和新集群冲突。

现象最快定位命令常见根因
节点 NotReadysystemctl status kubeletcgroup 驱动不一致、swap 未关
Pod Pendingkubectl describe pod资源不足、污点限制
Pod ContainerCreatingkubectl describe pod + 宿主机日志镜像拉取失败、CNI 未就绪
join 报 token 错误kubeadm token create --print-join-commandtoken 过期
kubectl 连不上 apiserverkubeadm certs check-expiration证书过期

5. 一些我自己的实操体会

这套 kubeadm 环境我前前后后搭过很多次,给你三条实在建议。

第一条,每个阶段都做最小验证。不要一口气把所有命令敲完再检查,尤其是 init 之后,先等 30 秒看 apiserver 和 etcd 是否 Running,再往下走。所有问题早暴露都比晚暴露好处理,这一条在集群运维里永远成立。

第二条,版本和网段的一致性记在本子上。kubeadm 对版本 lock 的要求很高,对 pod-network-cidr 和 CNI 网段的匹配要求更高。我见过太多人因为 Flannel 装成 Calico 的网段导致整个集群 Pod 网络瘫痪。写计划时就把这些值固定下来,不要边装边想。

第三条,保留一份干净的安装命令序列。以后你要在客户环境、测试环境快速复制集群,无脑按顺序执行就行。我自己的做法是把所有命令和参数维护在一个 Markdown 文档里,每次部署直接复制,比临时翻文档效率高一倍。

kubeadm 部署这套流程,踩过几次坑之后你会慢慢意识到,它其实就两条主线:一条是把控制平面组件的运行方式标准化了,一条是把节点接入的安全机制标准化了。搞懂这两条主线,再看 kubeadm 的每一步输出就不会觉得玄学,排查问题时也能顺着日志快速摸到根因。

返回列表