多节点 Kubernetes 集群这件事说难不难说简单也真不简单。我第一次在几台裸机上把集群拉起来的时候踩的坑比写的 YAML 还多——容器运行时版本对不上、Pod 网段和宿主机网段打架、节点加入时 token 过期、CoreDNS 一直 CrashLoopBackOff。后来折腾的次数多了才慢慢摸清楚这套东西的脾气。这篇内容就是把我这些年搭建多节点集群的完整思路和实操细节摊开讲一遍从机器规划、运行时选型、集群初始化、网络插件配置到节点扩容和常见故障排查尽量做到你照着做就能跑通。多节点 k8s 集群的核心价值在于把计算资源池化让调度器帮你决定 Pod 落在哪台机器上同时通过副本和反亲和性策略实现真正的高可用。单节点集群只能用来学习一旦节点挂了服务就没了多节点集群才能体现 Kubernetes 的编排能力。这篇内容适合已经了解 Docker 基础、想从单机 minikube 或 kind 过渡到真实多节点环境的开发者也适合需要在内网自建集群的运维同学。下面所有操作基于常见的 Linux 发行版和 containerd 运行时版本以 Kubernetes 1.28 及以上为准低版本的部分参数会有差异我会在对应位置标注。1. 集群整体设计与机器规划思路动手之前先把图纸画清楚比上来就敲命令重要得多。多节点集群的规划涉及几个绕不开的决策节点角色怎么分、网段怎么划、运行时选哪个、控制平面要不要做高可用。这些决策一旦定下来后面再改成本很高所以值得花时间想明白。1.1 节点角色划分与数量取舍一个最小可用的多节点集群至少需要一台控制平面节点control plane和一台工作节点worker。但我一般建议至少准备三台机器一台控制平面加两台工作节点。原因很实际——只有一台工作节点的话你根本验证不了跨节点调度、Pod 反亲和性、服务负载均衡这些多节点才有的特性集群等于白搭。如果对可用性有要求控制平面就要做高可用通常是三台控制平面节点加若干工作节点。控制平面为什么是三台而不是两台这跟 etcd 的选举机制有关。etcd 使用 Raft 协议需要多数派quorum才能正常写入三节点集群允许挂掉一台两节点集群挂掉一台就只剩一票达不到多数派整个集群的写入就停了。所以控制平面要么一台接受单点故障要么三台起步两台是最尴尬的配置。我常用的几种规格供参考场景控制平面工作节点适用情况学习验证12个人实验、功能验证小规模生产33 及以上内部服务、中等负载资源受限环境11仅做调度逻辑测试工作节点的数量取决于你的业务负载和资源需求但要注意每台节点上 kubelet、kube-proxy 以及网络插件本身会占用一部分 CPU 和内存一般预留 0.5 核和 500MB 内存给系统组件比较稳妥。1.2 网段规划最容易埋雷的地方网段规划是新手最容易忽略、出问题又最难排查的环节。多节点集群里至少涉及三层网络宿主机物理网络、Pod 网络、Service 网络。这三层网段绝对不能重叠否则会出现路由混乱、Pod 无法互通、Service 访问超时等一堆玄学问题。我的规划习惯是这样的宿主机用192.168.0.0/24这类常见内网段Pod 网段选10.244.0.0/16Service 网段选10.96.0.0/12。选这两个网段的原因是它们属于私有地址空间里相对冷门的段不容易和公司内网、办公网冲突。如果你所在环境已经占用了10.244.x.x那就换成172.16.0.0/16之类的。注意Pod 网段和 Service 网段在初始化集群时必须通过参数指定且初始化完成后无法直接修改。改网段意味着重建集群所以这一步务必确认清楚。另外还要确认宿主机之间网络互通、防火墙放行必要端口。控制平面节点需要放行 6443API Server、2379-2380etcd、10250kubelet、10257、10259 等端口工作节点需要放行 10250、30000-32767NodePort 范围以及网络插件使用的端口。很多节点加入失败的问题追根溯源都是防火墙没放行。1.3 容器运行时选型containerd 还是别的Kubernetes 从 1.24 版本开始移除了对 Docker 作为容器运行时的直接支持dockershim 被删掉了现在主流选择是 containerd 或 CRI-O。我这些年基本都用 containerd原因是它足够轻量、和 Kubernetes 的 CRI 接口对接顺畅、社区资料多、出问题好查。containerd 的配置里有一个关键点SystemdCgroup必须设为true。因为现在主流 Linux 发行版都用 systemd 管理 cgroup如果 containerd 还用 cgroupfs 驱动就会和 kubelet 的 cgroup 驱动不一致导致节点上 Pod 频繁重启、资源限制不生效。这个配置在/etc/containerd/config.toml里生成默认配置后手动改一行就行后面实操部分我会给出具体命令。选 containerd 还有一个隐性好处它的镜像加速配置、私有仓库认证配置都比较直观改配置文件重启服务即可生效不像以前 Docker 那套需要理解 daemon.json 的各种字段。2. 环境准备与基础配置实操机器规划定了之后就要对每台机器做统一的基础配置。这一步的核心原则是所有节点的基础环境必须尽可能一致操作系统版本、内核参数、时间同步、容器运行时版本都要对齐。不一致的环境是多节点集群各种诡异问题的温床。2.1 系统层面的统一配置先在所有节点上做几件必做的事。第一是关闭 swapkubelet 默认要求 swap 关闭否则启动会报错。临时关闭用swapoff -a永久关闭要注释掉/etc/fstab里的 swap 行。为什么要关 swap因为 Kubernetes 的调度和资源管理假设节点内存是可预测的swap 会让内存压力下的行为变得不可控Pod 的 QoS 保证也会失效。第二是加载内核模块。容器网络依赖overlay和br_netfilter两个模块前者支撑 OverlayFS 文件系统后者让网桥流量能经过 iptables 规则。用modprobe overlay和modprobe br_netfilter加载并写入/etc/modules-load.d/k8s.conf保证开机自动加载。第三是调整内核参数。需要开启 IP 转发并让 iptables 能处理桥接流量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 --systemnet.ipv4.ip_forward 1是 Pod 跨节点通信的基础不开这个跨节点的 Pod 流量根本转发不出去。bridge-nf-call-iptables则保证 Service 的 iptables 规则能正确作用于网桥流量。第四是时间同步。集群里各节点时间偏差过大会导致证书校验失败、日志时间错乱、etcd 选举异常。用 chrony 或 systemd-timesyncd 保证所有节点时间一致偏差控制在秒级以内。第五是主机名和 hosts 解析。每台机器设置唯一的主机名并在所有节点的/etc/hosts里互相添加解析记录。虽然可以用 IP 直接通信但用主机名管理起来清晰得多kubelet 注册节点时也会用主机名。2.2 安装 containerd 并修正 cgroup 驱动以常见的 Debian 系为例安装 containerd 可以通过官方仓库apt-get update apt-get install -y containerd安装完成后生成默认配置mkdir -p /etc/containerd containerd config default | tee /etc/containerd/config.toml然后编辑配置文件找到SystemdCgroup这一项把false改成true[plugins.io.containerd.grpc.v1.cri.containerd.runtimes.runc.options] SystemdCgroup true改完重启服务并设为开机自启systemctl restart containerd systemctl enable containerd这里再强调一次SystemdCgroup的重要性。我见过不止一次节点上 Pod 反复重启、kubectl describe显示容器被 OOMKilled 但实际内存远没到限制最后查出来就是 cgroup 驱动不一致。kubelet 用 systemd 驱动containerd 用 cgroupfs 驱动两套驱动对同一个 cgroup 的理解不同资源统计和限制就全乱了。2.3 安装 kubeadm、kubelet、kubectl这三个组件的分工要清楚kubeadm 负责集群的初始化和节点加入kubelet 是跑在每个节点上的节点代理负责管理本节点的 Pod 生命周期kubectl 是命令行客户端。kubelet 和 kubeadm 需要装在所有节点上kubectl 主要装在控制平面节点工作节点装不装都行看个人习惯。安装时要注意版本锁定。kubelet、kubeadm、kubectl 三者的版本要一致且不能随意升级因为 Kubernetes 只保证相邻小版本间的兼容性。用包管理器安装时建议指定版本号避免自动装到最新版导致和集群其他组件不匹配。apt-get install -y kubelet1.28.4-1.1 kubeadm1.28.4-1.1 kubectl1.28.4-1.1 apt-mark hold kubelet kubeadm kubectlapt-mark hold这步很关键它防止系统更新时把这三个包顺手升级了。我吃过这个亏——一次例行系统更新后 kubelet 被升到新版本和集群版本差了两个小版本节点直接 NotReady。提示安装完成后先别急着初始化用kubeadm version、kubelet --version、kubectl version --client确认三者版本一致再往下走。3. 集群初始化与网络插件配置基础环境就绪后就到了最关键的初始化环节。控制平面节点的初始化决定了整个集群的骨架参数一旦定错后面要么重建要么忍受各种别扭。3.1 kubeadm init 的参数拆解初始化命令看起来长但每个参数都有明确用途。我常用的初始化命令是这样的kubeadm init \ --apiserver-advertise-address192.168.0.10 \ --control-plane-endpoint192.168.0.10:6443 \ --pod-network-cidr10.244.0.0/16 \ --service-cidr10.96.0.0/12 \ --kubernetes-versionv1.28.4 \ --image-repositoryregistry.aliyuncs.com/google_containers逐个解释。--apiserver-advertise-address指定 API Server 对外通告的地址填控制平面节点的内网 IP。--control-plane-endpoint是集群的统一访问入口单控制平面时填本机 IP 加端口即可做高可用时要填一个虚拟 IP 或负载均衡地址。--pod-network-cidr和--service-cidr就是前面规划好的两个网段必须和网络插件的配置对应上。--kubernetes-version锁定版本避免 kubeadm 自动选到不匹配的版本。--image-repository指定镜像仓库因为默认的镜像仓库在国内拉取可能很慢换成可访问的镜像源能省很多时间。初始化过程中 kubeadm 会做一系列预检preflight checks包括检查端口占用、swap 状态、容器运行时、内核参数等。如果预检报错不要急着加--ignore-preflight-errors跳过先看清楚报错原因。跳过预检只是把问题藏起来后面照样会以更隐蔽的方式爆发。初始化成功后终端会输出一段提示告诉你怎么配置 kubectl 的 kubeconfig以及工作节点加入集群的命令。kubeconfig 的配置很简单mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config配好之后kubectl get nodes应该能看到控制平面节点但状态是 NotReady因为还没装网络插件。3.2 网络插件选型与安装网络插件CNI是多节点集群的命脉没有它 Pod 之间无法跨节点通信。主流选择有 Calico、Flannel、Cilium 等。我的经验是小规模集群、追求简单稳定选 Flannel需要网络策略NetworkPolicy、对网络性能有要求选 Calico想玩 eBPF、需要可观测性选 Cilium。Flannel 的安装最省心一条命令搞定kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml但要注意 Flannel 默认的 Pod 网段是10.244.0.0/16如果你初始化时用了别的网段要下载 YAML 改掉里面的Network字段再 apply。这个不一致是新手常犯的错——网段对不上Flannel 的 DaemonSet 起来了但 Pod 网络就是不通。Calico 的安装稍微复杂一点需要根据网段调整配置。用 operator 方式安装时要在自定义资源里指定cidr字段和初始化时的 Pod 网段一致。Calico 的好处是自带网络策略支持默认还会给节点间流量做加密可选适合对安全有要求的场景。装完网络插件后等一两分钟再kubectl get nodes控制平面节点应该变成 Ready。如果一直是 NotReady用kubectl get pods -n kube-system看网络插件的 Pod 状态再用kubectl describe和kubectl logs查具体原因。3.3 工作节点加入集群工作节点加入集群靠的是 kubeadm init 输出里的那条kubeadm join命令形如kubeadm join 192.168.0.10:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxx这条命令里的 token 默认有效期是 24 小时过期后需要重新生成kubeadm token create --print-join-command这条命令会直接输出一条完整的、带新 token 和证书哈希的 join 命令复制到工作节点执行即可。证书哈希的作用是让工作节点验证控制平面的身份防止加入到一个伪造的集群。工作节点执行 join 后回到控制平面kubectl get nodes就能看到新节点。刚加入时状态可能是 NotReady等 kubelet 拉取完必要的镜像、网络插件在该节点上启动完成后就会变 Ready。如果超过几分钟还是 NotReady去工作节点上systemctl status kubelet和journalctl -u kubelet -f看日志常见原因是容器运行时没配好、镜像拉不下来、或者网络插件在该节点启动失败。4. 集群验证与常见故障排查集群搭起来不等于搭对了。我习惯在交付前做一轮系统性验证把能想到的问题提前暴露出来而不是等业务上线后才发现某个节点网络不通。4.1 分层验证清单验证要分层做从下往上逐层确认。底层不通上层怎么测都是白搭。验证层级验证方法预期结果节点状态kubectl get nodes -o wide所有节点 Ready系统组件kubectl get pods -n kube-system所有 Pod Running跨节点 Pod 通信在两节点各起一个测试 Pod 互 ping能通Service 访问创建 Service 后从 Pod 内访问 ClusterIP能通DNS 解析Pod 内nslookup kubernetes.default解析成功跨节点调度创建多副本 Deployment副本分布在不同节点跨节点 Pod 通信的测试方法很直接在节点 A 上起一个带网络工具的 Pod在节点 B 上也起一个拿到各自 IP 后互相 ping。这一步能通说明 CNI 工作正常。DNS 解析测试则验证 CoreDNS 是否正常如果nslookup失败但 Pod 间 IP 能通问题多半在 CoreDNS 或 kube-proxy 上。4.2 高频故障速查表下面这张表是我这些年遇到频率最高的问题和对应的排查方向基本覆盖了 80% 的搭建故障现象可能原因排查命令节点一直 NotReady容器运行时异常 / 网络插件未就绪systemctl status containerd、journalctl -u kubelet节点加入失败token 过期 / 防火墙拦截 / 证书哈希错误kubeadm token list、检查端口连通性Pod 跨节点不通Pod 网段与 CNI 配置不一致对比 init 参数和 CNI YAML 中的网段CoreDNS CrashLoopBackOffService 网段冲突 / 网络插件未就绪kubectl logs -n kube-system coredns-podPod 频繁重启cgroup 驱动不一致 / 资源限制过紧检查 containerd 的 SystemdCgroup 配置API Server 无法访问6443 端口未放行 / 证书过期curl -k https://ip:6443/healthz拿Pod 跨节点不通这个说最常见的根因就是网段不一致。kubeadm init 时写了--pod-network-cidr10.244.0.0/16但 Flannel 的 YAML 里默认也是这个段看起来一致可如果当初 init 时手滑写成了10.245.0.0/16Flannel 还是按10.244分配地址两边对不上跨节点路由就断了。排查时先kubectl cluster-info dump | grep -i cidr确认集群实际网段再对比 CNI 配置。再比如节点加入失败里防火墙这一项很多人只记得放行 6443忘了 10250。工作节点的 kubelet 需要和控制平面的 API Server 通信10250 是 kubelet 的端口控制平面要能访问到它。如果中间有防火墙或安全组双向都要放行。4.3 几个容易被忽略的实操心得第一个心得初始化前先kubeadm config images pull把镜像拉下来。这一步能提前暴露镜像仓库不可达的问题而不是等到 init 跑到一半卡住。镜像拉取慢是初始化失败的头号原因提前拉能省很多等待和重试。第二个心得给控制平面节点打上污点容忍的注意。默认情况下控制平面节点带node-role.kubernetes.io/control-plane:NoSchedule污点普通 Pod 不会调度上去。学习环境资源紧张时可以去掉这个污点让控制平面也跑业务 Pod但生产环境不建议这么做控制平面节点的稳定性优先级更高。第三个心得kubeconfig 的权限管理。/etc/kubernetes/admin.conf里是集群管理员的凭证权限极大。日常操作建议创建权限受限的 ServiceAccount 和对应的 kubeconfig而不是人手一份 admin.conf。这个习惯在小团队里尤其重要避免误操作把集群搞挂。第四个心得节点扩容时新节点的基础配置要和现有节点完全对齐。我遇到过新加的节点因为内核参数没调、时间没同步加入后 Pod 在上面跑得时好时坏。扩容不是简单跑一条 join 命令环境一致性才是关键。5. 集群日常维护与扩展思路集群跑起来只是开始后面还有版本升级、节点扩缩容、证书轮换这些日常维护工作。这部分内容展开能写一整篇这里挑几个和多节点强相关的点讲透。5.1 节点扩缩容的正确姿势扩容就是新机器做好基础配置后执行 join 命令前面已经讲过。缩容则要先把节点上的 Pod 迁走再删除节点直接kubectl delete node会导致该节点上的 Pod 变成孤儿虽然控制器会重建它们但过程中会有服务中断。正确的缩容流程是先kubectl drain node --ignore-daemonsets --delete-emptydir-data把节点上的 Pod 驱逐走确认业务 Pod 都在其他节点重建完成后再kubectl delete node node最后在机器上kubeadm reset清理本地状态。drain的时候 DaemonSet 管理的 Pod 删不掉是正常的加--ignore-daemonsets忽略即可。5.2 版本升级的顺序与风险控制多节点集群升级要遵循固定顺序先升级控制平面再逐个升级工作节点。控制平面升级用kubeadm upgrade plan看可升级版本然后kubeadm upgrade apply执行。工作节点升级要先 drain再升级 kubelet 和 kubeadm 的包然后kubeadm upgrade node最后 uncordon 恢复调度。升级过程中最大的风险是版本跨度太大。Kubernetes 只支持逐个小版本升级比如 1.28 升 1.29 可以1.28 直接升 1.30 不行得先升到 1.29 再升 1.30。跨版本升级前一定要在测试环境演练一遍生产环境直接上风险太高。5.3 证书与 token 的生命周期管理集群里的证书和 token 都有有效期。kubeadm 生成的集群证书默认一年过期到期后 API Server 无法启动整个集群瘫痪。用kubeadm certs check-expiration可以查看所有证书的到期时间快到期时用kubeadm certs renew all续期。节点加入用的 bootstrap token 默认 24 小时过期这个前面提过。另外 kubelet 的客户端证书也会轮换如果轮换配置有问题节点运行一段时间后会突然 NotReady。这些生命周期相关的东西建议做成定期检查项而不是等出问题再救火。我在实际维护中养成了一个习惯给集群的证书到期时间设个日历提醒提前一个月处理。有次一个测试集群放了半年没管某天突然所有节点 NotReady查了半天才发现是证书过期这种问题排查起来特别费时间因为表象和网络故障很像。5.4 监控与日志的最小可用方案多节点集群没有监控就是睁眼瞎。最小可用方案是装 metrics-server 看资源使用再配一套日志收集。metrics-server 装起来简单kubectl apply官方 YAML 即可装完kubectl top nodes和kubectl top pods就能用。日志方面如果不想上重型方案可以先用kubectl logs加节点上的 journald 顶着但多节点环境下日志分散在各机器上查起来很痛苦。我的建议是至少部署一个轻量的日志聚合把各节点的容器日志集中到一个地方排查跨节点问题时效率会高很多。这套多节点集群的搭建流程我反复走过很多遍每次都能发现一些新的细节。最开始追求能跑起来后来追求跑得稳再后来追求出问题能快速定位。这三个阶段的关注点完全不同但基础都是把集群的骨架搭对、把每个参数背后的原因搞清楚。上面这些内容里网段规划、cgroup 驱动、版本一致性这三件事是我认为最值得反复确认的它们不出问题则已一出问题就是那种查一整天都找不到根因的类型。
阅读完成 · 觉得有帮助?