资讯详情

kubeadm高效部署Kubernetes集群:从环境准备到故障排查实践

📅 2026/10/14 15:53:33 | 华诺云谱 👁 阅读
kubeadm高效部署Kubernetes集群:从环境准备到故障排查实践
说到 kubeadm 部署 k8s我也是从被信息轰炸的阶段过来的。当时网上教程一抓一大把有人说先装 Docker有人说直接用 containerd有人让你关 swap有人让你开转发参数一篇跟一篇说法还不一致。后来我在几个真实项目里反复用 kubeadm 搭测试集群、搭生产环境的初始骨架才算是把这条路线背后的逻辑彻底理顺。这篇文章不是把官方文档翻译一遍而是把我在实际部署过程中真正踩过的坑、确认过的配置、以及遇到故障时的排查思路完整梳理出来。适合正在准备用 kubeadm 自建集群的同学也适合那种“照教程搭完觉得心虚、不知道哪一步为什么这么做”的人。我会尽量做到不但告诉你点哪里还告诉你为什么点这里。1. 为什么我最终选择了kubeadm这条部署路线1.1 几种常见部署方式的基本对比我在决定用 kubeadm 之前还真比较过几条主流路线。第一种是二进制手工部署。所有组件都是自己下载二进制、自己写 systemd 服务、自己签发证书、自己拼 kubeconfig。这套东西学一遍确实能把 K8s 的骨架看得很透但过程非常痛苦尤其对第一次接触集群的人来说apiserver、controller-manager、scheduler、etcd、kubelet 这一堆角色之间的调用关系和证书依赖很容易把人劝退。纯手工搭一套单 master 集群顺利的话也要大半天。第二种是 minikube、kind 这类本地工具。它们适合开发调试但离真实集群环境太远网络模型、多节点交互、证书生命周期这些东西都体验不到。你在这类工具上跑再多的应用换到真正的多节点集群时还是会有一种“从假环境掉进真环境”的落差。第三种是云厂商的托管集群。我自己的观点是如果公司业务主要跑在特定云平台上买托管集群没什么问题省心省事。但托管集群的底层对你是个黑盒你没法自由调整控制平面参数也没法体验节点加入、证书续期、控制平面组件出问题时的排错思路。如果团队以后可能要跨环境交付或者你需要彻底弄懂集群的运行机制那就不能只靠托管集群。最后就是 kubeadm。它是 Kubernetes 官方提供的集群引导工具把最烦琐的证书生成、控制平面组件启动、节点注册这几步做成了标准流程。它不封装成一个完全的黑盒而是把每一步产生的配置文件和证书都放在磁盘上出了问题可以 用 kubectl、journalctl 一层层扒。它适合你在任何环境裸机、虚拟机、内网虚拟化平台里搭建一套可维护的集群这套集群既能用于测试也能作为生产集群的起点。1.2 kubeadm的精确定位它是引导工具不是神我很早之前有过一个误解以为 kubeadm 是“集群的一键运维工具”用了它之后集群升级、组件故障自愈、证书管理全都包办了。实际用过之后才发现kubeadm 的定位非常克制官方文档里也很诚实地说它只管集群的“bootstrap”也就是从零到可用这一步。集群建起来之后kubeadm 不会天天盯着你的 apiserver 是否健康不会替你做节点故障转移更不会自动扩容。它做的事情本质上是三件第一检测当前机器是否满足集群创建条件第二把控制平面的组件以静态 Pod 的方式启动起来生成相关证书和 kubeconfig第三给工作节点提供一条可以安全加入集群的通道。这一点理解之后后面很多困惑就迎刃而解了。比如你可能会问为什么用 kubeadm 装完的集群control plane 组件看起来像“容器”但它们又不在常规的容器运行时管理列表里因为它们是静态 Pod由 kubelet 直接监管而不是通过 Deployment 这类工作负载对象托管的。理解了 kubeadm 的定位你对集群“建起来之后”的事才有正确的预期。2. 环境准备阶段的那些坑必须在动手前说清楚2.1 节点规划别上来就一把梭我建议最低拿两台虚拟机或者实体机来练一台作为控制平面节点一台作为工作节点。如果你只有一台机器光是验证 join 流程就会很遗憾地跳过而恰恰节点加入机制是 kubeadm 部署里最容易出问题、也最能体现价值的一部分。硬件配置方面控制平面节点建议 2 核 CPU、4G 内存起步。如果开交换分区想省内存我建议不要这么做Kubernetes 默认在节点启用 Swap 的情况下会严重影响调度和资源管理kubelet 在默认配置下甚至无法正常启动。K8s 对 swap 的支持一直是比较谨慎的官方策略是“部署进去的节点先关 swap”所以在规划阶段就把这条定下来。操作系统我经常用 Ubuntu Server 22.04 和 CentOS Stream 9 这两类系统验证。它们在基础依赖上略有差异但整体思路一致。准备节点时有几件事我会一次性做完第一主机名绝对不能重而且不要包含下划线或特殊字符。这直接影响 K8s 对节点的内部标识。第二所有节点的时间必须同步。k8s 的证书校验、token 有效期全都依赖系统时间。某次我搭环境时 master 和 worker 之间的时间差了两三分钟结果 join 时一直提示证书校验失败折腾了半小时才意识到是时间不同步。第三规划一个稳定的容器网段给后面的 Pod 网络插件留好位置。后面章节我会详细讲这个网段是怎么决定 Pod 地址的。2.2 内核模块、sysctl 与容器运行时配置集群节点上通常需要加载两个必要内核模块overlay 和 br_netfilter。overlay 是容器镜像分层存储依赖的文件系统驱动类型之一br_netfilter 让经过 Linux 网桥的流量也能被 iptables 规则处理这是实现 K8s 网络策略和服务转发的基础。不少教程会带过这一步但如果你跳过了后面大概率会遇到 Pod 间网络不通或 kube-proxy 转发异常。sysctl 方面需要开启四个关键项“net.bridge.bridge-nf-call-iptables”、“net.bridge.bridge-nf-call-ip6tables”、“net.ipv4.ip_forward”和“net.ipv4.conf.all.rp_filter”。其中 ip_forward 尤其关键因为 Pod 与外部通信时如果宿主机不开转发流量会在内核层面被直接丢弃。容器运行时这一块我现在的标准选择是 containerd。新版 Kubernetes 本身已不再内置 Docker 支持而是要经过 cri-dockerd 这种适配层才能用 Docker。与其多套一层不如直接用原生适配 Kubernetes CRI 的 containerd。安装 containerd 之后有一个非常隐蔽但极其重要的配置cgroup 驱动必须和 kubelet 保持一致。现在的 Linux 发行版普遍使用 systemd 作为 init 系统systemd 接管了 cgroup 的管理。为了让 k8s 的 kubelet 和容器运行时在资源管理上使用同一套接口社区推荐两者都使用 systemd 作为 cgroup driver。containerd 默认配置文件里SystemdCgroup 这一项往往是 false部署之前不改过来后面 kubelet、容器运行时的资源统计会打架典型表现就是节点 NotReady或者 Pod 一直 CrashLoopBackOff。这个坑我在后面故障排查部分会展开讲。2.3 一套可以直接抄的初始化脚本下面这段是我自己常用的节点预配置片段以 Ubuntu 22.04 为例版本要求是 containerd 1.7.x、Kubernetes 1.29.x。你可以根据实际版本微调。# 1. 关闭并禁用 swap sudo swapoff -a sudo sed -i / swap / s/^\(.*\)$/#\1/g /etc/fstab# 2. 加载内核模块 cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter# 3. 配置 sysctl 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 net.ipv4.conf.all.rp_filter 0 EOF sudo sysctl --system# 4. 安装 containerd 并修改配置 sudo apt update sudo apt install -y containerd sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml # 关键开启 systemd cgroup 支持 sudo sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml sudo systemctl enable --now containerd sudo systemctl restart containerd这里有一个细节值得说明默认生成的 config.toml 里还会带一套 sandbox_image 配置指向 pause 镜像。如果你的节点访问官方镜像仓库的网络不稳定建议在部署前就把 sandbox_image 改成内网镜像仓库或你确定可访问的地址否则 kubelet 给 Pod 启动 sandbox 时可能在拉镜像这一步反复重试。3. kubeadm init到底干了什么过程明白了排错才不慌很多人喜欢直接复制别人的 kubeadm init 命令跑完看到一堆输出然后照着提示做 join。这样虽然也能搭起来但一旦某个环节报错就完全不知道从哪查起。我建议先把 kubeadm init 的三个阶段搞清楚排错时你才知道该看哪个目录、哪个日志。3.1 第一个阶段Preflight Check到底在查什么kubeadm init 执行之后会先做一整套前置检查英文叫 Preflight Check。它会在你正式开始之前把系统里明显不符合条件的问题暴露出来。检查项包括当前是不是 root 或者有没有 sudo 权限节点内存是否低于某个阈值swap 是否关闭内核模块是否加载关键端口是否被占用6443、2379、2380、10250 等操作系统发行版是否在支持列表里已有的 K8s 安装痕迹是否残留比如旧的 /etc/kubernetes 目录。如果 Preflight 报错不要直接盲目加 --ignore-preflight-errors 跳过这等于把问题藏起来。先判断这个报错是否会影响后续流程。像“swap 没有关闭”这种宁可停下来处理掉也别带病上岗。只有一种情况我会考虑允许跳过那就是节点数据盘本来就规划好了由后续存储插件接管检查项里与磁盘相关的误报可以单独处理。3.2 第二个阶段控制平面初始化背后的静态Pod机制Preflight 通过之后kubeadm 会做控制平面初始化。它不会像传统安装器那样把 apiserver 直接做成 systemd 服务而是把组件清单写进 /etc/kubernetes/manifests 目录然后让节点上的 kubelet 把这些清单文件拉起成静态 Pod。静态 Pod 机制是理解控制平面组件的关键。你可以把 kubelet 想象成一个监工它看到 /etc/kubernetes/manifests 目录里有配置文件就会反复检查这些容器是否存活一旦发现挂了就用定义好的镜像重新拉起。这样的好处是控制平面组件不依赖 Docker、containerd 之外的进程管理器etcd、apiserver、controller-manager、scheduler 这些组件即使异常退出kubelet 也会自动恢复它们。这一阶段还会生成大量证书和 kubeconfig统一放在 /etc/kubernetes/pki 目录下。apiserver 对外提供 HTTPS 服务它和其他组件、以及 kubelet 之间的通信都依赖这些证书。用 kubeadm 的好处是证书签发和分发都自动化了你只需要知道这些文件在哪里、有效期多长后面续期才不会慌。初始化完成时kubeadm 会打印几段关键信息一是配置 kubectl 的提示命令二是给你一串用于加入集群的 token 和 CA 证书 hash。这些信息如果你没存下来后面也可能通过命令找回但最好当时就复制好。3.3 第三个阶段join凭据是如何生成和校验的worker 节点加入集群靠的是 kubeadm join 命令它的两个核心参数是 token 和 discovery-token-ca-cert-hash。token 可以理解成一张限时入场券默认有效期 24 小时。它本身是一串格式化字符串K8s 通过它完成节点与控制平面之间的短期身份认证。discovery-token-ca-cert-hash 则是一串哈希值它让人在加入集群前可以核对拿到的 CA 公钥是否真的属于目标控制平面。这样做的目的是防止某个恶意节点伪装成 master诱导你的工作节点把 kubelet 的证书发过去。可以类比成你出门之前先核对对方的警官证而不是只看对方说自己是警察。整个 join 的流程简单来说是客户端用 token 向控制平面发起请求拉取集群 CA 信息和 kubelet 初始配置随后 kubelet 生成自己的证书并自动注册到集群控制平面的 controller-manager 会分配一个 Node 对象随后 kubelet 开始承担该节点上的容器管理职责。4. 从init到join一次完整的集群搭建实操4.1 master节点初始化命令参数逐个讲环境说明控制平面节点 IP 假设是 192.168.10.10主机名 master-1工作节点 IP 假设是 192.168.10.11主机名 worker-1。我在 master 上执行初始化sudo kubeadm init \ --apiserver-advertise-address192.168.10.10 \ --control-plane-endpoint192.168.10.10:6443 \ --pod-network-cidr10.244.0.0/16 \ --kubernetes-versionv1.29.1逐个解释一下参数--apiserver-advertise-address 是用来指定 apiserver 对外广播的地址。如果机器有多张网卡尤其要注意这个参数否则 kubeadm 可能选错网卡地址。--control-plane-endpoint 在单节点场景看起来和 advertise-address 重复但它其实是给高可用预留的。如果以后要做多控制平面这里通常会写一个负载均衡器地址。现在写入 master 自己的地址也没问题后续可以通过修改配置迁移。--pod-network-cidr 是给 Pod 分配 IP 的网段。这个网段由你选择但必须和后续安装的 CNI 插件默认网段保持一致。以 Flannel 为例其默认网络就是 10.244.0.0/16所以这里写 10.244.0.0/16 是很常见的搭配。如果你打算用 Calico它的默认网段通常是 192.168.0.0/16就需要改为这个网段。初始化跑完后正常会看到类似这样的核心输出Your Kubernetes control-plane has been initialized successfully! To start using your cluster, you need to run the following as a regular user: mkdir -p $HOME/.kube sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config Then you can join any number of worker nodes by running the following on each as root: kubeadm join 192.168.10.10:6443 --token abc123.def456 \ --discovery-token-ca-cert-hash sha256:xxxxxxx4.2 配置kubectl与安装Pod网络初始化成功不等于集群可用。你需要先配置 kubectl 才能操作集群因为 kubeadm 把管理员连接信息放在了 /etc/kubernetes/admin.conf 里而 kubectl 默认读取的是当前用户的 ~/.kube/config。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 处于 NotReady 状态。这是因为集群里还没有安装任何 CNI 网络插件节点无法分配 Pod IP控制平面的一些组件的 Pod 会一直处于 Pending。接下来安装 Flannelkubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml如果你使用离线环境也可以提前把 YAML 文件下载下来再 apply。需要留意的是我上面用了 10.244.0.0/16 的网段而 kube-flannel.yml 里的默认 net-conf.json 通常就是 10.244.0.0/16正好匹配。等到 Pod 网络就绪控制平面组件容器全部进入 Running 状态master 节点会从 NotReady 变为 Ready。这一步最容易被人忽略的地方在于网络插件版本与集群版本的兼容性以及插件是否依赖额外的镜像拉取。如果拉取镜像失败节点会一直处于 NotReady这时候多半不是集群配置问题而是网络问题后面我会细说。4.3 worker节点加入集群现在到 worker-1 节点上操作。不需要重复执行 init只需保证 worker 节点也装了 kubelet、kubeadm 和 containerd并且做过环境预配置关闭 swap、加载内核模块、配好 sysctl。然后执行 master 输出的 join 命令sudo kubeadm join 192.168.10.10:6443 \ --token abc123.def456 \ --discovery-token-ca-cert-hash sha256:xxxxxxx如果当时没有保存 token在 master 上可以重新生成kubeadm token create --print-join-command这个命令会生成一个新 token并直接打印一条完整可用的 join 命令非常方便。如果只是查看已有 token可以用 kubeadm token list。4.4 三步确认集群真的健康集群搭建完毕后我建议用以下三步做基础健康验证。第一步看节点状态kubectl get nodes -o wide期望结果是 master 和 worker 都是 Ready 状态并且 VERSION 列一致。第二步看系统组件 Podkubectl get pods -n kube-system -o wide这一条能看到 kube-apiserver、kube-controller-manager、kube-scheduler、etcd、kube-proxy、flannel 等 Pod 的状态。如果某些 Pod 一直 CrashLoopBackOff就需要按故障排查章节的办法处理。第三步做个调度验证。在工作节点上真正跑一个临时 Pod确认不是只有控制平面“自嗨”kubectl run test-pod --imagenginx --restartNever kubectl get pod test-pod -o wide kubectl delete pod test-pod如果这个 Pod 能起来还能显示一个 10.244.x.x 之类的 IP说明 Pod 调度、容器运行时、CNI 网络都已经被打通了。5. 我实测遇到的三个经典故障排查过程比结果重要5.1 cgroup driver不一致导致kubelet CrashLoopBackOff这个故障是我在刚切换 containerd 时踩过的表现非常典型。现象是 kubeadm init 完成了kubectl get nodes 能看到 master 节点但状态一直是 NotReady。kubelet 服务看似活着系统日志里却不断刷错误。排查链路是这样的。第一步确认 kubelet 系统服务状态sudo systemctl status kubelet这时候大概率看到 active (running)但状态不代表健康。继续看完整日志journalctl -u kubelet -f --no-pager | tail -200日志里若出现类似Failed to run kubelet: failed to create kubelet: misconfiguration: kubelet cgroup driver: systemd is different from runtime cgroup driver: cgroupfs问题就锁定在 cgroup driver 不一致上。第二步检查 containerd 配置grep SystemdCgroup /etc/containerd/config.toml如果结果是 false那说明容器运行时使用的是 cgroupfs而 kubelet 期望用 systemd 接口管理 cgroup。两者在资源管理上无法协作最终导致 kubelet 无法正常启动管理能力节点也就一直 NotReady。第三步修改配置sudo sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml sudo systemctl restart containerd sudo systemctl restart kubelet之后再等到节点变成 Ready。这个故障的根子在于两者对 cgroup 的理解和对接方式必须一模一样。用生活类比就是两个人合作记账一个人用 Excel 一个用纸笔哪怕记的内容都一样最后合并报表也会对不上。cgroup 就是内核里那本账kubelet 和容器运行时必须用同一套记账方式。5.2 join时token失效或找不到的秘密有一次我搭测试集群master 搭好之后过了两天才想起来加 worker 节点。结果 join 命令报错信息是ERROR: discovery failed to fetch cluster-info from https://192.168.10.10:6443/...后来仔细看错误信息发现关键还是 token expired。因为 kubeadm 生成的 token 默认有效期只有 24 小时。如果初始化的时间离你加入节点的时间超过了这个期限token 自然就失效了。解决办法很简单。到 master 节点上重新生成一条 join 命令kubeadm token create --print-join-command有人可能会问为什么不能拿证书 hash 自己拼可以的。openssl x509 -pubkey -in /etc/kubernetes/pki/ca.crt | \ openssl rsa -pubin -outform der 2/dev/null | \ openssl dgst -sha256 -hex | sed s/^.* //这样能重新计算出 discovery-token-ca-cert-hash再结合新 token 构造 join 命令。这个故障的关键经验是把 token 和 hash 当成临时凭据对待不要留在聊天记录里到期了还反复使用。要加节点时提前在 master 上用 kubeadm token create --print-join-command 统一生成既保证有效期又保证命令完整正确。5.3 镜像拉取失败别急着怀疑网络还有一种高频故障是初始化时控制平面组件镜像拉不下来。常见的报错出现在 kubeadm init 阶段failed to pull image registry.k8s.io/kube-apiserver:v1.29.1: rpc error: code DeadlineExceeded desc context deadline exceeded看到这种信息第一反应是网络问题没错但我的建议是不要停在“调整网络”这个层面而是先把失败的具体环节定位清楚。可以先检查节点上到底哪些镜像已经存在、哪些缺失sudo crictl images如果发现所有镜像都没有再手动测试单个镜像能不能拉取sudo crictl pull registry.k8s.io/pause:3.9这里多说一句kubeadm 在 preflight 阶段其实会给一个提示说“you can try to pull images first”。你完全可以主动执行sudo kubeadm config images pull这一步把需要的镜像提前拉到本地然后再跑 kubeadm init可以把“初始化失败”和“网络拉取失败”这两个问题隔离开。如果你的节点访问官方镜像仓库的网络不稳定更稳妥的做法是提前在 containerd 的 config.toml 里配置 registry mirrors指向你所在环境的镜像加速源或内网仓库。改完配置后记得重启 containerd。这个故障给我的教训是看到“镜像拉取失败”先区分是单个镜像的问题还是所有镜像都无法拉取先区分是认证问题、路径问题还是超时问题。直接去调网络往往解决不了实质问题因为你不知道到哪一步才真正失败。6. 集群起来之后我建议你马上做的几件事6.1 一张表验证集群健壮性集群搭好只是个开始为了确保后续使用不会突然翻车我习惯在搭建完成后做一轮完整检查把结果记录下来。检查对象关键命令期望结果节点状态kubectl get nodes所有节点 Ready控制平面 Podkubectl get pods -n kube-system全部 Running 或 Completedkubelet 日志journalctl -u kubelet --no-pager无持续报错容器运行时systemctl status containerdactive (running)etcd 健康kubectl get --raw/healthz?verbose返回 oketcd 条目为 healthyAPI Server 健康kubectl get --raw/readyz返回 okPod 调度kubectl run test --imagenginxPod 被正常调度并获得 IP服务解析kubectl run test --imagebusybox --command -- sleep 300进入 Pod 后可解析集群内 Service 域名这些检查做完基本可以确认集群处于健康可用状态。6.2 版本升级与证书续期的问题用 kubeadm 搭的集群在运行一段时间后还有两件事绕不开证书续期和版本升级。默认情况下kubeadm 签发的控制平面证书有效期是一年。对于长期运行的集群这本身就是个大坑。很多人的测试集群跑着跑着突然发现 kubectl 命令报证书过期其实是因为没有提前续期。查看各类证书的过期时间kubeadm certs check-expiration续期命令sudo kubeadm certs renew all执行完后需要手动重启控制平面组件让新证书生效。对于静态 Pod 组件可以通过删除对应 Pod 来实现重启kubelet 会自动把它们拉起来。同时还需要更新管理员的 kubeconfig 文件否则本地 kubectl 也会一直用旧证书。版本升级这件事尤其要遵循官方推荐流程。先看当前版本kubeadm version然后在 master 节点查看可升级版本和具体步骤sudo kubeadm upgrade plan升级控制平面sudo kubeadm upgrade apply v1.30.0升级完成后再在 master 和 worker 节点上依次更新 kubelet 相关的 apt 包或者 rpm 包重启 kubelet。注意不要只改控制平面忘了升级节点上的 kubelet否则版本混乱会让你很难排查问题。6.3 一些散落在实操里的体会最后再分享几条我在反复操作中沉淀下来的个人经验。第一所有节点的时间同步不是小事。我在环境准备中提过一次但还是想单独强调K8s 集群里的时间不同步不只是日志对不上的问题它会直接导致证书校验失败、token 过期判断出错。建议在节点规划阶段就把 NTP 服务装好不要等出错再补。第二worker 节点也需要完整安装 kubeadm。很多新手以为工作节点只要装 kubelet 和 containerd 就行实际上 kubeadm join 命令本身就要调用 kubeadm 二进制少装一个后面执行 join 就会报 command not found。第三不要随手用 --ignore-preflight-errors。这条是我自己的教训早期为了省事遇到 preflight 报错就顺手跳过结果集群跑起来之后这个隐患还在后面排错花的精力远大于当初处理问题的时间。preflight 报错本质上是在替你把关认真对待它后面的路会顺很多。第四日志是最好的老师。遇到任何异常第一步先看 kubelet 日志再看对应组件的日志最后才猜配置。我的经验里几乎所有看似玄学的问题最后都能在日志里找到一行直接原因。用 kubeadm 部署 k8s 这套流程熟练之后二十分钟就能跑完两节点集群但真正值钱的不是跑完这件事而是过程中你建立起对集群内部机制的正确感知。希望这篇实践记录能帮你少走点弯路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑