资讯动态

kubeadm 部署 Kubernetes 集群:系统配置、CNI 与排障

发布时间:2026/10/27 7:31:33 来源:尧图企业网站定制
kubeadm 这个工具我从 Kubernetes 1.9 时代就在用了一路看它从“实验性工具”变成现在创建集群的事实标准。这些年帮人排查过的集群没有一百也有八十绝大多数问题根本不是出在 kubeadm 本身而是出在准备阶段那一堆看着不起眼的系统配置上。这篇内容就是把这套东西完整拆一遍Kubernetes集群部署用 kubeadm 到底怎么走每一步为什么这么走哪些坑我踩过、你大概率也会踩。内容偏向有一定 Linux 基础的运维和开发同学纯新手也能跟着做但我建议你先在一台虚拟机上跑通一遍再去动生产环境因为里面涉及主机名、网卡、网段这类改起来很烦的东西。1. 先想清楚kubeadm 到底替你做了什么1.1 从手工装组件到声明式初始化的转变早年间搭一个 Kubernetes 集群是件体力活etcd 要单独装、证书要用 openssl 一条条签、apiserver 的启动参数能写满一屏、各个组件的 kubeconfig 得手动生成。任何一个参数写错报错信息还特别含糊排查起来能把人熬到半夜。kubeadm 做的事情本质上是把这套手工装配流程沉淀成了一套可复现的、带状态检查的自动化动作它把集群引导拆成了init、join、reset、upgrade这几个语义明确的子命令。理解这一点很关键因为它决定了你该用什么样的心态去用它。kubeadm 不是集群管理器它更像一个引导器bootstrapper它负责把控制平面的静态 Pod 拉起来、把证书签好、把 bootstrap token 发出去然后就基本退场了。集群跑起来之后的日常运维——扩缩容、滚动升级、故障自愈——是 kubelet、调度器和控制器在干活跟 kubeadm 没多大关系。所以你会看到一个现象集群装好之后kubeadm 几乎不再被调用除非你要加节点或者升级版本。明白了这个定位很多设计就顺理成章了。为什么它坚持用静态 Pod 跑控制平面因为这些组件必须在 apiserver 还没起来的时候就能运行用静态 Pod 是最省事的办法——kubelet 直接读/etc/kubernetes/manifests下的 YAML 文件拉起容器,不需要经过 apiserver 调度。为什么它生成的证书有效期是一年因为默认配置面向的是先跑起来这个目标长期运行的集群需要你自己规划证书轮换这一点后面我会专门讲。1.2 kubeadm 的能力边界哪些事它真的不管这是新手最容易误解的地方我必须掰开说清楚。kubeadm 不管容器运行时不管 CNI 网络插件不管存储不管 Ingress也不管监控。它只保证一件事控制平面能起来节点能注册进集群节点之间能通过你装的网络插件互通。剩下的一切都要你自己选、自己装。这个边界划得很清楚是有道理的。容器运行时在 1.24 版本之后彻底移除了 dockershimkubeadm 不再假装自己能管理运行时它只通过 CRI 接口跟运行时对话。网络插件更是如此Calico、Flannel、Cilium 的实现思路差异巨大kubeadm 强行内置一个反而是负担。所以你会看到kubeadm init跑完之后集群是NotReady状态这是正常现象不是装坏了——因为还没有 CNI节点没法分配 Pod IPkubelet 会一直报network plugin is not ready。我见过太多人在这里慌神反复kubeadm reset重装。其实你只要装个网络插件状态立刻就变Ready。所以我的建议是把装 CNI当成 init 流程的一个必选步骤写进你的部署文档里而不是当成事后补救。1.3 版本选型别追最新追可维护版本选择这件事我的经验是跟住上游的维护窗口但别踩在最新版的第一周。Kubernetes 的发布节奏是每年三个小版本每个小版本维护大约 14 个月。这意味着你选一个版本要保证在未来一年内它还在维护期内否则出了 CVE 你连补丁都拿不到。我一般的做法是选当前次新版本比如上游已经发到 1.31我就用 1.30 的最后一个补丁版。理由很实在新版首发那几周,插件生态经常跟不上,网络插件、存储 CSI、监控 agent 都可能存在兼容问题,而这些组件出问题排查成本极高。等补丁版出来,该修的 bug 修得差不多了,资料也多,遇到问题搜一下就有答案。还有一个必须强调的点kubeadm 和 kubelet、kubectl 的版本要保持一致或最多差一个小版本。kubeadm 自己会做版本校验如果你的 kubelet 版本比 kubeadm 高init 会直接拒绝。这个问题在包管理器里特别容易踩——因为kubelet装的时候如果不锁版本apt install kubelet会给你装最新版然后你的 kubeadm 是旧版直接卡住。正确做法是三个包同时指定版本安装# Debian/Ubuntu 系示例版本号自己替换 apt-get install -y kubelet1.30.4-1.1 kubeadm1.30.4-1.1 kubectl1.30.4-1.1 apt-mark hold kubelet kubeadm kubectlapt-mark hold这一步千万别省。我吃过亏某次例行apt upgrade把 kubelet 从 1.29 顶到了 1.30重启之后节点直接NotReady因为 kubelet 的配置格式在新版本里有变化。生产环境上节点批量掉线的那一刻心跳是真的会飙升。2. 环境准备那些容易在第三天爆发的细节2.1 主机基础配置清单环境准备这一节看似枯燥但我可以负责任地说集群上线前三天的故障有八成出自这里。我习惯把所有节点包括控制平面和工作节点的基础配置整理成一张清单逐项打勾绝不凭记忆。先说要改的东西。主机名必须唯一且能被解析——如果你的集群里有两台机器都叫localhostkubelet 注册节点时就会互相顶掉日志里报Node ... already exists。改主机名的同时把/etc/hosts一起改了让所有节点都能通过主机名互相 ping 通这样你在排查时不用记 IP。时间同步是另一个高频雷区。Kubernetes 的证书校验、etcd 的租约、ServiceAccount token 的过期判断都依赖时间。节点之间时间差超过几秒你可能什么错都看不到就是x509: certificate has expired or is not yet valid或者节点莫名其妙被踢出集群。我建议所有节点统一指向内部的时间源用chrony或者systemd-timesyncd都行关键是要验证timedatectl status # 关注 System clock synchronized: yes chronyc sources -v # 如果用 chrony防火墙这块要提前规划好端口别等装完了再回头开。控制平面需要 6443apiserver、2379-2380etcd、10250kubelet API、10257controller-manager、10259scheduler工作节点需要 10250 和 30000-32767NodePort 范围。如果内部有网络策略或者安全组这些端口要提前放通。我见过一个案例集群全装好了节点也 Ready 了但kubectl logs和kubectl exec全部超时查了两小时发现是 kubelet 的 10250 端口被拦了。2.2 内核参数与模块加载这部分是不配也能跑但跑着跑着会出问题的典型。Kubernetes 的网络和 iptables 转发依赖几个内核模块和 sysctl 参数缺了它们最常见的结果是Pod 之间 ping 不通或者 Service 的 ClusterIP 完全没响应。必须加载的模块是overlayOverlayFS容器镜像分层存储要用和br_netfilter网桥的 iptables 规则转发。加载方式我推荐写进/etc/modules-load.d/k8s.conf保证重启后还在cat /etc/modules-load.d/k8s.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfiltersysctl 参数里net.bridge.bridge-nf-call-iptables和net.bridge.bridge-nf-call-ip6tables必须为 1否则走网桥的流量不会经过 iptablesService 的 DNAT 规则就失效了——这个现象特别迷惑人因为 Pod 直接访问没问题一旦通过 Service 访问就卡死。net.ipv4.ip_forward也要开这是节点做转发的前提。同样是写文件到/etc/sysctl.d/cat /etc/sysctl.d/k8s.conf EOF net.bridge.bridge-nf-call-iptables 1 net.bridge.bridge-nf-call-ip6tables 1 net.ipv4.ip_forward 1 EOF sysctl --system注意br_netfilter没加载的时候sysctl里的 bridge 相关参数会报no such file or directory。如果看到这个错先lsmod | grep br_netfilter确认模块是否加载成功再回头执行sysctl --system。还有一个容易被忽略的是文件句柄数和进程数限制。默认的 1024 在高密度 Pod 的节点上明显不够会导致容器启动失败或者连接被拒绝。我会在/etc/security/limits.conf里把nofile调到 65536 甚至更高nproc放开。这不是 Kubernetes 特有的要求但它是节点稳定运行的底色。2.3 关闭 swap 与 SELinux不是迷信是有原因的swapoff -a这一步几乎人人都会做但很多人不知道为什么。原因在于 kubelet 的调度是基于内存请求量做的如果节点开了 swapPod 实际用的内存可能超过 request 而被换到磁盘上这会让内存请求这个契约失效调度器判断失准最终结果是节点内存被打爆而调度器浑然不觉。所以 kubelet 干脆强制要求关 swap不关就不启动。注意swapoff -a只是临时生效要永久关闭得注释掉/etc/fstab里的 swap 行或者用swapoff配合 systemd 的处理。别忘了验证free -h输出里 Swap 那一行应该是 0。SELinux 这块我的态度比较务实能关就关。虽然新版本在 SELinux enforcing 下也能跑需要给容器打正确的 label但排查问题时多一层安全模块的干扰成本远高于收益。在测试和内网环境下我会在/etc/selinux/config里设成SELINUXdisabled并重启。如果公司安全基线不允许关那必须在装运行时之前就把 SELinux 策略配好注意顺序——先装运行时再改 SELinux会出现容器起不来的兼容问题。2.4 容器运行时containerd 的配置细节决定成败1.24 之后 dockershim 被移除现在的事实标准是 containerd或者 CRI-O。我这边统一用 containerd因为它的架构清晰、配置项少、跟 CRI 的贴合度好。安装 containerd 建议走官方仓库别用系统自带的版本——Ubuntu 仓库里的 containerd 往往版本偏旧config.toml的字段跟文档对不上。装完之后最关键的一步是生成配置并开启 systemd cgroup 驱动mkdir -p /etc/containerd containerd config default /etc/containerd/config.toml # 修改 SystemdCgroup false 为 true sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml systemctl restart containerd systemctl enable containerd为什么这一步不能省因为现代 Linux 发行版用 systemd 管理 cgroup如果 containerd 用 cgroupfs 而 kubelet 用 systemd就会出现两套 cgroup 管理器打架的局面。表现出来的症状是节点资源统计不准、Pod 频繁被 OOMKill 但节点内存明明还有富余甚至 kubelet 直接报failed to get container info。这个问题的隐蔽之处在于它不是必现的压测的时候才会冒出来。顺便把sandbox_image的地址也换掉默认指向的是公共仓库内网环境经常拉不动。[plugins.io.containerd.grpc.v1.cri] sandbox_image 你的内网仓库/pause:3.9另外containerd 的SystemdCgroup设置好之后kubelet 那边也必须是systemd。kubeadm 1.22 之后默认就是 systemd 了但如果你是手工改过 kubelet 配置记得检查/var/lib/kubelet/config.yaml里的cgroupDriver字段。两边不一致节点就一定不稳。3. 动手用配置文件初始化控制平面3.1 为什么一定要用配置文件而不是命令行参数kubeadm init支持两种用法塞一长串命令行参数或者给一个 YAML 配置文件。我强烈建议用配置文件理由有三个。第一可版本化这个文件可以进 Git集群怎么搭的有据可查换个人接手也能看懂。第二可扩展后面要改podSubnet、要加extraArgs、要配置imageRepository配置文件里加一行就行命令行参数会越滚越长。第三可复现命令行参数在 shell 里没有记录出问题时你根本不知道当时写了什么。一个够用的配置大概长这样apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 10.10.0.11 bindPort: 6443 nodeRegistration: criSocket: unix:///run/containerd/containerd.sock imagePullPolicy: IfNotPresent kubeletExtraArgs: - name: node-labels value: node-rolecontrolplane --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: v1.30.4 imageRepository: registry.internal.example.com/k8s networking: serviceSubnet: 10.96.0.0/12 podSubnet: 10.244.0.0/16 dnsDomain: cluster.local controlPlaneEndpoint: 10.10.0.11:6443 apiServer: certSANs: - 10.10.0.11 - api.k8s.internal controllerManager: extraArgs: - name: bind-address value: 0.0.0.0 --- apiVersion: kubelet.config.k8s.io/v1beta1 kind: KubeletConfiguration cgroupDriver: systemd几个字段值得单独说。advertiseAddress是 apiserver 对外宣告的地址必须是其他节点能访问到的 IP不能填 127.0.0.1。我在多云环境下见过填了内网回环地址导致所有工作节点 join 失败的案例日志报connection refused但控制平面上kubectl一切正常非常迷惑。certSANs要提前把所有可能访问 apiserver 的地址和域名都写进去包括负载均衡的 VIP、内部 DNS 名。证书签完之后再想加 SAN只能重新签很麻烦。podSubnet和serviceSubnet绝对不能跟你的内网网段重叠。这个错误我犯过一次公司内网用的是10.0.0.0/8我随手写了podSubnet: 10.244.0.0/16结果集群起来后凡是 Pod IP 落在10.244.x.x的访问内网10.244.x.x的机器全部失败——因为路由表里这个网段被指向了 cni0 网桥。判断方法很简单拿到内网网段之后用ip route和ipcalc算一下有没有交集。我会在部署文档里明确写出Pod 网段、Service 网段、节点网段三者必须无交集这条约束排在第一位。imageRepository建议指向内部仓库。控制平面需要的镜像有十来个apiserver、controller-manager、scheduler、etcd、pause、coredns、kube-proxy如果走公共源节点数量一多拉镜像就是个反复失败的过程。稳妥的做法是先在能出网的机器上kubeadm config images list拿到完整列表再kubeadm config images pull拉下来然后用内部同步机制推到私有仓库。3.2 init 过程与输出解读配置文件准备好一条命令下去kubeadm init --config kubeadm-config.yaml --upload-certs --v5--upload-certs是为后续加控制平面节点准备的它会把证书加密后存到集群里的 Secret其他控制平面节点 join 时直接取用省去手工拷贝。init 过程有几个阶段输出里会依次打印preflight、certs、kubeconfig、kubelet-start、control-plane、etcd、upload-config、mark-control-plane、bootstrap-token、addon。我一般会重点盯三个地方一是 preflight 阶段。kubeadm 会做一堆检查比如端口是否被占用、swap 是否关闭、容器运行时是否可用、内核参数是否达标。有些检查只是 warning比如SystemVerification报 cgroup 版本可以忽略但FileExisting-conntrack或者Swap报 error 就必须处理那是硬性阻断。二是 etcd 和 control-plane 阶段的等待时间。健康节点的机器上通常一两分钟如果卡在waiting for the kubelet to boot up the control plane as static Pods超过五分钟基本可以确定是镜像拉不下来或者 kubelet 和容器的 cgroup driver 不一致。这时候别干等另开一个窗口crictl ps -a看看容器状态journalctl -u kubelet -n 100看 kubelet 日志问题一目了然。三是最后输出的kubeadm join命令。那串 token 和 hash 一定要存好虽然可以用kubeadm token create --print-join-command重新生成但每次生成都得在控制平面上操作多一步麻烦。3.3 kubeconfig 落地与日常操作习惯init 成功后会提示你把/etc/kubernetes/admin.conf拷到$HOME/.kube/config。这里我分享一个小习惯我会在控制平面上同时配好三样东西省去后面反复折腾。第一是 kubectl 补全写进~/.bashrcsource (kubectl completion bash) alias kkubectl complete -o default -F __start_kubectl k第二是给常用命名空间起别名。集群多起来之后kubectl get pod -n kube-system敲到手酸我会定义kskubectl -n kube-system这类快捷方式。第三是安装 kubectx/kubens 这类上下文切换工具。如果你手上有多个集群不带上下文切换工具的kubectl会在错误的集群上执行删除命令——这种事故我见过不止一次。3.4 网络插件选型与网段对齐回到最初的边界问题kubeadm 装完 CNI 之前节点是NotReadyCoreDNS 会一直Pending。这时候要装网络插件。我一般在新集群上先用 Flannel 跑通因为它简单、依赖少几分钟就能让集群 Ready生产环境我倾向 Calico因为它的 NetworkPolicy 实现更完整BGP 模式在大规模下更省心。选型的判断标准我整理成一张表方便你对着业务需求挑方案实现方式适用场景主要注意点FlannelVXLAN 覆盖网络中小规模、快速验证、对网络策略要求不高默认不支持 NetworkPolicy需配合 Calico 策略CalicoBGP 或 IPIP生产环境、需要网络策略隔离默认 podSubnet 是 192.168.0.0/16必须改CiliumeBPF大规模、可观测性要求高、想省掉 kube-proxy对内核版本有要求老内核体验差装 Calico 时最容易踩的坑是网段。它默认的CALICO_IPV4POOL_CIDR是192.168.0.0/16跟你 kubeadm 配置里的podSubnet十有八九对不上。对不上的结果不是报错而是集群看起来正常节点也 Ready但跨节点 Pod 通信全部不通——因为 Calico 分配的 Pod IP 落在了一个 kubeadm 根本没告知 kubelet 的网段里。我一般直接在清单文件里把CALICO_IPV4POOL_CIDR改成跟podSubnet一致然后再 apply。还有一个细节如果你的节点有多块网卡一定要显式指定IP_AUTODETECTION_METHOD否则 Calico 可能选到一张跟集群无关的网卡比如某个虚拟网卡或者管理网卡。指定方式可以按网卡名匹配IP_AUTODETECTION_METHODinterfaceeth0。这个坑在多网卡环境下是必踩的症状是部分节点之间 Pod 不通calicoctl node status显示 BGP 邻居没建立。装完之后验证三件事kubectl get nodes全部 Ready、kubectl get pod -n kube-system里 coredns 变成 Running、随便起两个分布在不同节点的 Pod 互相 ping 通。三个都过了才算网络这块真的通了。3.5 加入工作节点token 与证书的配合工作节点加入的命令 kubeadm init 结束时会打印长这样kubeadm join 10.10.0.11:6443 --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:xxxxxxxxxxxxtoken 默认有效期 24 小时过期了就得重新生成。这个设计我最初觉得麻烦后来理解了bootstrap token 本质是一次性凭证长期有效等于给集群开了一个后门。过期后重新生成一条命令就行kubeadm token create --print-join-command加入前要确认工作节点跟上面 2.1、2.2、2.4 节的配置完全一致——同样的容器运行时、同样的内核参数、同样的 cgroup driver。我遇到过工作节点偷懒只装了 containerd 没配SystemdCgroupjoin 成功了但节点一直NotReady日志里是misconfigured cgroup driver。所以我的做法是把节点初始化写成一个脚本所有节点跑同一个脚本杜绝这台机器上多做了点、那台机器上漏了点的情况。还有一个可选的细节给工作节点打标签。比如kubectl label node node-02 node-role.kubernetes.io/worker这样kubectl get nodes的 ROLES 列就有区分了。标签这事不着急但标签体系越早规划越好后面做污点调度、亲和性调度都靠它。4. 验证与加固集群交付前的自检清单4.1 一次性体检别等出问题才查集群装完我会跑一遍固定动作大概十几条命令全部通过才算交付。这套动作我整理成了下面这张表你直接照着执行即可检查项命令期望结果节点状态kubectl get nodes -o wide全部 ReadyIP 正确系统组件kubectl get pod -n kube-system全部 Running无重启集群信息kubectl cluster-info能连上 apiserver服务发现kubectl run -it --rm dnsutils --imageregistry.internal/dnsutils -- nslookup kubernetes能解析跨节点通信两个不同节点的 Pod 互 ping通网络策略创建 deny-all 策略后验证隔离生效证书有效期kubeadm certs check-expiration剩余时间大于半年节点数量超过三个之后我会再加一条批量检查所有节点的 kubelet 版本和配置是否一致。kubectl get nodes -o custom-columnsNAME:.metadata.name,VERSION:.status.nodeInfo.kubeletVersion一眼就能看出有没有版本漂移的节点。版本漂移在滚动升级期间特别危险不同版本的 kubelet 行为差异会导致调度和网络出现难以复现的问题。4.2 证书有效期一年之后的那次故障这是 kubeadm 最著名的坑。用 kubeadm 装出来的集群客户端证书默认有效期一年控制平面组件间的证书也是。很多团队装完就忘了一年后的某个早上整个集群失联kubectl报证书错误业务全挂。根因是kubeadm在升级版本时才会自动续期证书而很多团队不升级就不管。我的建议是两件事都做把证书到期时间做成监控项提前一个月告警同时准备好手动续期的操作路径。# 查看所有证书到期时间 kubeadm certs check-expiration # 全部续期 kubeadm certs renew all # 续期后必须重启控制平面组件否则加载的还是旧证书续期之后控制平面的静态 Pod 不会自动重载证书这一点必须记住。重启方式有多种最直接的是把/etc/kubernetes/manifests下的 YAML 临时移走几秒再移回来kubelet 会重建 Podcd /etc/kubernetes/manifests mkdir /tmp/bak mv *.yaml /tmp/bak sleep 20 mv /tmp/bak/*.yaml .这个操作对生产环境有短暂影响建议在维护窗口做。操作后立即kubectl get pod -n kube-system确认组件恢复再用kubeadm certs check-expiration验证新的到期时间。4.3 收紧暴露面把不该开的关掉kubeadm 默认生成的配置里controller-manager 和 scheduler 的监听地址是 127.0.0.1apiserver 的--insecure-port早就不存在了这些都没问题。但还有几个地方我会顺手收紧。一是 kubelet 的匿名访问。kubelet 默认开启了匿名认证anonymous.enabled: true意味着只要能连上 10250 端口不带凭证也能读到一些信息。开源的 kubelet 配置检查工具基本都会提这一项。关闭方式是在 kubelet 配置里设置authentication.anonymous.enabled: false同时配置authorization.mode: Webhook让鉴权走 apiserver。二是 apiserver 的审计日志。默认没开审计出了安全事件连谁干了什么都不知道。我会加一个基础的审计策略文件至少记录 metadata 级别的操作日志量可控出问题时能追溯。三是 NodePort 范围。默认 30000-32767 全部开放如果节点有公网 IP这相当于把一堆服务直接暴露出去。规划时明确哪些服务需要 NodePort其余走 Ingress 或者 LoadBalancer能少暴露一点是一点。5. 高频故障排查实录5.1 镜像类问题卡在 init 不动症状很好认kubeadm init卡在waiting for the kubelet to boot up the control plane或者 join 时卡在拉 pause 镜像。第一步永远是看 kubelet 日志和容器运行时状态journalctl -u kubelet -n 200 --no-pager | grep -i -E pull|image|failed crictl images | grep pause crictl ps -a排查思路分三层。如果日志里是dial tcp ... i/o timeout那是网络问题检查节点能不能访问你的镜像仓库如果是unauthorized或者manifest unknown那是仓库认证或者镜像名不对检查imageRepository拼写如果是x509: certificate signed by unknown authority说明私有仓库用的是自签证书需要把 CA 放到节点的/etc/containerd/certs.d/目录下并在 containerd 配置里配好。我的经验是集群部署前先把镜像全部准备好kubeadm config images pull在每台节点上都执行一遍成功之后再 init。这样 init 阶段不会因为镜像问题卡住出了问题也能立刻定位到是准备阶段漏了什么。5.2 网络类问题节点 Ready 但服务不通这类问题最耗时间因为表面上一切正常。我的排查顺序是固定的先看 CNI Pod 是否全 Running再看节点上有没有 cni0 网桥和对应的路由然后看 iptables 规则是否生成最后抓包。# 1. CNI 组件状态 kubectl get pod -n kube-system -o wide | grep -E calico|flannel|coredns # 2. 网桥与路由 ip link show cni0 ip route | grep 10.244 # 3. iptables 规则 iptables-save | grep -c KUBE-SERVICES如果cni0网桥不存在说明 CNI 插件没跑起来回头看 CNI 的日志。如果 iptables 里KUBE-SERVICES链是空的说明 kube-proxy 有问题看它的日志和配置模式iptables 还是 ipvs。一个特别隐蔽的场景节点上装过别的容器平台留了残留的 iptables 规则和网桥新的 CNI 装上去之后规则冲突。这种问题只能靠干净的系统来规避我现在的做法是所有节点一律用最小化安装装完立刻做系统快照出问题回滚。5.3 常见问题速查表现象大概率原因处理方式init 报Swap is enabled未关闭 swapswapoff -a并注释 fstabinit 报port 6443 is in use之前残留的 apiserver 进程kubeadm reset后重启节点 NotReady日志报network plugin is not ready未装 CNI安装 Calico 或 FlannelCoreDNS 一直 PendingCNI 未就绪或有污点检查 CNI检查节点污点join 报x509: certificate has expired控制平面证书过期续期证书并重启组件join 报token id ... not foundtoken 过期或已被清理kubeadm token create --print-join-commandPod 跨节点不通podSubnet 与 CNI 网段不一致统一网段后重建网络节点反复重启后 NotReadykubelet 配置与 containerd cgroup 不一致两边都设为 systemdkubectl exec超时kubelet 10250 被防火墙拦截放通端口加入节点后调度不到控制平面有 NoSchedule 污点单节点场景需手动去污点关于最后一条我要多说一句。单节点集群比如开发测试环境默认是不允许调度业务 Pod 到控制平面的因为 init 时打了node-role.kubernetes.io/control-plane:NoSchedule污点。去掉的方式是kubectl taint nodes --all node-role.kubernetes.io/control-plane-结尾那个减号表示移除。生产环境千万别去掉控制平面节点跑满业务 Podapiserver 抢不到 CPU 的时候整个集群就雪崩了。5.4 reset 与重建干净卸载比装更重要kubeadm reset这个命令我要重点说因为很多人 reset 之后重装还是失败原因是没清干净。kubeadm reset会清理 kubelet 配置、证书、静态 Pod 清单但它不会清理 CNI 留下的网桥、路由和 iptables 规则也不会清理容器运行时里的镜像和容器。我习惯的完整清理流程是这样的kubeadm reset -f rm -rf /etc/cni/net.d /var/lib/cni /var/lib/etcd $HOME/.kube ip link delete cni0 2/dev/null ip link delete flannel.1 2/dev/null iptables -F iptables -t nat -F iptables -t mangle -F iptables -X ipvsadm --clear 2/dev/null systemctl restart containerd kubelet注意iptables -F会清掉节点上所有 iptables 规则。如果这台机器上还跑着别的服务比如 Docker 或者防火墙脚本执行前一定要确认。这是我在测试环境养成的顺手习惯生产环境请谨慎最好只精确删除跟 K8s 相关的链。清理完再重装成功率就高很多。我建议把这个流程写成脚本存起来因为重新搭集群这件事一年总会遇上那么几次。6. 高可用与平滑升级、节点维护6.1 三控制平面拓扑与负载均衡单控制平面的集群只适合测试和边缘场景生产环境必须做高可用。标准拓扑是三个控制平面节点 若干工作节点 一个指向三个 apiserver 的负载均衡地址VIP 或者 DNS 轮询。关键配置是controlPlaneEndpoint它必须填那个 VIP 或者负载均衡域名不能填某一台控制平面的 IP。原因是这个值会被写进所有节点的 kubeconfig 和 kubelet 配置一旦写死某台机器的 IP那台机器一挂所有节点都连不上 apiserver集群照样雪崩。三个节点加入的方式是第一个节点正常kubeadm init --upload-certs --config ...另外两个节点用kubeadm join ... --control-plane --certificate-key xxx。--certificate-key会在 init 的输出里给出有效期两小时过期了用kubeadm init phase upload-certs --upload-certs重新生成。etcd 这边kubeadm 默认部署的是堆叠式 etcd和控制平面同机部署。三个节点的情况下etcd 会自动组成三成员集群容忍一个节点故障。这个方案简单缺点是控制平面和 etcd 抢资源节点规格要留够余量——我会给控制平面节点至少 4 核 8Getcd 单独占一块盘。如果你的规模很大那就得考虑外置 etcd 集群那是另一个话题了。6.2 版本升级小版本一步步走升级这件事最容易犯的错是跨版本。比如从 1.28 直接升到 1.30kubeadm 会直接拒绝因为版本倾斜策略只允许升一个小版本。正确路径是 1.28 → 1.29 → 1.30一步都不能跳。升级顺序也是固定的先升控制平面再逐个升工作节点。控制平面用kubeadm upgrade plan看能升到哪个版本然后kubeadm upgrade apply v1.29.x。注意upgrade apply只升控制平面组件和证书不会升 kubeletkubelet 和 kubectl 要用包管理器单独升升完重启。工作节点升级分两步先kubeadm upgrade node在工作节点上执行再把节点上的 Pod 驱逐出去kubectl drain node-02 --ignore-daemonsets --delete-emptydir-data升级 kubelet然后uncordon放回集群。这个流程一本正经照做就行我唯一的补充建议是一次只升一个节点升完后观察十分钟确认业务 Pod 起来且健康了再动下一个。批量升级看着快一旦出问题是全集群范围的事故。drain 的时候要注意PodDisruptionBudget。如果某个服务的 PDB 设置是minAvailable: 2而它只有两个副本drain 会一直卡住不动不是命令有问题是 PDB 在保护它。这种情况要么先扩容要么临时调整 PDB别硬来。6.3 节点下线与日常维护节点要下线换硬件、缩容时标准动作是drain→ 确认 Pod 都迁移走 →kubeadm reset→ 从集群删除节点对象。顺序反了会留下一堆处于Unknown状态的 Pod 和僵尸节点记录清理起来很烦。kubectl drain node-03 --ignore-daemonsets --delete-emptydir-data --force kubectl delete node node-03 # 在 node-03 上执行 kubeadm reset 和清理--ignore-daemonsets是必须的因为每个节点都有 DaemonSet 的 PodCalico、kube-proxy、日志采集 agent不忽略会报错。--delete-emptydir-data是确认可以删除使用 emptyDir 的 Pod这个参数不加会拒绝执行属于安全闸门。日常维护里我最想强调的一点是定期跑一遍集群健康检查别等告警。我会每周看三样东西——kubectl get componentstatuses虽然已经废弃但kubectl get --raw /healthz仍然好用、etcd 的db size和fsync延迟、以及节点上的磁盘使用率。etcd 的 db 超过 8G 就该考虑碎片整理了etcdctl defrag磁盘满了会让整个集群进入只读状态那时候再处理就被动了。最后分享一个我在实际搭建过程中养成的小习惯可能比上面任何一条配置都值钱把整个部署过程写成脚本从系统初始化到 CNI 安装到健康检查一气呵成。原因不是懒而是 Kubernetes 集群的搭建细节太多靠人记必然出错而脚本能保证每次搭出来的集群是一模一样的。我第一次搭出稳定运行的集群之后花了半天把全过程脚本化后面每次新环境部署二十分钟搞定而且因为过程可复现出问题时能快速定位到是哪一步的差异导致的。这个投入产出比比你在网上抄十份配置文件都划算。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑