kubeadm搭建Kubernetes集群:从环境准备到CNI网络插件选型
这几年经常有人问我Kubernetes 集群到底怎么装才比较稳我的回答始终是老老实实走 kubeadm。市面上的安装方式很多有二进制手工部署、有发行版自带的一键脚本、也有 Minikube 这类单机玩具但 kubeadm 是 Kubernetes 官方维护的标准引导工具它不替你包办一切只帮你把控制平面拉起来、把证书和 token 生成好剩下的网络插件、存储、监控全留给你自己决定。这种半自动的做法反而最值得推荐——既不会黑盒化到出了问题无从下手也不会累到亲手一个个进程去部署。这篇文章就基于 kubeadm 完整走一遍集群安装流程从环境准备、三件套安装、init 参数设计到网络插件选型和排障链路适合刚接触 K8s 想搭一套可用集群的同学也适合已经装过但老被各种报错卡住的人对照排查。1. 为什么至今仍推荐 kubeadm官方半自动方案的取舍逻辑1.1 先看懂几条安装路径的区别在动手之前我建议你先花十分钟理解一下不同安装方式的定位差异不然很容易装完就开始后悔。二进制手动部署是最原始的方式所有的组件、证书、kubeconfig、systemd 服务全部自己创建和配置。它的好处是让你把每个组件的作用、监听端口和配置关系彻底搞明白坏处是步骤多、容易出错、升级困难适合学习原理不适合日常使用。kubeadm 是官方工具工作时它负责生成证书、生成 kubeconfig、把 apiserver、etcd、controller-manager、scheduler 以静态 Pod 的方式跑起来然后等着你自己装 CNI 网络插件。对比二进制方案它省去了大量机械重复的证书和配置工作但又没把底层逻辑完全藏起来所有生成的文件都在 /etc/kubernetes 下面可以查看。Minikube 和 Kind 这类方案主要面向本地开发和 CI 场景它们的价值是快速获得一个可用的单节点环境但生产语义和真实集群有差距网络、存储、多节点调度都做过简化。此外部分云厂商或发行版会提供一键脚本或托管集群方案这类方式最省心但如果你是想真正掌握 K8s 运维能力我还是建议从 kubeadm 开始它是最好的教学与生产中间态。1.2 控制平面静态化的设计精髓kubeadm 最核心的设计是把控制平面组件全部声明为 kubelet 管理的静态 Pod。具体来说执行kubeadm init之后kubeadm 会在/etc/kubernetes/manifests/目录下生成四个 manifest 文件分别对应 kube-apiserver、kube-controller-manager、kube-scheduler 和 etcd。kubelet 会持续监听这个目录只要发现里面有 manifest 文件就用容器运行时把它们拉起来如果某个组件进程意外退出kubelet 会按 manifest 里的定义自动重新拉起。这就带来了两个重要特性。一个是自愈能力apiserver 挂了kubelet 会自动重启它而不是需要运维手动 systemctl restart。另一个是升级路径清晰升级 Kubernetes 版本时只需要替换 manifest 里的镜像版本kubelet 检测到变更后就会滚动更新对应静态 Pod。理解了这一点你排查控制平面问题时就会多一个思路——先看/etc/kubernetes/manifests里的文件是否正常。1.3 从单集群到高可用kubeadm 的成长空间还有人担心 kubeadm 只能用于搭建实验环境。实际上 kubeadm 设计的升级路径很完整单机实验集群可以用它生产环境的多控制平面高可用集群同样可以用它。高可用与单机安装的核心差异只有两点一是控制平面节点要配置一个负载均衡地址作为 controlPlaneEndpoint让多个 apiserver 共享一个入口二是 etcd 可以选择堆叠方式部署也就是每个控制平面节点上各跑一个 etcd 实例组成三节点集群。kubeadm join 时如果控制平面节点加入需要额外传入--control-plane参数并把证书文件拷贝到位。换句话说从单节点练手到生产高可用中间的知识增量是平滑的这也是我推荐 kubeadm 的另一个原因。2. 动手前必须敲定的三类事情版本、节点规划与系统参数2.1 版本与操作系统选型稳定比新更重要Kubernetes 的版本迭代非常快每个小版本会持续收到约三个月的安全更新。我的建议是不要追新选择最新的稳定小版本并在实验环境里把目标版本固定下来不要装完一周就想着升级。目前我常用的版本是 1.28.x它在 kubeadm、kubelet、containerd 的配合上已经非常成熟踩坑资料也最多。对于操作系统Debian/Ubuntu 系和兼容 RHEL 的发行版都能正常跑。需要注意内核版本太老的内核在 iptables、网络命名空间方面会出问题建议内核不低于 5.4。如果你用的是较老的发行版先确认内核版本再动手。容器运行时方面从 Kubernetes 1.24 开始已经彻底移除了对 Docker 作为运行时dockershim的支持现在主流方案是 containerd。你不需要再安装 Docker直接装 containerd 即可这也让整个安装链路更干净。2.2 节点规划单机实验与三机集群的推荐配置建议在规划阶段把节点角色、IP、内存、磁盘列清楚。下面是两种常见规划的参考。场景节点角色数量配置建议说明实验环境单节点同时承担控制平面与工作负载14C8G40GB 可用磁盘需注意默认控制平面节点带有污点单节点需要去除污点才能调度业务 Pod最小生产演练控制平面节点34C8G80GB SSD3 个控制平面节点形成 etcd 高可用最小生产演练工作节点28C16G100GB SSD按业务需要扩容IP 规划也要提前定好。控制平面节点之间、控制平面与工作节点之间需要二层互通。如果你要用 VLAN 或云网络提前确认端口放通策略特别是 6443、2379、2380、10250、10251、10252、10255 以及 NodePort 段。更好用的做法是先写好端口清单再对照安全组或防火墙策略逐项放通避免初始化到一半才发现 apiserver 端口不通。还需要留意 Pod 网段和 Service 网段不要和现有物理网络冲突。比如你公司内网用的是 10.0.0.0/8那 Pod 网段就不要再用 10.244.0.0/16可以换成 172.20.0.0/16。这个网段一旦确定后面装网络插件时必须保持一致再想改就只能重置集群。2.3 内核模块、sysctl 与 swap最容易被忽略的前提kubeadm 对系统环境有一定要求其中三项配置如果漏掉初始化必定失败或者集群跑起来非常不稳定。第一项是关闭 swap。kubelet 默认要求禁用 swap因为它要和 cgroup 的内存统计保持一致如果系统还在使用 swapkubelet 的内存 QoS 策略会失去意义Pod 的内存限制也无法可靠执行。你可以用swapoff -a临时关闭同时把/etc/fstab里的 swap 行注释掉避免重启后重新挂载。第二项是加载必要的内核模块。容器网络需要 bridge 和 overlay 相关模块执行下面命令cat EOF | sudo tee /etc/modules-load.d/k8s.conf overlay br_netfilter EOF sudo modprobe overlay sudo modprobe br_netfilter第三项是写入 sysctl 参数。注意每次重启后需要重新加载模块所以 modules-load.d 和 sysctl.d 都要配置好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设置为 1需要确保 br_netfilter 模块已经加载成功否则 sysctl 会报错。另一个容易忽略的点是部分云主机的 iptables 后端是 nftablesKubernetes 的 kube-proxy 会根据节点上的 iptables 版本自动选择模式一般不用额外处理但如果你临时改了默认 iptables 版本需要注意保持一致。3. 三件套安装与 containerd 调优第二道门槛是版本对齐3.1 用软件源安装并固定 kubeadm、kubelet、kubectl 版本kubeadm、kubelet、kubectl 这三个组件是配套的版本必须严格对齐。kubeadm 版本不能比 kubelet 版本新太多否则初始化时会报版本不匹配的错误。以 Debian/Ubuntu 系为例先添加 Kubernetes 官方软件源。不同小版本需要进入对应的源路径下面的示例使用 v1.28sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl gpg curl -fsSL https://pkgs.k8s.io/core:/stable:/v1.28/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.28/deb/ / | sudo tee /etc/apt/sources.list.d/kubernetes.list sudo apt-get update apt-cache madison kubeadmapt-cache madison会列出所有可用的版本号然后我们需要手动指定一个完全相同的版本号安装比如sudo apt-get install -y kubelet1.28.2-00 kubeadm1.28.2-00 kubectl1.28.2-00安装完成后立刻做标记防止之后其他依赖安装把版本带偏sudo apt-mark hold kubelet kubeadm kubectl为什么这么强调版本对齐因为 kubelet 是节点上的代理kubectl 是客户端kubeadm 是引导工具。三者如果跨了多个小版本API 协议协商时可能因为特性门控不一致而出现诡异报错比如 kubelet 无法和 apiserver 通信、字段被丢弃、初始化过程中出现 Unimplemented 等。与其花时间排查不如一开始就锁死版本。3.2 containerd 安装与 cgroup driver 一致性容器运行时这边我推荐直接安装 containerd。安装方式和系统包管理有关安装完成后需要做两个改动。第一步生成默认配置sudo mkdir -p /etc/containerd containerd config default | sudo tee /etc/containerd/config.toml /dev/null第二步修改/etc/containerd/config.toml。重点有两个地方[plugins.io.containerd.grpc.v1.cri] sandbox_image pause镜像地址 systemd_cgroup truesandbox_image是每个 Pod 启动时用于创建网络命名空间的 pause 容器镜像。默认值指向 Kubernetes 官方镜像仓库这个仓库在我的网络环境里不一定能直接拉取。你需要先确认自己环境能访问哪些镜像仓库把这里的地址替换成可用的 pause 镜像地址。版本要和 Kubernetes 小版本匹配比如 1.28 集群一般用 pause 3.9 系列镜像。systemd_cgroup true对应 cgroup 驱动采用 systemd。这里有一个关键点kubelet 默认使用的 cgroup driver 是 systemd而 containerd 的默认配置有可能是 cgroupfs。如果两边不一致kubelet 在启动后会报类似 failed to run kubelet 的错误。所以容器运行时的 cgroup 驱动必须和 kubelet 保持一致统一用 systemd 在目前主流发行版上是兼容性最好的选择。改完配置后重启 containerdsudo systemctl restart containerd sudo systemctl enable containerd3.3 装完三件套后如何快速自检不要急着跑 kubeadm init先把基础状态确认一遍。systemctl status kubelet containerd此时 kubelet 可能处于没配置好的状态因为它还没有/etc/kubernetes/kubelet.conf等文件这是正常的。接着检查 kubelet 日志确认没有因为系统参数或运行时问题反复崩溃journalctl -u kubelet --since 5 minutes ago如果日志里出现权限、cgroup、swap 相关的错误说明前面的系统准备没做完先回头处理。另外用kubectl version --client验证 kubectl 版本是否正常这时不需要集群也可以直接看客户端版本输出。4. kubeadm init 配置详解从初始化到 worker 节点加入4.1 我更推荐用配置文件而不是一长串命令行参数很多教程把kubeadm init写成一行带一堆参数比如--apiserver-advertise-address1.2.3.4 --pod-network-cidr10.244.0.0/16。这种写法不是不行但参数一多就容易漏而且不方便复用、不方便记录。我更推荐写一份 kubeadm 配置文件单控制平面实验环境的参考如下apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.1.10 --- apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.28.2 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12这里有两个惯用点需要说明。podSubnet要和 CNI 插件保持一致我这里的 10.244.0.0/16 是 Flannel 默认使用的网段如果你后面选择 Calico 作为网络插件可以把 podSubnet 换成 Calico 对应的默认网段。advertiseAddress要填写当前节点真实可达的 IP千万不要填回环地址或云主机内网 IP 段里不可达的地址否则 worker 节点连不上 apiserver。如果规划的是多控制平面高可用集群配置里需要额外增加controlPlaneEndpoint也就是负载均衡的虚拟 IP 或域名。这个值会写进生成的证书 SAN 里所以一定要提前规划好。4.2 init 输出里的关键信息kubeconfig、token、join 命令执行初始化sudo kubeadm init --config kubeadm-config.yaml这一步会做全套预检包括系统参数、端口占用、镜像拉取、证书生成。如果一切正常最后会输出一段类似这样的信息Your Kubernetes control-plane has 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.1.10:6443 --token xxxxx --discovery-token-ca-cert-hash sha256:xxxxx注意这里有两段信息不能丢。第一段是 admin.conf 的拷配置方法很多新手直接用小写的 config 去测试 kubectl结果权限对不上。第二段是 join 命令worker 节点加入集群就靠它。强烈建议把 join 命令完整保存到一个文件里别等 token 过期后再到处找。对于单节点实验环境控制平面节点默认是不调度业务 Pod 的因为节点上有污点。如果你就是想在一台机器上做实验执行下面命令允许 Pod 调度kubectl taint nodes --all node-role.kubernetes.io/control-plane-4.3 worker 节点加入与 token 过期后的处理在 worker 节点上同样需要先完成系统参数配置、三件套安装和 containerd 配置然后再执行 master 输出的 join 命令。如果 join 命令已经丢失或者 token 过期可以回到控制平面节点执行kubeadm token create --print-join-command这是我最常用的命令它会生成一条新的带有效期的 join 命令默认 token 有效期 24 小时。如果需要更长时效可以加--ttl0表示永久有效但生产环境不建议这样做。加入完成后等待片刻在控制平面节点运行kubectl get nodes如果 worker 节点状态是 NotReady大概率是网络插件还没安装。这就进入了下一个关键章节——CNI 插件。5. CNI 网络插件为什么节点 NotReady 和它有关5.1 kubeadm 为什么默认不装网络插件很多第一次装集群的人都会遇到这个困惑三个节点都成功 join 了但 kubectl get nodes 全都是 NotReady。原因几乎都是同一个——集群里没有任何 CNI 网络插件。kubeadm 刻意不预装网络插件因为 Kubernetes 的容器网络模型允许有多种实现比如 Flannel、Calico、Cilium、Weave Net 等。它们解决的都是同一件事让集群里任意一个 Pod 都能通过自己的 IP 与其他 Pod 互通并且实现 Service 的负载均衡。如果没有 CNI 插件kubelet 无法给 Pod 配置网络命名空间节点健康检查就会失败节点自然显示 NotReady。选择网络插件时优先考虑三个因素是否支持 NetworkPolicy、封装方式带来的性能损耗、部署和运维的复杂度。下面单独拆两个最常见的方案。5.2 Flannel 实施细节Flannel 的优点是简单、依赖少、适合快速上手。它默认使用 VXLAN 封装Pod 之间通过隧道通信因此对底层网络要求不高跨主机也能跑通。实施时先下载 Flannel 的 manifest 文件一般是一个kube-flannel.yml。这个文件默认使用的网络是 10.244.0.0/16它必须和你 init 时配置的podSubnet一致。如果我用的是这个默认网段直接 apply 即可kubectl apply -f kube-flannel.yml如果不是这个网段需要先修改 manifest 里的 Network 字段或者 init 时就直接把 podSubnet 改掉两者必须统一。Flannel 装完后coredns 处于 Pending 状态是正常的因为还没有可用节点等 Flannel 在节点上的 Pod 启动成功节点会很快变为 ReadyDNS 也会被调度起来。Flannel 的局限也比较明显它默认不提供 NetworkPolicy 能力也没有 Ingress Controller 相关的支持。如果你的场景只是内部服务互通、不涉及跨命名空间访问控制它完全够用。5.3 Calico 实施细节与模式取舍Calico 是另一个非常流行的方案功能比 Flannel 完整得多支持 BGP、NetworkPolicy、IPIP 封装等。在节点数量较多、生产环境有多租户隔离需求时Calico 是更实际的选择。安装 Calico 通常从项目发布页获取一个包含 operator 或完整 manifest 的文件。使用 manifest 方式的话需要检查文件里默认的 CALICO_IPV4POOL_CIDR 是否与集群podSubnet一致不一致就改掉env: - name: CALICO_IPV4POOL_CIDR value: 10.244.0.0/16Calico 支持 IPIP 和 BGP 两种模式IPIP 模式将 Pod 流量封装后走宿主机网络BGP 模式让节点之间直接通过三层交换路由信息。BGP 模式网络性能更好但对底层网络有要求节点和交换机需要支持并允许 BGP 协议交互。如果你在云环境下部署默认 IPIP 模式通常更省心。我还遇到过一个细节Calico Pod 如果因为镜像拉取失败启动不了节点会一直 NotReady。这时候先排查镜像是否能访问优先在本地提前把 Calico 镜像 pull 过来再 apply manifest可以避免很多时间浪费。5.4 网络插件切换的正确姿势如果你已经用 Flannel 跑了一段时间想换成 Calico或者反过来不要直接在集群里 apply 新插件的 manifest。CNI 插件的残留配置会导致 node 上存在多套 CNI 配置kubelet 不知道该用哪一套。正确操作是先把旧的插件资源清理掉然后在每个节点上删除/etc/cni/net.d/下的旧配置文件重启 kubelet最后再 apply 新的 CNI。如果切换后异常复杂最干净的办法是kubeadm reset后重新 init。我的经验是在一开始就定好网络插件并且不要频繁切换比事后折腾省心得多。6. 装完不算完验证清单与初始化失败的排查链路6.1 初始化后的黄金验证流程集群初始化完成、CNI 安装成功之后不要急着部署业务先把下面这套验证流程跑一遍确认核心组件真的健康。第一节点状态kubectl get nodes -o wide所有节点都 Ready 后查看组件状态。注意kubectl get cs在老版本里已经废弃更可靠的检查方式是看静态 Podkubectl get pods -n kube-system -o wide重点看 coredns、etcd、kube-apiserver、kube-controller-manager、kube-scheduler 是否 Running。如果 coredns 一直 CrashLoopBackOff先确认网络插件是否正常多数情况是 CNI 没生效。第二DNS 和网络功能测试。运行一个临时 Pod 测试 DNS 解析kubectl run -it --rm test-curl --imagecurl --restartNever -- nslookup kubernetes.default.svc.cluster.local第三验证 Pod 跨节点通信。你可以部署两个测试 Pod分别调度到不同节点用 ping 或 wget 测试对端 Pod IP。这一步能确认 CNI 的实际数据链路是通的而不只是节点状态显示 Ready。6.2 高频报错的排查链路我把实际使用中最常见的几类问题按排查链路列出来遇到报错时不要慌按顺序看。第一类kubeadm init 卡在 Waiting for the kubelet to boot。这种情况通常是 kubelet 拉起静态 Pod 失败了。先看 kubelet 日志journalctl -u kubelet -f如果日志里有 container runtime is down 相关字样说明 containerd 没起来或者配置有问题。检查 containerd 状态和服务日志systemctl status containerd journalctl -u containerd -f还要确认 crictl 能连上 containerdcrictl config --set runtime-endpointunix:///run/containerd/containerd.sock crictl ps -a通常到这里就能发现问题比如 cgroup driver 不一致、pause 镜像拉不动、socket 路径不对。第二类节点显示 NotReady。先看节点上的 Pod 情况kubectl describe node node-name kubectl get pods -n kube-system -o wide | grep node-name大概率是 CNI 没有就绪或者 kube-proxy 异常。查看对应 Pod 日志多半会看到网络插件初始化失败。如果 kube-proxy 报错也可能是节点上的 IP 规则冲突清理后重启 kubelet 即可。第三类join 时提示 token 无效或证书校验失败。重新生成 join 命令kubeadm token create --print-join-command如果多次 join 失败建议先kubeadm reset清理这个节点再重新 join。reset 不会影响其他节点只是清空当前节点的 kubelet 配置和 CNI 残留是一个相当安全的操作。6.3 避免重复踩坑的几个习惯最后分享几个我吃过的亏换来的习惯能让后续安装顺利很多。建议把每次安装的版本号和改动记在一个笔记文件里。很多环境问题都是因为不同机器上的内核版本、containerd 版本、kubeadm 版本不一致引起的把版本固定下来能极大减少不确定性。用脚本保存你完整的安装命令。我现在会在每台机器上先跑一段初始化脚本把 sysctl、模块、软件源、三件套版本全部搞定然后再手动执行 kubeadm init。这样重建环境的时候非常快也避免人工操作遗漏。不要忽略预检。kubeadm init 的预检其实已经覆盖了很多常见问题但它不会替你检查 CNI 网段冲突、不会检查镜像仓库连通性。所以动手前先kubeadm config images list看一下需要哪些镜像在你当前环境里手动拉一遍确认都能拿到再开始 init这是节省时间的最有效手段。装完集群只是第一步但它是最重要的一步。把 kubeadm 这套流程吃透之后后面不管是加节点、升级版本、还是迁移到高可用架构都会顺手很多。