☰
k8s集群安装实战:kubeadm初始化、containerd配置与CNI网络插件排障
2026/10/19 5:06:29 网站建设 项目流程

k8s(Kubernetes)的安装,说实话难度不大,但坑非常稳定地集中在几个固定位置:容器运行时配置、网络插件、版本不一致、镜像拉不下来。前几年我搭第一套集群时,光是在这三个坑里就反复折腾了好几天。这篇文章把我最近一次从头搭建集群的完整过程整理成文,从环境规划、基础初始化、containerd配置、kubeadm初始化,到工作节点加入、CNI网络插件部署,以及最后的人间真实排查记录,全部理顺一遍。适合正在准备自己搭一套k8s环境、又不想只在文档里云体验的读者,照着走基本可以一步到位。

1. 动手安装前先把三件事定明白:版本、节点、网络

很多人拿到安装文档就急着敲命令,结果装到一半发现某个组件版本不对,或者两个节点配置不一致,回头排查非常痛苦。我现在的习惯是,先在纸上把架构写清楚,再动手。

1.1 安装路线选择:为什么一直用kubeadm

k8s集群的安装路线大致有四条:Minikube单机玩具、kubeadm标准集群、二进制手工部署、自动化平台部署(某发行版方案)。日常学习和搭建生产可用的小集群,我首选kubeadm。

理由很简单:kubeadm是官方维护的标准集群引导工具,它会自动帮你处理控制平面的证书、kubeconfig、etcd部署等一大串脏活。二进制手工部署能让你对每个组件理解得更深,但是手动维护一堆systemd服务和证书,第一套环境大概率会踩到组件之间握手失败的坑。Minikube适合体验功能,但它只是一台虚拟机,不是真正的多节点集群。

我需要说清楚一个观念:kubeadm是引导工具,不是运维工具。它只管把集群拉起来,后续的升级、证书续签、备份恢复这些事仍然要自己操心。

1.2 节点角色与规模的最小方案

我这次规划的是最经典的3节点结构,1个控制平面节点加2个工作节点。如果你的机器资源紧张,也可以把控制平面和工作负载混在一起,但生产环境强烈不建议这么干。控制平面节点一旦被业务Pod挤爆内存,apiserver会非常不稳定。

节点角色主机名建议推荐配置
control-planek8s-master2C4G起步
worker-1k8s-worker14C8G根据负载调整
worker-2k8s-worker24C8G根据负载调整

这里的主机名不是随便起的,后面kubelet注册节点、日志定位、证书组织名称都会用到。建议安装前就让主机名规范下来,而不是装完了再改。

1.3 版本对齐:k8s组件版本一致性原则

k8s集群里需要版本对齐的有三件套:kubeadm、kubelet、kubectl。这三个组件的版本必须保持在同一Minor版本。比如你都用v1.28.x系列,那么kubeadm是v1.28.3,kubelet也必须是v1.28.3,kubectl可以略微放宽,但最好一致。

容器运行时尽量选择稳定版本的新版本。containerd当前主流是1.7.x,至少要大于1.6.x,否则和kubelet的CRI交互可能出现兼容问题。

提示:系统内核版本建议在3.10以上,实际测试中5.x内核在性能上明显更好,特别是网络和文件系统相关的场景。装系统的时候直接选择较新发布版本通常不会错。

2. 每台机器都要过的初始化关卡:网络、内核参数、系统服务

这个阶段最容易被人忽略,但恰恰是后面很多诡异问题的根源。所有节点,包括控制平面和工作节点,都需要做同样的操作。

2.1 主机名、hosts解析和关键内核模块

先给每台机器设置主机名,以控制平面节点为例:

hostnamectl set-hostname k8s-master

然后修改每台机器的/etc/hosts,把三个节点的IP和主机名对应关系写进去。这一步的目的,是让集群内部组件之间可以通过主机名互访,避免之后kubelet或etcd因为解析问题导致通信失败。

192.0.2.10 k8s-master 192.0.2.11 k8s-worker1 192.0.2.12 k8s-worker2

随后需要加载两个内核模块。overlay负责容器镜像的分层存储,br_netfilter负责桥接网络的iptables规则穿透,缺少它会导致Service的ClusterIP转发失效。

modprobe overlay modprobe br_netfilter

为了让这两个模块在重启后依然生效,写入模块加载配置,我习惯直接创建一个.conf文件放在/etc/modules-load.d/下面。

2.2 sysctl内核参数:IPv4转发和iptables桥接

接下来是内核参数,这一步直接关系到Pod网络和Service转发的正常工作。创建文件/etc/sysctl.d/k8s.conf,内容如下:

net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1

执行 sysctl --system 让参数立即生效。这三个参数不用背,只需要理解逻辑:容器网络通过bridge转发时,iptables规则必须对桥接流量生效,否则Service对应的DNAT规则永远匹配不到容器流量。

2.3 关闭swap和防火墙策略

swap在k8s中是被默认禁止的。kubelet在资源分配时如果检测到swap开启,在旧版本会直接拒绝启动。虽然新版本在一定程度上容忍swap,但为了稳定性,我依然选择关闭它:

swapoff -a sed -i '/ swap / s/^/#/' /etc/fstab

注意两行都要执行,否则重启后swap又回来了。

防火墙部分要分场景看待。我在干净的内网测试环境里,会直接关闭firewalld,减少干扰变量。但是生产环境建议保留防火墙,只放行必要端口:6443(apiserver)、2379/2380(etcd)、10250(kubelet)、30000-32767(NodePort服务段)。

systemctl stop firewalld && systemctl disable firewalld

3. 容器运行时:containerd是第一个大坑集中地

容器运行时是整个集群里最容易出错的一层。我在早期版本里习惯装Docker,然后让k8s通过dockershim通信。自从k8s移除了dockershim后,新的环境中直接用containerd才是正道,没有必要再多套一层Docker。

3.1 安装containerd及基础配置文件

安装containerd相对简单,通过发行版软件源安装即可:

yum install -y containerd

但安装出来只是开始。系统自带的默认配置通常不满足k8s运行需求,需要先导出一份默认配置再修改。

mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml

很多人省略了这一步,导致之后Pod反复进入ContainerCreating状态。

3.2 最关键的两处配置修改

修改config.toml时有两个关键点,缺一不可。

第一处是SystemdCgroup参数。k8s的kubelet默认使用systemd作为cgroup驱动,containerd的默认配置却是cgroupfs。两者不一致,会导致Pod里进程的资源统计错乱、偶发杀进程。必须把这段配置改为true:

[plugins."io.containerd.grpc.v1.cri"] systemd_cgroup = true

第二处是pause镜像地址。containerd在创建Pod时,会先拉取一个sandbox镜像(pause),这个镜像的默认地址指向公网仓库。在很多网络环境下,从公网仓库拉取镜像可能不稳定。我的一般做法是,提前在能连通目标仓库的环境上把所需镜像导出来,打包带到各节点,或者把配置里的sandbox_image指向团队内网已有的镜像仓库地址。

sandbox_image = "registry.example.local/k8s/pause:3.9"

这里registry.example.local是示例域名,实际使用中你需要替换为自己内网仓库的地址。保存配置后重启containerd:

systemctl restart containerd

注意:每次修改config.toml后,都要通过 systemctl restart containerd 重启,并且建议用 crictl pull 提前测试一次镜像拉取是否正常,别等到kubeadm初始化了才发现运行时有问题。

3.3 用crictl验证运行时状态

安装完containerd后,我习惯验证一遍:

crictl version crictl pull nginx:latest

crictl是CRI命令行工具,用来和容器运行时直接交互。如果pull能够成功,说明containerd的运行环境基本没问题。这个工具后续排查Pod状态时也会经常用到。

4. kubeadm初始化控制平面:参数一旦定下,后面不好改

容器运行时就绪后,开始在控制平面节点安装k8s三件套并执行初始化。这一节我把需要装的组件、命令参数的含义和后续配置全部展开。

4.1 软件源配置与三件套安装

kubeadm、kubelet、kubectl三件套通过软件源安装。我这里不给具体的源配置细节,只说一个原则:把对应发行版的软件源地址配置为团队内维护的稳定源,或者直接使用发行版官方源,然后安装指定版本。

yum install -y kubeadm-1.28.3 kubectl-1.28.3 kubelet-1.28.3

这里有一个容易忽略的细节:需要确认kubelet开机自启但暂不启动。因为kubelet要等kubeadm生成相关配置后才能正常工作,提前启动会报一堆错误,虽然不影响后续,但会让你的错误日志看得心烦。

systemctl enable kubelet

先enable,不start。等init完成后,kubelet自然会被kubeadm拉起。

4.2 kubeadm init 完整参数与含义

在控制平面节点执行初始化前,我最后确认一次参数。生产上常用这样一套:

kubeadm init \ --kubernetes-version=v1.28.3 \ --apiserver-advertise-address=192.0.2.10 \ --control-plane-endpoint=192.0.2.10 \ --pod-network-cidr=10.244.0.0/16 \ --service-cidr=10.96.0.0/12

逐个解释参数的意义:

  • apiserver-advertise-address:apiserver对外广播的IP地址,通常就是本机内网IP。
  • control-plane-endpoint:高可用集群时是负载均衡器的地址,单控制平面直接填本机IP。
  • pod-network-cidr:Pod网段。这个网段必须和你后面要装的CNI插件保持一致。如果用的网络方案是flannel,默认匹配10.244.0.0/16;如果用的方案是calico,默认使用10.10.0.0/16或102.168.0.0/16,一定要提前查清楚再填。
  • service-cidr:Service网段,用于ClusterIP分配。

Pod网段和Service网段绝对不能重叠,否则一旦IP冲突,网络表现会非常诡异。

4.3 初始化成功后的三个配置步骤

初始化成功后会输出一大段提示。重点有三件事要做。

第一,配置本用户访问集群的kubeconfig:

mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config

第二,保存好join命令。kubeadm init的输出末尾会给出一个kubeadm join命令,包含token和ca证书hash,这个命令是工作节点入集群的关键,复制保存下来,后面要用。

第三,如果你需要多控制平面高可用,还需要复制证书到其他控制平面节点;如果没有这个需求,可以忽略。

执行完以上步骤,用kubectl get nodes看状态。这时的节点大概率为NotReady状态,不要慌,这是正常的,因为还没有部署网络插件。

5. 工作节点加入集群:从token到证书的全流程

工作节点的前缀步骤和前面2、3节完全一样,需要把基础设施初始化、containerd配置全部做完,然后再执行join。很多踩坑都是因为工作节点上的containerd配置没改,直接join进来之后Pod无法创建。

5.1 工作节点执行join

在控制平面节点,用如下命令查看当前的token:

kubeadm token list

如果token过期,或者你压根没保存之前的join命令,可以用如下命令重新生成:

kubeadm token create --print-join-command

生成的结果就是一条完整的join命令,直接在工作节点上以root身份执行即可。它内部做的事情是:下载集群CA证书、生成该节点的kubelet配置、向apiserver发起注册请求。

kubeadm join 192.0.2.10:6443 --token xxxxx --discovery-token-ca-cert-hash sha256:xxxxx

5.2 确认节点健康状态

回到控制平面节点执行:

kubectl get nodes

正常情况会看到新节点以NotReady状态加入,等待网络插件部署完成后转为Ready。如果节点状态是Unknown,通常说明kubelet没有正常启动或网络不通;如果是NotReady并伴随MemoryPressure等提示,需要进一步查看kubelet日志。

5.3 节点token过期与重新申请的注意点

token默认有效期是24小时,如果你隔了一段时间再添加新节点,大概率会碰到token过期的问题。重新用kubeadm token create --print-join-command生成即可,不需要重新初始化整个集群。这个命令在每次扩容时几乎都会用到,值得记牢。

6. CNI网络插件:部署时机和Pending的排查思路

网络插件决定Pod之间、Pod和Service之间的连通性。它必须在集群初始化后立即部署,越早越省心。如果拖太久,即使节点状态表现正常,DNS和网络功能也是残缺的。

6.1 为什么集群初始化后要立刻部署网络插件

kubeadm在初始化阶段不会帮你部署任何CNI插件。CNI必须由用户额外安装。没有CNI时,节点的condition会处于NotReady。原因是kubelet启动时有一个网络准备检查,它要求节点上存在可用网络接口给Pod分配,没有CNI就完不成这个检查。

CNI插件的选择直接影响网络转发模型。我目前的测试环境用的是基于IPIP或VXLAN封装方案的CNI,它能覆盖大多数跨节点通信场景,无需额外依赖底层网络设施。如果你追求极致的性能,可以选择基于BGP或eBPF的方案,但它们的部署复杂度更高,第一次搭建不建议使用。

6.2 部署步骤:先下载manifest,再apply

从CNI项目的官方仓库下载对应的yaml文件,然后执行:

kubectl apply -f calico.yaml

下载的yaml文件会直接创建DaemonSet、Deployment等一系列资源。执行后观察资源状态:

kubectl get pods -n kube-system -o wide

正常情况下,calico和coredns的Pod会陆续变成Running。coredns依赖网络插件,所以网络插件工作之前它会一直Pending,这是正常现象。

这里有必要提醒一个坑:某些CNI版本的manifest里默认指定了网段,如果你在kubeadm init时指定的pod-network-cidr和它不一致,需要修改yaml中的对应字段后再apply,否则Pod会陷入一直创建、一直失败的循环。

6.3 网络Pod一直Pending的排查链路

遇到Pod一直Pending,我一般按下面的顺序查:

  1. 先看Pod的Events详情:kubectl describe pod -n kube-system
  2. 如果是ImagePullBackOff,说明镜像是根本原因,需要回到containerd配置镜像源或者手动导入镜像。
  3. 如果是FailedCreatePodSandBox,多半是CNI冲突或网卡问题,检查kubelet日志。
  4. 如果状态是CrashLoopBackOff,去查看Pod日志,最常见原因是etcd或apiserver的证书问题。

网络插件是很诚实的,报错多数都能直接定位到某个组件上。怕的是你不断重启网络插件但不能静下心看日志,白折腾一圈。

7. 验证与高频故障现场:实操时最容易碰到的三类问题

全套部署完之后,最忌马上宣称“装好了”,先做一轮完整验证。

7.1 验证命令清单

我习惯按顺序执行以下命令:

kubectl get nodes kubectl get pods -n kube-system kubectl run test-pod --image=nginx:1.25 kubectl get pod test-pod -o wide kubectl expose pod test-pod --port=80 --type=NodePort kubectl get svc

到这一步,如果test-pod的状态是Running,且通过NodePort可以访问nginx页面,说明集群的基本链路已经通了:节点调度、镜像拉取、Pod网络、Service网络、NodePort映射全部正常。

7.2 三个高频故障的根因排查

第一类,节点NotReady。先看节点上是哪个Pod异常,再看kubelet日志。运行kubelet日志查看命令后,最常看到的是CNI配置未找到。如果你确实已经安装CNI,检查/opt/cni/bin目录下是否有对应的可执行文件。有时候是软件包没装全,重装cni-plugins即可。

第二类,镜像拉取失败。公网镜像仓库拉取不稳定是常见问题。解决方案分两层:在生产环境搭建内网镜像仓库,同步所需镜像;在测试环境,提前通过crictl pull把常用镜像拉到本地,Pod创建时直接复用。永远不要在每个节点上各自和公网仓库较劲,收效甚微。

第三类,证书类错误。k8s内部组件间通信全部靠TLS证书。最常见的报错是x509 certificate has expired或unknown authority。前者基本都是集群时间不同步导致,ntp同步服务必须全集群覆盖。后者多数是kubeconfig里的证书和集群实际证书不匹配,重新从主节点拷贝admin.conf即可。

7.3 给自己的时间沉淀建议

最后分享一点个人习惯。每次部署完一套集群后,我会把当时的kubeadm init原始命令、join命令、CNI版本和yaml文件改动点,全部记录到一套部署文档里,下次遇到类似环境直接拿来对照差异。不要依赖记忆,那点记录存的不是字,是当时的排查思路。

以我的经验,k8s安装本身最多花半天,真正花时间的是安装完之后的那几轮验证和排障。只要环境初始化、containerd配置、网络插件选择这三关稳住了,整套集群基本不会出大幺蛾子。以后想扩展节点,按住第5节重新生成token的方法走一遍就行,这条路我已经反复走过很多次了,稳定性是有保证的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询