Kubernetes集群安装避坑指南:kubeadm、containerd与CNI排障实战
发布时间:2026/10/21 21:46:40 锦皓数字建站

1. 项目背景与核心问题1.1 为什么K8s安装如此容易翻车K8s安装这件事说实话我一直觉得它被严重低估了。很多人以为K8s难在后面的调度、服务发现、持久化存储这些上层概念结果真上手装的时候才发现光是把一个集群跑起来就能让人怀疑人生。我自己在不止一个环境里装过K8s有生产用的多节点集群也有拿来练手学习的单节点环境。过程不能说一帆风顺准确点说是一路踩坑一路填。有次装生产环境的控制平面节点光一个初始化失败就折腾了大半天最后发现是个非常低级的配置问题——cgroup驱动没对齐。这种问题不遇到一次光看文档根本想不到。所以这篇笔记我不打算讲PPT式的架构原理而是把我实际安装K8s过程中遇到过的、解决过的问题连同排查思路和命令一起整理出来。内容包括环境准备阶段的检查项、kubeadm init失败的典型错误、CNI网络插件接入的坑、工作节点加入时的异常排查、以及一张可以直接当手册用的问题速查表。如果你正准备搭一套K8s环境或者已经装到一半卡住了这篇文章应该能帮你省下不少时间。即使你用的是托管型K8s服务了解底层安装的这些坑对排查业务侧的问题也有帮助。1.2 我使用的环境与版本先说清楚我的安装环境方便你比对。集群是3节点的操作系统是CentOS 7.9内核版本没刻意升级就用的系统默认内核。K8s版本用的是1.28.x容器运行时用的containerd 1.7.x安装工具kubeadm、kubelet、kubectl三个二进制版本保持一致。之所以新装环境不再用Docker作为CRI是因为从K8s 1.24开始Dockershim已经被正式移除。如果你还要用Docker就得靠cri-dockerd这个适配器来桥接。个人建议新环境直接上containerd省心不少。containerd的cgroup驱动配置如果和kubelet对不上会出现节点一直NotReady的诡异问题后面我会专门展开讲。2. 环境准备阶段的关键检查2.1 内核参数、模块与系统资源的核对清单很多人装K8s失败第一道坎就卡在环境检查上。kubeadm init在正式干活之前会自检一大堆前置条件任何一项不满足都会直接报错退出。所以第一步先把系统层面该配的配齐。先说内核模块。K8s依赖iptables对流量做转发和过滤而iptables要正确处理桥接流量需要加载br_netfilter模块。检查方法lsmod | grep br_netfilter如果没有输出就用modprobe加载同时写进开机自动加载的配置modprobe br_netfilter echo br_netfilter /etc/modules-load.d/k8s.conf接下来是内核参数。需要在/etc/sysctl.d/目录下放一个配置文件里面至少包含这几项cat EOF /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这个参数尤其容易漏。K8s集群里Pod与Pod之间、Pod与外部网络之间的通信都需要经过节点转发ip_forward不开后面网络插件再正常也跑不通。所谓内核参数没配好后面全都白干这句话真不是吓唬人。swap也必须关掉。kubeadm默认要求禁用swap因为K8s的QoS机制和内存管理依赖cgroup开启swap会影响内存限制的语义。注意一点光执行swapoff -a只对当前生效重启之后swap又回来了。正确做法是把/etc/fstab里swap那一行注释掉或者直接删掉。主机名和/etc/hosts也值得提前处理。每个节点的hostname要全局唯一不要出现两个节点都叫localhost的情况。主节点要能通过主机名解析到其他节点建议把集群内所有节点的IP和主机名映射都写进/etc/hosts。否则后面kubelet注册节点、etcd组集群的时候可能因为解析不出节点名而各种报错。资源方面我踩过内存不足的坑。当初在一个只有2GB内存的机器上跑kubeadm init控制平面组件全部PENDING查看日志发现kube-apiserver因为OOM反复重启。后来官方文档也确认了控制平面节点建议至少2核CPU、2GB内存。单节点实验环境最少给到4GB内存否则跑起来也是卡到怀疑人生。2.2 containerd配置cgroup驱动与pause镜像容器运行时这边我用的是containerd。直接yum装好之后不要急着跳过配置有两处必须改。第一处是cgroup驱动。kubelet默认使用systemd作为cgroup驱动而containerd默认配置的cgroup驱动是cgroupfs。如果两边驱动不一致kubelet启动后会报类似failed to run kubelet的错误或者节点状态一直是NotReadyKubelet日志里会明确提示cgroup driver: cgroupfs与预期不符。修改方法编辑/etc/containerd/config.toml找到SystemdCgroup参数默认是false改成true。如果你手里没有这份配置文件先通过containerd config default /etc/containerd/config.toml生成一份默认配置再改。第二处是pause镜像地址。containerd默认的sandbox_image指向的是k8s.gcr.io/pause:3.x这个地址在国内网络环境下基本拉不动更别说内网隔离环境了。我之前就卡在这kubelet一直在反复创建sandbox容器但pause镜像始终拉不下来节点看着是ReadyPod全被卡在ContainerCreating。解决思路是把sandbox_image换成可以访问的镜像源。改完之后记得重启containerd。我一般是这么操作systemctl restart containerd然后验证一下配置是否生效crictl info | grep sandbox总之containerd的配置要像检查系统参数一样装完就改别等出了问题才回头找原因。这里还有一个容易被忽略的点containerd的registry.mirrors配置也就是镜像加速配置虽然平时看起来不影响集群初始化但后面部署业务镜像时会深刻影响体验建议提前配好。3. kubeadm init失败的典型错误与解决3.1 预先校验kubeadm init之前的自检项kubeadm init的输出信息本质上是很友好的它会先跑一遍preflight checks把所有不满足的条件一次性列出来。有些人看到一堆带[ERROR]前缀的输出就直接截图问人其实它们每条都写得很直白。我常用的做法是先在每个节点上跑一次kubeadm init --dry-run或者直接执行kubeadm init等它自己报错然后把[ERROR]开头的行收集起来逐条处理。这些错误常见的有几类。比如[ERROR FileContent--proc-sys-net-bridge-bridge-nf-call-iptables]说明2.1节的内核参数没有设置[ERROR Swap]说明swap没关。这种错误按照提示改配置就行。真正麻烦的是那种通过自检但初始化仍然失败的场景比如apiserver容器起不来。3.2 初始化失败的几种典型场景场景一是apiserver容器反复重启。查看容器状态时发现kube-apiserver一直在重启日志里报listen tcp的问题。这个多半是--apiserver-advertise-address参数配置不对。如果你机器上有多个IPkubeadm会默认使用第一个内网IP但有时候那个IP不可达导致apiserver监听的地址不对。解决办法很直接init的时候显式指定kubeadm init --apiserver-advertise-address192.168.1.10 ...场景二是etcd集群建立失败。这在多节点控制平面场景比较常见。etcd之间会通过节点hostname互相发现如果/etc/hosts里没有各节点的主机名解析或者hostname重复etcd日志里会出现一堆peer连接失败的记录。解决路径是把集群控制平面节点的IP、主机名解析关系老老实实写进hosts。场景三是containerd socket找不到。kubeadm init报[ERROR CRI]: container runtime is not running。这种情况先排查systemctl status containerd crictl info如果containerd确实在跑但kubeadm还是找不到检查一下kubelet的配置看看--container-runtime-endpoint是不是被指向了错误的socket路径。新版本默认是unix:///run/containerd/containerd.sock如果之前机器上装过Docker可能残留配置指向了docker.sock就容易出现这种错位。3.3 镜像拉取失败的解决方案kubeadm init过程中要拉取kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、pause、etcd、coredns这7类镜像。任何一个拉不到初始化就直接失败。解决的思路无非两种一是让kubeadm从能访问的镜像仓库拉取镜像二是提前把镜像拉好再用kubeadm init --image-repository指定本地或私有仓库。先看当前需要的镜像列表kubeadm config images list如果网络不通就换镜像源。比如指定某云厂商提供的K8s镜像仓库作为镜像仓库地址kubeadm init --image-repositoryregistry.xxx.io/k8s这种写法相当于把kubeadm默认的registry.k8s.io整体替换掉。如果你有内网私有仓库只需要把镜像手动推到私有仓库里再用同样的参数指定即可。还有一种方式是手动拉镜像再导入。适合完全离线的环境方法是在一台能联网的机器上把kubeadm需要的镜像push到私有仓库然后在离线节点上用crictl pull或者直接让kubeadm从私有仓库拉取。这里请注意一点如果你只是需要在测试环境快速跑起来优先用防火墙与加速器方案而不是手动装载。防抖的原则是在网络层面解决镜像源不可达的问题少跟镜像耦合死磕。4. CNI网络插件的接入与故障定位4.1 选Flannel还是Calico集群初始化完成之后还有个关键步骤就是装CNI网络插件。这个不装节点状态永远都是NotReady。CNI插件负责给每个Pod分配IP、维护节点间的网络连通。很多新手觉得CNI只是一个可选组件其实它和K8s集群的可用性强相关。我最初装Flannel用了几年后来切到Calico又踩了不少坑这两个插件我都实际部署过说下选型感受。Flannel的优点是简单。它用VXLAN或者host-gw模式在节点间建立隧道部署起来就是一条kubectl apply命令的事资源占用也低。缺点是它不带网络策略功能也不要指望它对流量做太多控制。适合小规模集群、学习环境、对网络策略要求不高的场景。Calico的功能更丰富支持BGP路由模式、NetworkPolicy网络策略、甚至可以把外部网络和Pod网段直接打通。它适合中大规模集群和生产环境但配置项多排障门槛也高。BGP模式下对节点的网络环境有要求跨数据中心互通时要额外配置。我的建议是先想清楚你的集群要解决什么问题再选插件。不要一上来就追求Calico的功能全开那只会给自己加难度。4.2 Flannel安装后的Pod不通问题Flannel配置里最关键的参数是--pod-network-cidr。如果你在kubeadm init时指定的是--pod-network-cidr10.244.0.0/16那Flannel的部署文件里也必须是这个网段。我第一次装Flannel就是栽在这。init时随手写了个10.0.0.0/16作为Pod网段然后直接拿官方默认的kube-flannel.yml去apply结果Pod之间ping不通。Flannel分配IP时会默认使用10.244.0.0/16这个网段去规划子网和kube-controller-manager分配给节点的PodCIDR完全对不上网络自然就废了。如果遇到了先检查节点分配的PodCIDRkubectl get node -o jsonpath{.items[*].spec.podCIDR}再对照一下Flannel daemonset的环境变量看网段是否一致。不一致就改改完重新apply。另一个Flannel常见问题是多网卡环境下选错网卡。Flannel默认会用默认路由对应的网卡来建立VXLAN隧道但如果你机器上有多个网卡比如内网网卡和外网网卡并存Flannel可能选到外网网卡导致隧道建立不起来。解决办法是在kube-flannel.yml的args里增加指定网卡的参数比如--ifaceeth1。4.3 Calico部署后的地址冲突Calico安装本身通常很顺利但有个问题容易掉坑——默认的Pod网段。Calico默认使用192.168.0.0/16作为Pod网段这个网段和企业内网的地址冲突率相当高。如果你的局域网恰好把192.168.x.x段占用了Calico会给Pod分配一个跟物理网络冲突的IP导致Pod访问外部IP时路由错乱表现为Pod能启动但网络不通。解决办法是在安装Calico之前先想清楚你的Pod网段规划。Calico部署文件里有一个CALICO_IPV4POOL_CIDR环境变量改成你自己的规划网段比如10.20.0.0/16然后再安装。同时保证kubeadm init时的--pod-network-cidr和这个网段保持一致。Calico还有个selector特性只对匹配了特定标签的Pod生效。默认的IPPool配置通常覆盖全部Pod。如果你做了自定义的IPPool注意每个节点分配的PodCIDR要落在池子里否则Pod创建后IP分配不出状态会一直Pending。5. 工作节点的加入与控制平面异常5.1 token过期与join命令失效kubeadm init结束之后会输出一段kubeadm join命令里面有token和证书hash。很多人喜欢先把这条命令存下来结果隔了一天再去加节点发现token已经过期了。token默认有效期是24小时。加节点时报错ERROR TokenExpired解决办法是重新生成tokenkubeadm token create --print-join-command这条命令会输出一个新的join命令直接复制到工作节点上执行即可。如果加了--ttl0token永远不会过期适合懒人但不建议在生产这么干。还有种情况是join命令报证书hash不匹配。仔细看kubeadm init输出的是--discovery-token-ca-cert-hash sha256:xxx如果你把命令复制错了或者中间证书轮转过就会报错。再次执行kubeadm token create --print-join-command把整条命令完整复制下来包括后面的hash参数基本能解决。5.2 节点加入后一直NotReady节点执行完join命令后在控制平面节点上看新节点的状态很可能长时间是NotReady。这种情况先别急着骂kubelet按照从近到远的思路排查。先在控制平面节点上查看kubectl get nodes kubectl describe node 节点名describe的输出里有Conditions列表会说明具体卡在哪。最常见的是Ready Unknown一般意味着kubelet上报心跳有问题。下一步进到工作节点上看kubelet状态systemctl status kubelet journalctl -u kubelet -f日志是最直接的证据。我发现几个高频原因一是镜像拉不动。kubelet启动后需要拉pause容器如果你的containerd没配置好镜像源kubelet日志里会一直刷Failed to pull image。解决方式参照2.2节。二是节点证书没有被批准。Kubelet启动时会生成一个证书签名请求如果没有在控制平面节点上批准节点永远无法注册成功。查看方法kubectl get csr看到Pending状态的证书请求就用kubectl certificate approve csr名称实际操作时如果节点多可以用一条命令批量批准所有Pending的请求。三是PLEG问题。kubelet日志里出现PLEG is not healthy字样多半是containerd响应卡顿或者容器列表同步不过来了。一般先重启containerd再重启kubelet能暂时缓解。如果反复出现需要检查节点负载和系统资源。5.3 控制平面组件的守护与恢复控制平面的几个Podkube-apiserver、kube-controller-manager、kube-scheduler都是跑在static pod里的也就是说它们本质上是kubelet直接管理的容器。这些组件起不来的话整个集群都处于亚健康状态。排查apiserver异常我是这么做的进入控制平面节点先看容器状态crictl ps -a | grep apiserver没有输出说明kubelet没把它拉起来。看kubelet日志。如果容器处于Exited状态就把容器日志抓出来看crictl logs 容器ID常见问题是证书路径不对、etcd连接失败、或者apiserver的advertise地址不通。apiserver起不来时集群所有API请求都会失败kubectl get nodes会直接报连接被拒绝。有一种情况容易忽略控制平面组件占用的端口被占用。比如主机上本来跑了别的服务占用了6443端口apiserver就会起不来。排查的时候顺手看一下ss -lntp | grep 6443如果确认被占用要么停掉占用的服务要么修改apiserver的监听端口。6. 安装问题速查表与排障心得6.1 高频问题速查表日常排障多了我习惯把问题整理成一张对照表用的时候直接查效率高很多。下面这表是我在多次安装中总结的覆盖了大部分安装阶段的常见问题。现象排查命令常见根因解决方案kubeadm init自检报swap错误free -h未关闭swapswapoff -a注释fstab条目init自检报iptables错误sysctl net.bridge.bridge-nf-call-iptables内核模块未加载modprobe br_netfilter设置内核参数初始化卡在拉镜像kubeadm config images list镜像源不可达配置镜像仓库或私有仓库节点加入后NotReadykubectl describe node网络插件未安装安装CNI插件节点NotReady且日志有PLEGjournalctl -u kubeletcontainerd异常或负载高重启containerd、kubelet检查资源Pod一直ContainerCreatingkubectl get pods -Apause镜像拉取失败修改sandbox_image重启containerdjoin报token过期kubeadm token create --print-join-commandtoken超过24h重新生成tokenapiserver反复重启crictl logs apiserver容器advertise地址不对或端口占用显式指定IP释放端口etcd起不来crictl logs etcd容器hostname无法解析修复/etc/hosts解析kubelet报cgroup驱动不一致journalctl -u kubeletcontainerd和kubelet驱动不一致修改SystemdCgrouptrue这张表不完整但覆盖面已经足够广。遇到新问题时基本上都是容器起没起来日志怎么说配置对不对这三个维度去查。6.2 几条拿得出手的排障习惯与避坑心得踩了这么多坑之后我也沉淀出了几套固定的排查习惯现在每次装K8s都会下意识执行确实帮我省了很多事。第一安装前先做三查三试。查内核模块加载情况、查系统参数、查swap状态。试一下containerd能否正常使用、试一下crictl能否连接、试一下镜像能否拉下来。这六件事做完后面80%的初始化报错都不会再出现。第二版本绝对不要混装。kubeadm、kubelet、kubectl三个二进制文件版本必须一致K8s版本和容器运行时版本也要兼容。有一次我kubelet和kubeadm差一个minor版本结果init一切正常但节点加入时kubelet直接拒绝启动日志里报版本不匹配查了好久才意识到问题。第三kubeadm reset是一个非常好的工具。每次环境搞乱了与其反复猜测哪里没配置好不如直接kubeadm reset -f rm -rf /etc/cni/net.d iptables -F iptables -t nat -F iptables -t mangle -F把残留配置清理干净再重新init。干净的初始状态比任何修复技巧都可靠。注意reset之后cni配置文件和iptables规则可能会残留不清理干净重装后CNI插件反而会闹脾气。这步我每次都会做。第四遇到问题先看日志再提问不要盲改配置。很多K8s问题的表象是一样的但根因千差万别。比如NotReady有可能是网络插件没装、镜像拉不动、证书没批准、cgroup驱动不一致、资源不足五类原因。不打开journalctl看kubelet的日志光靠猜是做无用功。第五初始化的参数一定要留档。--pod-network-cidr、--service-cidr、--apiserver-advertise-address这些参数我每次都会写进部署文档里。因为以后加节点、换网络插件、排查路由问题时都要回来参考这些参数。别等到出问题时才发现自己忘了当初填的是什么网段。最后再分享一个小技巧多节点环境里如果控制平面节点和工作节点都装了防火墙尽量先关掉或者把K8s需要的端口加到白名单。我以前在一台防火墙默认拒绝的环境里装集群节点状态反反复复最后才发现是防火墙把kubelet和apiserver通信的端口拦了。像6443、2379-2380、10250、10251、10252、30000-32767这些端口都得确保放通。与其在排障时一层层剥洋葱不如一开始就把网络层面的通路准备好。K8s安装这件事本质上不复杂但涉及的系统层面细节很多很容易被一个不起眼的参数绊住。希望这篇笔记能让你少走一些我走过的弯路。如果你正卡在某个具体的报错上照着上面的思路一路查下来大部分问题都能定位到根因。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。