kubeadm部署Kubernetes集群实战与NotReady排查 Kubernetes集群部署这件事第一次用kubeadm走通的时候我印象很深不是因为顺利而是因为从执行kubeadm init到worker节点真正Ready整整卡了大半天。那会儿我以为装个容器运行时、敲几条命令就能成结果在cgroup驱动、CNI网段、kubelet证书几个地方来回翻车。后来带过几批新人发现大家踩的坑高度重合环境检查省了、参数随手抄、报错不看日志直接搜答案。这篇就把我用kubeadm做Kubernetes集群部署的完整链路拆开讲一遍从三台机器的环境摸底到init之后的每一项参数含义再到集群起来之后的补强动作。内容偏向动手实操适合刚接触集群搭建、手里有虚拟机或物理机可以做实验的读者也适合已经在用但想弄清楚每个参数为什么这么填的同学。全程以最小可用集群为起点把每个为什么讲透保证你能照着复现而不是只会复制粘贴命令。1. 为什么我建议先用kubeadm啃下集群初始化这块硬骨头很多人一上来就想用托管服务或者一键脚本把集群拉起来图省事。这本身没错生产环境也确实该这么做。但如果你连kubeadm init背后干了什么都不知道后面集群一出问题就只能干瞪眼。kubeadm的价值恰恰在于它把集群初始化这件复杂的事拆成了可见的、可解释的步骤生成证书、写静态Pod清单、拉起控制平面组件、配置kubeconfig、输出join命令。每一步你都能看到、都能干预出问题也知道去哪找。1.1 kubeadm到底帮你做了什么没做什么先说清楚边界。kubeadm负责的是把控制平面和节点引导起来它不负责装容器运行时不负责装CNI网络插件不负责给你配负载均衡器也不管你的机器内核参数调没调对。这几件事它默认你自己搞定或者它只做最基本的检查然后报错退出。我见过不少人以为kubeadm init能一步到位结果卡在kubelet起不来本质上就是容器运行时没配好。换句话说kubeadm更像一个装配工而不是装修队。它把组件摆到位、接上线但地基操作系统和管线网络、运行时得你先打好。理解这一点你在排查问题时就不会漫无目的。比如节点一直NotReady八成不是kubeadm的锅而是CNI没装或运行时没通。还有一点值得强调kubeadm做的是最佳实践引导它生成的证书有效期、控制平面组件的默认参数、etcd的部署方式都是社区公认的稳妥选择但它不会替你做高可用拓扑也不会帮你规划地址段。这些决策必须你自己在init之前想清楚填错参数后面返工很麻烦。1.2 单控制平面起步是更稳的学习路径我不建议新手一上来就搞三控制平面高可用。原因很实际高可用需要额外的负载均衡器哪怕是keepalived加haproxy这种软件方案需要--control-plane-endpoint指向一个虚拟地址需要处理证书SAN里包含这个地址。任何一环配错你会同时面对集群起不来和不知道是哪一层的问题两个困境。先用一台控制平面加一到两台worker把单控制平面集群跑通把所有基础概念吃透节点如何注册、Pod网络如何打通、Service如何做负载均衡、证书放在哪。等你对这套流程熟了再把控制平面扩成三个你会发现高可用只是多了个前置负载均衡和--control-plane-endpoint参数其余逻辑完全一致。我个人的经验是单控制平面跑通再上高可用整体成功率比直接硬上高可用高得多。1.3 实验环境的最低配置与资源规划资源这块别太抠。控制平面节点至少2核4Gworker节点至少2核2G磁盘每台给20G以上。低于这个配置控制平面组件尤其是etcd和apiserver会因为资源紧张频繁抖动你会误以为是配置问题实际是机器扛不住。系统盘最好用SSDetcd对磁盘IO很敏感机械盘在高频写入下延迟会明显拖慢整个控制平面。网络规划要提前定好三件事节点之间能互相通信建议同一网段或至少路由可达、Pod网段不能和节点网段重叠、Service网段也不能和节点网段重叠。这三条如果冲突后面会出现Pod能起但访问不了这类诡异问题。我一般的习惯是节点用192.168.10.0/24Pod用10.244.0.0/16Service用10.96.0.0/12这套组合几乎不会和常见内网冲突。角色建议配置数量说明控制平面2核4G20G磁盘1实验/3高可用跑apiserver、etcd、controller-manager、schedulerworker2核2G20G磁盘1-3承载业务Pod网络节点同网段Pod/Service网段不冲突-三网段必须互不重叠2. 三台机器的环境摸底从内核参数到容器运行时的逐项排查环境准备这一步最容易被跳过也最容易埋雷。我总结下来80%的init失败或节点NotReady都能在这一步提前掐掉。下面几项是我每次都必查的按顺序做完基本不会有环境级问题。2.1 主机名、hosts与时间同步这些不起眼但会咬人的细节主机名必须唯一且符合DNS命名规范小写字母、数字、短横线别用下划线和大写。我见过有人把三台机器都叫localhost结果节点注册时互相覆盖排查了半天。改完主机名记得同步更新/etc/hosts把每台机器的IP和主机名对应上控制平面和worker都要能互相解析。时间同步更要命。Kubernetes的证书校验、token有效期、etcd的raft选举都对时间敏感节点间时间差超过几分钟就可能出现证书校验失败或节点无法加入。实验环境里手动用chronyc或timedatectl配一个可靠的时间源即可至少保证所有节点时间误差在几十秒以内。# 设置主机名每台机器分别执行 hostnamectl set-hostname k8s-master01 # 校验解析 cat /etc/hosts ping -c 2 k8s-master01 # 检查时间同步状态 timedatectl status这三条命令我每次开局必跑。ping那一步看着傻但能立刻暴露hosts没写对的问题。时间同步用timedatectl status看System clock synchronized是否为yes不是的话先解决这个再往下走。2.2 关闭swap与内核模块加载的实际影响kubelet默认不允许swap开启这不是洁癖而是调度器的内存计算模型假设节点没有swap开了swap会导致Pod内存超限时的行为不可预测。所以要swapoff -a并把/etc/fstab里的swap行注释掉保证重启后也不生效。内核模块方面需要加载br_netfilter和overlay。前者让iptables能处理桥接流量Pod跨节点通信依赖它后者是overlayfs文件系统的支撑容器镜像分层存储要用。同时要打开net.bridge.bridge-nf-call-iptables和net.ipv4.ip_forward这两个内核参数否则Pod的流量出不了节点。# 关闭swap swapoff -a sed -ri s/.*swap.*/#/ /etc/fstab # 加载模块并持久化 cat /etc/modules-load.d/k8s.conf EOF overlay br_netfilter EOF modprobe overlay modprobe br_netfilter # 内核参数 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 --systemsysctl --system执行后可以用sysctl net.ipv4.ip_forward确认值是不是1。这一步如果漏了最典型的症状是Pod能拿到IP但ping不通别的节点上的Pod很多新手会误以为是CNI的问题其实是ip_forward没开。2.3 容器运行时选型containerd与Docker的关系梳理从Kubernetes 1.24开始内置的dockershim被移除kubelet不再直接对接Docker。现在主流选择是直接用containerd它更轻、启动更快、和kubelet的CRI接口对接更顺。如果你以前习惯用Docker命令用containerd之后可以用crictl替代功能上完全够用。安装containerd之后最关键的一步是改配置文件里的cgroup驱动。新版containerd默认用systemd作为cgroup驱动而kubelet这边也必须配成systemd两边必须一致。不一致的典型症状是kubelet反复重启日志里报failed to run Kubelet: misconfiguration: kubelet cgroup driver: cgroupfs is different from docker cgroup driver: systemd。这个坑我踩过不止一次。# 生成默认配置 containerd config default /etc/containerd/config.toml # 修改关键项cgroup驱动改为systemdsandbox镜像改为可用地址 sed -i s/SystemdCgroup false/SystemdCgroup true/ /etc/containerd/config.toml systemctl restart containerd systemctl enable containerd # 验证 systemctl status containerd改完记得重启并设开机自启。另外config.toml里的sandbox镜像pause镜像地址如果拉取困难建议换成企业内部仓库地址或提前在每台机器上预拉取。这是环境准备里比较繁琐但很值得做的一步。2.4 kubelet、kubeadm、kubectl的版本对齐原则这三个组件的版本关系有个硬规则kubeadm和kubelet的版本必须一致或非常接近kubectl可以比集群版本低一个小版本但不能高。比如集群是1.28kubectl用1.28或1.27都行用1.29就可能出现兼容问题。安装时用包管理器固定版本避免装到最新版导致和预期不符。以Debian系为例添加仓库后执行apt-get install -y kubelet1.28.x-* kubeadm1.28.x-* kubectl1.28.x-*把版本号钉死。装完用kubeadm version和kubelet --version确认一致。还有个小细节apt-mark hold把这三个包锁住版本防止后续apt upgrade无意中升级。我见过一次因为系统自动更新把kubelet升了半代结果和kubeadm版本对不上init直接报错。锁版本这个动作花不了十秒能省掉很多麻烦。3. kubeadm init之前必须想清楚的几个参数kubeadm init这一条命令里参数填错比环境没配好更难排查因为错误往往是看起来像集群问题的形态出现。下面这几个参数是我认为最需要提前想清楚的每个都说清楚取值逻辑。3.1 --pod-network-cidr与CNI插件的地址段配合这个参数不是随便填的它必须和你选的CNI插件默认网段一致或者你改CNI配置去适配它。Calico默认用192.168.0.0/16Flannel默认用10.244.0.0/16。如果你填了10.244.0.0/16却装CalicoCalico会按自己的192.168.0.0/16去分配两边对不上Pod虽然能起来但跨节点通信会出问题。我的习惯是统一用10.244.0.0/16然后装Flannel时不用改配置装Calico时把CALICO_IPV4POOL_CIDR改成一致。这样无论后面换哪个插件地址段都是明确的。填之前务必确认这个网段和节点网段不重叠重叠的后果是Pod IP和节点IP撞车路由会乱。另外这个参数一旦定了后面改起来很麻烦因为已经分配的Pod IP都在这个池子里。所以建议在集群创建前就规划好不要抱着先随便填后面再改的心态。3.2 --apiserver-advertise-address在单机与多网卡环境下的取值这个参数告诉集群apiserver对外暴露的地址是哪个。单网卡环境直接填控制平面节点的IP就行。多网卡环境就要小心了如果你机器上有内网管理网卡和业务网卡填错了会导致worker节点join时连不上apiserver。判断方法很简单想清楚worker节点会通过哪个网段访问控制平面就填那个网段对应的IP。不确定的话ip addr看一下有几块网卡、各自在什么网段然后选worker能路由到的那个。我一般会在init之后立刻从worker上telnet或curl一下6443端口确认网络通再继续避免join到一半失败还要回滚。3.3 --control-plane-endpoint与高可用雏形的关系单控制平面实验用不上这个参数但如果你打算后面扩成高可用我强烈建议一开始就加上。这个参数的值是一个稳定的DNS名或虚拟IP指向控制平面的负载均衡入口。即使现在只有一个控制平面把它设成控制平面节点的IP或一个临时域名后面加节点时就不用改worker的kubeconfig。不加这个参数的后果是worker的kubeconfig里直接写死了控制平面IP等你扩成三个控制平面worker还是只连原来那台高可用形同虚设。这个坑很隐蔽因为集群表面上能跑只有那台挂了你才发现问题。# 高可用场景的init示例单控制平面也可先这么写 kubeadm init \ --control-plane-endpoint k8s-api.example.internal:6443 \ --apiserver-advertise-address 192.168.10.11 \ --pod-network-cidr 10.244.0.0/16 \ --image-repository registry.example.internal/k8s \ --kubernetes-version v1.28.5这里k8s-api.example.internal是个示例域名实际用你环境里能解析的名字。域名要提前在每台机器的hosts或内部DNS里配好否则init时会因为解析失败卡住。3.4 镜像仓库与超时问题的处理思路kubeadm默认从公共仓库拉取控制平面组件镜像网络条件不好的环境里这一步经常超时。解决办法有两个一是用--image-repository指定一个可达的镜像仓库二是提前kubeadm config images pull把镜像拉到本地。我一般推荐第二种因为可控。先kubeadm config images list看看需要哪些镜像然后用--image-repository配合pull一次性拉全。拉完之后init时就不会再去网络上折腾速度快很多也避免中途超时导致集群半初始化状态。如果拉取过程中某几个镜像反复失败可以用crictl pull单独拉或者从其他机器crictl save导出再load进来。这个手法在完全隔离的内网环境里几乎是必备技能值得提前练一遍。4. 执行kubeadm init之后的完整链路拆解init成功那一刻的终端输出信息量很大很多人看一眼就往下走其实那几段输出把后面所有关键信息都给了。这一段我按实际执行顺序拆开讲把每条输出的用途说清楚。4.1 init输出里的三件事kubeconfig、join命令、tokeninit成功后会打印三类东西。第一类是kubeconfig配置命令三条mkdir、cp、chown作用是让当前用户能用kubectl操作集群。这三条必须执行否则kubectl get nodes会报连不上。执行完敲kubectl get nodes应该能看到控制平面节点状态是NotReady这正常因为还没装CNI。第二类是worker节点的join命令形如kubeadm join ip:6443 --token token --discovery-token-ca-cert-hash sha256:hash。这条命令24小时内有效token默认TTL过期了可以用kubeadm token create --print-join-command重新生成。注意join命令里的hash是证书的指纹用来验证控制平面身份别随手改。第三类是高可用场景下其他控制平面节点的join命令带--control-plane参数单控制平面实验用不上。如果你一开始就规划了高可用这条命令要保存好后面加控制平面节点要用。4.2 安装CNI插件Calico与Flannel的取舍与实测CNI不装节点永远是NotReady这是新手最常卡住的地方。Flannel和Calico是最常见的两个选择。Flannel配置简单一条kubectl apply就完事适合实验和小规模集群Calico功能更强支持网络策略生产环境更常见但配置项多一些对内核和路由的要求也略高。实验环境我一般先用Flannel把流程跑通因为它几乎不会有额外依赖装完几分钟节点就Ready。等流程熟了再换Calico体验网络策略。需要注意Flannel的yaml里默认网段要和--pod-network-cidr一致不一致就改yaml再apply。# Flannel部署网段需与init时一致 kubectl apply -f https://example.internal/manifests/kube-flannel.yml # 观察Pod状态 kubectl get pods -n kube-flannel -o wide kubectl get nodeskubectl get pods -n kube-flannel要看到所有Pod是Runningkubectl get nodes状态从NotReady变Ready。如果Flannel Pod一直ContainerCreating或CrashLoopBackOff八成是网段冲突或br_netfilter没加载回到第2章那几项复查。Calico的话还要关注calico-node这个DaemonSet是不是在每个节点都跑起来了。4.3 节点NotReady的排查顺序节点NotReady是最常见的状态排查要有固定顺序否则容易东一榔头西一棒子。我的顺序是先看kubectl describe node name的Conditions看是NetworkUnavailable还是KubeletNotReady再看journalctl -u kubelet -n 100看kubelet报什么最后看CNI Pod是否正常。NetworkUnavailable基本都是CNI没装或没装好。KubeletNotReady则可能是cgroup驱动不一致、运行时没起来、证书问题。kubelet日志里如果出现failed to get sandbox image说明pause镜像拉不到出现cgroup driver字样回到2.3节改containerd配置。这个排查链路走熟了大部分NotReady都能在十分钟内定位。4.4 worker节点加入与集群状态确认worker节点执行join命令前确认它已经完成了第2章的全部环境配置尤其是swap关闭、内核参数、容器运行时。join成功后在控制平面执行kubectl get nodes -o wide应该看到worker节点初始状态NotReady等CNI在它上面也跑起来后会变Ready。确认集群健康我一般跑三条kubectl get nodes看节点状态、kubectl get pods -A看所有系统Pod是否Running、kubectl run一个测试Pod看能不能被调度到worker上并正常访问。第三条最能说明问题因为节点Ready不代表Pod真的能跑起来网络、调度、镜像拉取任何一个环节有问题都会在这一步暴露。5. 首次集群跑起来后最常见的几个坑与排查链路集群能起来和能稳定用是两回事。这一章我把实际遇到频率最高的几类问题按排查链路的顺序写出来方便你对照现象定位而不是只给结论。5.1 cgroup驱动不一致导致的kubelet反复重启现象是systemctl status kubelet显示activating (auto-restart)journalctl -u kubelet里反复出现misconfiguration: kubelet cgroup driver cgroupfs is different from ... systemd。根因是containerd用systemd而kubelet用了cgroupfs或者反过来。修复方法是两边统一。containerd这边改/etc/containerd/config.toml里的SystemdCgroup true并重启kubelet这边在/var/lib/kubelet/config.yaml里确认cgroupDriver: systemd或者在kubeadm init时通过--cri-socket和配置确保一致。改完systemctl restart kubelet观察日志不再报错即可。这个坑的特点是日志写得很明确照着改就行难的是第一时间想到去看kubelet日志而不是去搜报错。5.2 防火墙与iptables链路对Pod通信的影响节点间Pod通信依赖iptables规则和转发如果系统防火墙firewalld或ufw没放行或者FORWARD链默认策略是DROP跨节点Pod通信就会断。现象是同一节点内的Pod能通跨节点的Pod ping不通。排查方法是先在源Pod里ping目标Pod IP再在节点上traceroute或tcpdump看包到哪一层被丢。确认是防火墙问题后实验环境可以直接关掉防火墙或放行相关网段生产环境则要精细放行至少放行节点网段、Pod网段、Service网段以及apiserver的6443、etcd的2379/2380、kubelet的10250等端口。5.3 证书过期与集群恢复的应急处理kubeadm生成的证书默认一年有效到期后apiserver起不来整个集群失联。这个问题的排查链路是kubectl报x509: certificate has expiredjournalctl -u kubelet里也会看到证书过期相关信息。确认后执行kubeadm certs check-expiration看各证书到期时间。恢复用kubeadm certs renew all续期然后重启控制平面组件静态Pod会自动重建或手动重启kubelet。续期后要重新生成kubeconfig并分发worker节点也要更新/etc/kubernetes/kubelet.conf里的证书信息。这个过程有点繁琐建议在证书到期前一个月就检查一次别等挂了才处理。5.4 时区、DNS与CoreDNS的连锁故障系统Pod频繁重启、日志里出现DNS解析失败、Service名访问不了这类问题往往和CoreDNS有关。CoreDNS本身依赖Service网络和CNI如果CNI没装好CoreDNS会一直Pending或CrashLoopBackOff进而导致所有依赖Service名的Pod异常。排查顺序是kubectl get pods -n kube-system看CoreDNS状态kubectl logs -n kube-system coredns-pod看报错如果报no route to host或connection refused回到CNI和kube-proxy那边查。另外节点时间不同步也会导致证书校验失败进而让CoreDNS起不来这就是为什么第2章要强调时间同步。现象最可能原因排查入口节点一直NotReadyCNI未安装/网段冲突kubectl describe node、CNI Pod日志kubelet反复重启cgroup驱动不一致journalctl -u kubelet跨节点Pod不通防火墙或转发未开节点iptables、ip_forwardkubectl报证书过期证书超过有效期kubeadm certs check-expirationCoreDNS起不来CNI异常或时间不同步CoreDNS日志、timedatectl6. 把集群从能跑推到敢用的几个补强动作集群Ready只是起点。真要用起来还有几件事得补不然你会在业务上线后发现各种能用但不顺手的问题。6.1 移除控制平面污点与节点标签规划kubeadm默认给控制平面节点打上node-role.kubernetes.io/control-plane:NoSchedule污点意思是普通业务Pod不会被调度上去。实验环境只有一台机器的话你得把这个污点去掉才能跑业务Podkubectl taint nodes name node-role.kubernetes.io/control-plane-。生产环境则保留污点让控制平面专心跑管理组件。节点标签也要提前规划。给worker打上node-role.kubernetes.io/worker之类的标签后面做亲和性调度、污点容忍时才有依据。我一般还会按硬件规格给节点打标签比如diskssd、cpuhigh方便后续用nodeSelector做定向调度。6.2 持久化存储与Ingress的接入时机业务一旦涉及有状态服务就需要持久化存储。实验环境可以用local-path-provisioner这类简单方案快速验证PVC流程生产环境则要上分布式存储或对接云盘。接入时机建议在业务部署前而不是等业务跑了才发现没存储。Ingress同理。Service的NodePort或LoadBalancer能满足基本访问但要做域名路由、TLS终止就得Ingress。我一般在集群稳定后第一件事就是装Ingress Controller配合一个示例应用验证从外部域名到Pod的完整链路这条链路通了后面的业务部署会顺很多。6.3 备份etcd与证书轮换的日常操作etcd存着整个集群的状态丢了就得重建。备份用etcdctl snapshot save建议做成定时任务把快照存到集群外的位置。恢复时用etcdctl snapshot restore注意恢复后apiserver和kubelet的证书路径要对应上不然起不来。证书轮换除了前面说的到期续期还可以在kubeadm init时通过配置缩短证书有效期做演练熟悉renew流程。日常巡检我一般看两样kubeadm certs check-expiration和etcd快照的最近时间这两项正常集群的底盘就是稳的。6.4 版本升级路径的选择kubeadm升级要逐个小版本走不能跨版本。比如1.26升1.28要先升到1.27再升1.28。升级顺序是先升控制平面kubeadm upgrade plan看可升级版本然后kubeadm upgrade apply再逐台升级kubelet和kubectl最后升worker节点。每一步之间要确认节点状态正常再继续。升级前务必做etcd快照和配置备份并且在一个非生产集群上先把整个流程演练一遍。我见过跳过演练直接升生产、结果卡在某个版本差异上回滚的案例代价很大。升级这件事慢就是快。最后分享一个我个人一直在用的小习惯每次集群搭完把kubeadm init的完整命令、join命令、证书到期时间、CNI版本、各节点角色整理成一页文档存起来。后面无论是扩容、排错还是交接这一页纸能省掉大量回忆成本。集群运维的很多麻烦本质上不是技术难而是信息散落各处没人整理。