news 2026/9/28 22:27:47

离线部署K8s 1.32.11集群到银河麒麟V10的完整指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
离线部署K8s 1.32.11集群到银河麒麟V10的完整指南

接到一个挺典型的任务:机房里的银河麒麟V10服务器,网络是物理隔离的,完全没有外网,要在这一批机器上把Kubernetes 1.32.11集群搭起来,后面还有应用要往上部署。这种场景在内网交付里太常见了,在线安装时一条kubeadm init就能自动完成的拉依赖、拉镜像,到了离线环境全得自己动手。从前期的物料打包,到系统侧的基础配置,再到镜像导入、节点加入,任何一环漏了都要花成倍的时间去排查。

这篇文章就以k8s 1.32.11 + 银河麒麟高级服务器操作系统V10为例,把离线部署的完整链路过一遍。内容包括打包机的准备、RPM和镜像的离线物料收集、麒麟V10系统侧的基础配置、kubeadm init的细节、worker节点加入、Calico网络插件部署,以及我在实际部署中踩过的一堆坑。适合正在做内网交付、信创环境落地、或者准备在无外网机房搭k8s的运维和实施工程师参考。不同基础的人都能从中找到对应自己阶段的内容——如果纯新手,按章节顺序一步步来就行;如果有一定经验,可以直接跳到第6章看排错部分。

1. 把"离线安装"想清楚,你就成功了一半

1.1 离线装k8s到底难在哪

在线安装的时候,kubeadm init会替你完成几乎所有脏活:从官方仓库拉取kube-apiserver、kube-controller-manager、etcd、pause等镜像,从系统源里解决rpm依赖。离线环境等于把所有"在线的便利"全部取消,你需要在另一台有网的机器上,提前把整个集群需要的所有东西搬到一个U盘或者移动硬盘里,再进内网。

拆解下来,离线部署要做的事情其实就三件:

  1. rpm依赖树:kubelet、kubeadm、kubectl这些组件的rpm包,以及container runtime(我们用的是containerd)的rpm包和它们的依赖包。
  2. 容器镜像:k8s核心组件镜像、pause镜像、etcd镜像、coredns镜像,还有网络插件Calico的镜像。这些必须预先导出成tar文件,再到内网节点上导入到containerd里。
  3. 系统参数就位:内核模块、sysctl参数、swap关闭、时间同步、SELinux/防火墙策略。这一步在离线环境和在线环境没什么区别,但在内网环境里更容易被忽略,尤其内核模块缺失这种事,真的能卡一整天。

很多人觉得"离线"就是"没有网",只要把包拷进去就完了。实际上离线安装最考验的是"完整性问题"——你拷进去的包必须是完整的依赖树和镜像集合,少一个都装不起来。所以先想清楚这三件事,再动手,后面就会顺很多。

1.2 为什么选k8s 1.32.11 + 麒麟V10这个组合

银河麒麟高级服务器操作系统V10,底层兼容CentOS 8/RHEL 8的用户态和包管理生态,所以大多数面向RHEL 8系的rpm包都能直接安装使用,这给离线部署提供了很大的便利。k8s官方仓库提供的kubelet、kubeadm、kubectl的rpm包就分为el7和el8两套,麒麟V10走el8这条线完全没有问题。

k8s 1.32.x这个版本,containerd早就成了唯一的CRI运行时,kubeadm在初始化时已经不再为Docker单独做适配。你在配置层面只需要跟containerd打交道,系统里装一个containerd,然后让kubelet和kubeadm走unix:///run/containerd/containerd.sock就行。相比早期版本一堆runtime选择,1.32这条线的注意力更集中。

另外kernel版本方面,麒麟V10的内核通常是4.19或5.10,满足k8s对Linux内核的最低要求。真正要留意的是内核模块,比如br_netfilter、nf_conntrack、ip_vs这些,后续会专门讲到。建议不要在昂贵的CPU和内存配置上纠结,先确保操作系统版本和rpm镜像架构一致,这是最前面的一道坎。

1.3 整体方案选型:本地镜像导入 还是 内网Harbor

离线环境下,容器镜像的分发方式有两种常见选择。

方案适用场景优点缺点
每个节点本地ctr导入tar集群3~5台,节点规模不大架构简单,不需要额外部署镜像仓库服务节点多时每台都要导一遍,操作重复
内网部署Harbor集群规模较大,后续频繁发布新镜像镜像集中管理,新增节点不用重新导镜像,接近半在线体验需要额外一台机器跑Harbor,并要把所有镜像push到Harbor

如果只是先把k8s集群跑起来,我建议先走第一种。等集群稳定了、需要频繁发布业务镜像时,再考虑架Harbor。这样能把"搭集群"和"搭镜像仓库"两件事解耦,排查问题时的复杂度会低很多。本文接下来的流程也是按"本地镜像导入"这个方案来写的。

2. 在有网机器上打包离线物料:yum源、rpm、镜像三件套

这一章非常关键,基本决定了离线安装能不能顺利走通。打包机器的选择、rpm的下载方式、镜像的tag命名,都直接关系到最后内网里能不能装成功。

2.1 打包机准备:架构和系统版本必须提前对齐

打包机的作用是用一台能上外网的机器,把rpm包和容器镜像下载整理好。这里面最容易被忽略的是架构一致性:

  • 如果服务器是x86_64,那打包机、所有rpm包、所有容器镜像都必须是amd64的。
  • 如果服务器是ARM(鲲鹏、飞腾等常见国产CPU),那打包机也必须是ARM架构,rpm包要重新下载ARM版本的,容器镜像要拉arm64的版本。

操作系统层面,建议打包机同样选一个RHEL 8系的系统(CentOS 8 Stream、RockyLinux 8、或者麒麟V10都行)。这样用yumdownloader --resolve解析出来的依赖树,和内网麒麟V10的依赖树最贴近,不容易出现"这个包在打包机上能解析出来,到了内网却装不上"的情况。

我踩过一次很深的坑:在CentOS 7的机器上打包k8s相关rpm,结果带出来一堆el7的依赖包,到了麒麟V10里怎么都装不进去,最后只能重新在RHEL 8系机器上再打一份。所以打包机的系统版本,一定要模拟目标内网环境,别小看这一步。

2.2 下载kubeadm、kubelet、kubectl的rpm包

首先在打包机上配置k8s的yum源。国内网络环境下,优先使用阿里云镜像源:

cat > /etc/yum.repos.d/kubernetes.repo << 'EOF' [kubernetes] name=Kubernetes baseurl=https://mirrors.aliyun.com/kubernetes/yum/repos/kubernetes-el8-x86_64/ enabled=1 gpgcheck=0 repo_gpgcheck=0 EOF

注意baseurl里的el8和x86_64要根据你的打包机架构调整,ARM机器对应的是kubernetes-el8-aarch64。

然后安装yumdownloader工具,下载kubeadm、kubelet、kubectl三个rpm包及其所有依赖:

yum install -y yum-utils mkdir -p /opt/k8s-offline/rpms cd /opt/k8s-offline/rpms yumdownloader --resolve kubeadm-1.32.11 kubelet-1.32.11 kubectl-1.32.11

如果你希望之后能精确控制版本,建议直接写死版本号,比如kubeadm-1.32.11-0。这样打包出来的rpm目录相对干净,不会因为yum源里出现更新版而拉到意外的东西。

另外,一同下载createrepo工具自身的rpm包,后面内网建yum源时要用:

yumdownloader --resolve createrepo_c

2.3 containerd及运行时的依赖包

现在的k8s集群基本都用containerd,可以从docker-ce源里拿containerd.io这个rpm包。在打包机上配置docker-ce源:

cat > /etc/yum.repos.d/docker-ce.repo << 'EOF' [docker-ce-stable] name=Docker CE Stable baseurl=https://mirrors.aliyun.com/docker-ce/linux/centos/8/x86_64/stable/ enabled=1 gpgcheck=0 EOF

然后下载:

cd /opt/k8s-offline/rpms yumdownloader --resolve containerd.io

containerd.io这个包一般会把runc、libseccomp这些核心依赖同时带出来,--resolve会自动把它们下载到同一个目录。这样内网机器只要把这里所有rpm包都装上,containerd就能跑起来了。

我这里没有强制指定containerd的具体版本号,建议你下载时看一下这个包当前解析出来的版本,通常1.7.x或2.0.x都没问题。但要注意,后续kubeadm init时containerd的CRI接口必须正常工作,如果你下载的是非常老的版本,有概率出现CRI兼容问题。

2.4 用国内镜像仓库准备离线镜像

首先在一台能访问外网的机器上,根据k8s版本号生成镜像清单:

kubeadm config images list --kubernetes-version=v1.32.11

1.32.11这个版本对应的核心镜像大致包括:

镜像说明
registry.k8s.io/kube-apiserver:v1.32.11API Server
registry.k8s.io/kube-controller-manager:v1.32.11Controller Manager
registry.k8s.io/kube-scheduler:v1.32.11Scheduler
registry.k8s.io/kube-proxy:v1.32.11Proxy,每个节点都需要
registry.k8s.io/pause:3.10每个Pod的基础容器
registry.k8s.io/etcd:3.5.16etcd存储
registry.k8s.io/coredns/coredns:v1.12.1集群DNS

国内网络直接拉registry.k8s.io的镜像通常很慢,实际工作中我用的是阿里云的镜像仓库做中转。可以把镜像拉下来后重新打tag,再导出成tar包:

docker pull registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.11 docker tag registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.11 registry.k8s.io/kube-apiserver:v1.32.11

核心组件以外的pause、etcd、coredns,同样在registry.aliyuncs.com/google_containers下能找到,路径后缀与官方完全一致,只是前缀不一样。把所有镜像pull完、tag完,再统一导出:

docker save -o /opt/k8s-offline/images/k8s-images.tar \ registry.k8s.io/kube-apiserver:v1.32.11 \ registry.k8s.io/kube-controller-manager:v1.32.11 \ ...

这里有一个非常容易踩的坑:docker save保存的是你在本地看到的镜像名。如果你没有先把tag改成registry.k8s.io/...就直接save,导入到内网后镜像名会带着registry.aliyuncs.com/google_containers前缀。之后kubeadm init去找registry.k8s.io/kube-apiserver:v1.32.11时会直接报镜像找不到。所以一定要在打包机上把tag改好,或者在内网导入后再用ctr images tag补一次。这个细节后面第6章还会展开讲。

2.5 内网yum源的最小可用方案

rpm包下载好之后,到了内网有两种装法。

第一种,机器少就可以纯rpm -Uvh一把梭:

cd /opt/k8s-offline/rpms rpm -Uvh *.rpm

这种方式的缺点是不能解决依赖顺序问题,如果rpm包之间有依赖关系(比如先装libseccomp再装containerd),直接*.rpm批量装有可能失败。好在yumdownloader --resolve已经把依赖包都下全了,rpm -Uvh *.rpm通常能一次成功。万一碰到"xxx is needed by xxx"的报错,就手动先装被依赖的包。

第二种,生产环境我更推荐在某个内网节点上建一个本地yum源。把整个rpms目录拷到内网服务器上,用createrepo生成元数据:

createrepo /opt/k8s-offline/rpms

然后用python一句话起个http服务:

cd /opt/k8s-offline && python3 -m http.server 8080

其他节点只需要配置一个repo文件指向这台机器,就能像在线环境一样yum install了。后续扩容worker节点时,不用再拷贝一堆rpm包,直接yum源装包,体验好很多。

3. 麒麟V10系统侧准备:先让containerd健康,再谈kubelet

系统侧准备是离线安装中最容易被跳过的部分。很多人在内网里清了包、导了镜像,然后kubeadm init,结果kubelet起不来或者一直NotReady,最后排查一圈发现是内核模块或者swap没关。这部分步骤比较繁琐,但绝对省不掉。

3.1 主机规划、时间同步、关闭swap

先把每台机器的主机名和IP规划好,写入/etc/hosts。kubeadm在初始化时会对hostname做解析,如果解析不到,会直接宕掉预检。

hostnamectl set-hostname k8s-master01 cat >> /etc/hosts << EOF 192.168.10.11 k8s-master01 192.168.10.12 k8s-node01 192.168.10.13 k8s-node02 EOF

时间同步方面,离线环境访问不了外网NTP服务器,所以集群内至少要有一台机器能作为内网时间源,或者所有节点在部署时确保手动校正时间一致。k8s的证书机制对时间比较敏感,节点间时间差太大,会出现各种诡异的认证失败。

关闭swap是kubeadm预检的硬性要求:

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

第2行的注释是为了防止重启后swap又自动挂载回来。如果机器内存确实紧张,kubeadm也允许加--ignore-preflight-errors=Swap跳过检查,但生产环境不建议长期带swap跑,关掉是最稳的。

3.2 内核模块和sysctl参数:离线下最容易卡住的地方

k8s集群正常工作依赖几个内核模块,典型的是br_netfilter、nf_conntrack,以及用了IPVS模式时需要的ip_vs系列模块。IPVS这块尤其容易出事,因为默认内核并没有都自动加载。

创建模块加载配置:

cat > /etc/modules-load.d/k8s.conf << EOF br_netfilter nf_conntrack ip_vs ip_vs_rr ip_vs_wrr ip_vs_sh EOF modprobe br_netfilter modprobe nf_conntrack modprobe ip_vs

然后配置sysctl参数:

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 vm.swappiness = 0 EOF sysctl --system

注意,br_netfilter如果没加载,即使你写了net.bridge.bridge-nf-call-iptables=1,也会在sysctl --system时报错,或者参数根本不生效。可以先检查一下:

lsmod | grep br_netfilter

如果模块加载失败,先看内核有没有这个模块文件。麒麟V10上我遇到过内核模块存在但modprobe失败的情况,通常是模块依赖没装上,检查/lib/modules/$(uname -r)下相关文件是否存在,必要时重启机器让模块配置重新加载。

3.3 安装containerd并校准Cgroup配置

进入内网后,安装之前打包好的containerd相关rpm:

cd /opt/k8s-offline/rpms rpm -Uvh *.rpm

然后启动并设置开机自启:

systemctl enable containerd --now

containerd包的默认配置不一定会自动生成,建议手动生成一份完整配置:

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

这里必须改一个关键参数:SystemdCgroup。找到plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options这段,把SystemdCgroup改为true。

为什么要改这个?因为kubelet默认使用systemd作为cgroup driver,如果containerd这边还是cgroupfs,两边会不一致,最终kubelet启动后直接报cgroup相关的错误,Pod无法正常运行。这个参数不做对齐,后面必然要返工。

配置完成后重启containerd:

systemctl restart containerd

顺便配置crictl,让它能和containerd通信:

cat > /etc/crictl.yaml << EOF runtime-endpoint: unix:///run/containerd/containerd.sock image-endpoint: unix:///run/containerd/containerd.sock timeout: 10 debug: false EOF

验证containerd已经健康:

crictl version crictl info

如果crictl info正常输出,说明容器运行时这块就绪了。在这个阶段花十分钟检查,会避免后面kubeadm init卡半小时。

3.4 SELinux和防火墙:内网部署别让安全策略变成拦路虎

麒麟V10默认的SELinux可能是enforcing,也可能是permissive。kubelet和容器运行时在文件访问上经常会跟SELinux策略冲突,最典型的症状是Pod拉起失败、挂载权限被拒。内网部署时我一般直接设为permissive:

setenforce 0 sed -i 's/^SELINUX=enforcing/SELINUX=permissive/' /etc/selinux/config

防火墙方面,如果是标准的等保内网,我建议放行以下端口,而不是直接关firewalld。这样做安全策略也能交代过去:

端口用途
6443kube-apiserver
2379-2380etcd客户端通信
10250kubelet
10259kube-scheduler
10257kube-controller-manager
30000-32767NodePort服务端口段
179Calico BGP端口

执行放行后,最好用nc -vz或telnet验证一下节点间端口是否真的通了。离线环境里没有外网干扰,但节点与节点之间的网络却是最容易出问题的环节。

4. kubeadm init:镜像导入和初始化参数的一次性对齐

系统侧准备完毕,接下来的重点是把镜像导入到每台机器,然后用kubeadm init把控制平面初始化出来。这一步的核心是"镜像名称必须和kubeadm期望的名称完全一致"。

4.1 把离线镜像包导入到k8s.io命名空间

kubeadm通过containerd的CRI接口检查镜像是否存在,而containerd里和k8s相关的镜像都放在k8s.io这个命名空间下。所以导入时必须指定-n=k8s.io:

ctr -n=k8s.io images import /opt/k8s-offline/images/k8s-images.tar

导入完成后检查一下镜像列表:

ctr -n=k8s.io images list | grep -E "v1.32.11|pause|etcd|coredns"

如果发现镜像名的前缀不对(比如还是registry.aliyuncs.com/google_containers/...),用ctr images tag修正:

ctr -n=k8s.io images tag registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.11 registry.k8s.io/kube-apiserver:v1.32.11

pause镜像必须存在于容器运行时的镜像列表里,因为在每个Pod创建时kubelet都会去拉pause镜像。如果你的containerd配置里sandbox_image指向的还是registry.k8s.io/pause:3.10,那导入的pause也必须恰好是这个tag,一个字符都不能差。

这里也可以顺手验证crictl视角下的镜像列表:

crictl images

crictl默认连的是containerd的k8s.io命名空间,所以它看到的就是kubelet能看到的内容。

4.2 编写kubeadm配置文件

直接在命令行里挂一大堆参数虽然也能init,但可维护性太差。更推荐的方式是写一份配置文件,然后kubeadm init --config。

k8s 1.32.x用的API版本是kubeadm.k8s.io/v1beta4,如果你拿到的kubeadm版本比较旧只认v1beta3,把版本号改回去即可:

apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration localAPIEndpoint: advertiseAddress: 192.168.10.11 bindPort: 6443 nodeRegistration: criSocket: unix:///run/containerd/containerd.sock --- apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: v1.32.11 imageRepository: registry.k8s.io networking: podSubnet: 172.16.0.0/16 serviceSubnet: 10.96.0.0/12

这里几个关键点说明一下:

  • advertiseAddress填的是本机内网IP,k8s组件将在这个IP上监听,后续worker节点也要通过这个IP访问apiserver。
  • imageRepository默认是registry.k8s.io,如果你的镜像都已经是这个前缀,保持默认即可;如果内网镜像tag用了别的仓库名前缀,这里就要改成对应的值。
  • podSubnet建议不要用192.168.0.0/16,除非你确定内网网段不冲突。很多内网环境本身就用192.168网段,Calico的默认网段撞车之后,Pod之间怎么都不通。这里用172.16.0.0/16避开主网段,是个比较稳妥的选择。

4.3 执行init并按里程碑排查

配置写好后执行:

kubeadm init --config=kubeadm-config.yaml

正常的情况下,你会看到kubeadm依次做这些事情:

  1. 检查系统预检条件。
  2. 从containerd中检查镜像是否存在。
  3. 启动kubelet。
  4. 初始化控制平面组件,启动apiserver、etcd、controller-manager、scheduler。
  5. 安装coredns和kube-proxy。

最后输出的kubeadm join命令要保存下来,后面worker节点加入集群时要用。如果没保存也没关系,控制平面节点上随时可以用kubeadm token create --print-join-command重新生成。

如果init卡在某个环节,优先做两件事:

journalctl -u kubelet -f

另一件事是查看kubelet是否真的连接上了containerd:

crictl ps -a

最常见的失败原因就两个:镜像缺失(kubeadm init报image not found),或者kubelet和containerd之间的cgroup driver不匹配。排查完修复后,用kubeadm reset清理现场:

kubeadm reset -f rm -rf /etc/kubernetes/manifests /var/lib/kubelet

再重新执行init就可以。kubeadm reset只处理k8s自己的配置,不会动容器镜像和rpm包,所以离线环境里反复init的成本并不高。

5. join节点和网络插件:离线集群最后的两步棋

控制平面起来了,接下来就是让worker节点加入集群,然后装好网络插件让Pod之间真正能通信。

5.1 worker节点加入:离线环境下怎么join最稳

worker节点不需要init,只需要把系统侧准备(第3章)完整做一遍,包括containerd、内核模块、sysctl、rpm包。然后把镜像tar包同样导入到worker节点的k8s.io命名空间,因为kube-proxy、pause、calico-node这些组件也会在worker节点上跑。

如果之前保存过kubeadm join命令,直接在worker节点执行即可:

kubeadm join 192.168.10.11:6443 --token xxxx --discovery-token-ca-cert-hash sha256:xxxx

如果token过期,或者ca-cert-hash字符串找不到了(这是离线项目里特别容易发生的事,因为你可能隔了几天甚至几周才去扩容节点),在master节点上执行:

kubeadm token create --print-join-command

这会打印一条全新的带token和hash的join命令,直接复制到worker上跑就行。

离线环境还有一种更不容易出错的join方式:使用--discovery-file直接指定ca.crt文件。先把master节点上的/etc/kubernetes/pki/ca.crt拷贝到worker节点,然后:

kubeadm join 192.168.10.11:6443 --token xxxx --discovery-file /etc/kubernetes/ca.crt

这种方式绕过了Hash计算和校验,对离线环境更友好。不过要注意token仍然会过期,文件方式只解决了ca证书发现的问题。批量加入大量worker时,建议提前把token的TTL设长一点,或者接受每次扩容都重新生成一次join命令。

5.2 控制平面高可用:如果不止一台master

单master的集群跑测试没问题,但生产环境通常要三台控制平面节点。离线环境下配置HA稍微多几步,但整体也不复杂。

主master init时配置里加上controlPlaneEndpoint,比如:

controlPlaneEndpoint: "192.168.10.100:6443"

这个地址可以是内网负载均衡(比如keepalived VIP),也可以是域名。后面第二台、第三台master加入时用:

kubeadm join 192.168.10.100:6443 --token xxxx --discovery-token-ca-cert-hash sha256:xxxx --control-plane

注意--control-plane参数,它表示这个节点要作为控制平面节点加入,kubeadm会自动把etcd、apiserver等control plane组件在这个节点上拉起。控制平面节点之间的证书分发,kubeadm通过--certificate-key来协商,如果这个参数不方便管理,也可以直接把master1上的/etc/kubernetes/pki整个目录拷贝到其他master节点,更直接。

5.3 安装Calico网络插件:镜像tag、网段、IPIP模式

网络插件我以Calico为例,它在离线环境里的部署足够经典。

先在有网机器上准备Calico的镜像。以Calico v3.29为例,通常需要三个镜像:

docker pull docker.io/calico/cni:v3.29.0 docker pull docker.io/calico/node:v3.29.0 docker pull docker.io/calico/kube-controllers:v3.29.0 docker save -o calico-images.tar docker.io/calico/cni:v3.29.0 docker.io/calico/node:v3.29.0 docker.io/calico/kube-controllers:v3.29.0

这里保持docker.io/calico/...前缀即可,不需要刻意改成registry.k8s.io前缀。导入到每个节点:

ctr -n=k8s.io images import calico-images.tar

然后下载Calico的部署清单(有网机器上可以curl拿官方给的calico.yaml),内网机器也能在打包时一起带入。部署前改两个地方:

  1. 修改Pod网段:把CALICO_IPV4POOL_CIDR改成和kubeadm init里的podSubnet一致,例如172.16.0.0/16。
  2. 确认网络模式:内网环境通常用IPIP或VXLAN模式,取决于你的网络架构。如果所有节点在同一个二层网络,IPIP模式就够了;如果跨VPC、跨SDN网络,用VXLAN更稳。Calico默认的IPIP模式在大多数内网环境都能跑通。

然后应用清单:

kubectl apply -f calico.yaml

等待Calico全部Running:

kubectl get pods -n calico-system -w

当calico-node都变成Running,coredns也不再是Pending之后,整个离线集群的网络就算通了。

6. 离线安装排错实录:这些坑我都替你踩过了

写完流程,惯例要聊聊排错。离线环境的排错和在线环境有个最大的不同:你没有"去网上搜一条现成的解决方法"的便利,很多问题必须自己根据日志和现象倒推。以下几个坑是按实际踩坑频率排序的,可以说每一个都价值半天工时。

6.1 坑一:kubelet起不来,cgroup driver不一致

症状:kubeadm init卡在[kubelet-start] Waiting for the kubelet to start,journalctl -u kubelet里刷错,常见内容包含failed to run Kubelet: failed to get cgroup stats或者cgroup driver: "cgroupfs" is different from docker之类的提示。

原因:containerd的SystemdCgroup没有改成true。kubelet这边用的是systemd,containerd那边还是cgroupfs,两边管理cgroup的机制对不上。

处理:把/etc/containerd/config.toml里SystemdCgroup = false改成true,然后systemctl restart containerd,再kubeadm reset重来。这个坑在麒麟V10上特别容易出现,因为很多人改配置的时候只改了前半段,没有找到runc相关的SystemdCgroup项。

6.2 坑二:镜像导入后tag不对,kubeadm报镜像找不到

症状:kubeadm init执行到一半,报某个镜像image not found,比如registry.k8s.io/kube-apiserver:v1.32.11 not found。

原因:这是离线安装最高频的问题。打包时如果直接从阿里云镜像仓库拉取并保存,没有把tag改成registry.k8s.io前缀,导入后containerd里存的镜像名是registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.11,而kubeadm拿着registry.k8s.io/kube-apiserver:v1.32.11去找,当然找不到。

处理:

ctr -n=k8s.io images list # 确认实际的镜像名前缀 ctr -n=k8s.io images tag registry.aliyuncs.com/google_containers/kube-apiserver:v1.32.11 registry.k8s.io/kube-apiserver:v1.32.11

如果镜像很多,写个循环批量处理更快。我习惯在打包机上save之前就统一tag好,这是最省事、最不会忘的做法。

6.3 坑三:节点NotReady,大量Pod Pending

症状:kubectl get nodes显示master状态NotReady,kubectl get pods -A里coredns一直是Pending。

原因:网络插件没装,或者Calico尚未就绪。NotReady的节点通常意味着CNI没有成功初始化,节点上的Pod因为网络不通无法调度到Ready状态。

处理:先看Calico节点Pod状态:

kubectl logs -n calico-system daemonset/calico-node

常见原因包括:

  • Calico镜像没有导入到当前节点,Pod卡在ImagePullBackOff。
  • 内核没有加载ipip模块,Calico的IPIP模式起不来。
  • firewalld挡住了179端口,BGP peer建立失败。

内网环境最常见的是镜像导入不全,其次是ipip模块缺失。检查一下:

lsmod | grep ipip

如果没有,加载模块:

modprobe ipip

6.4 坑四:token过期,join时提示过期或找不到

症状:过了一段时间再扩容节点,跑之前保存的kubeadm join命令,报token过期,或者token not found。

原因:kubeadm的bootstrap token默认有效期24小时,过期后之前的命令就失效了。

处理:这条真的不用慌,重新生成就行:

kubeadm token create --print-join-command

如果你希望token长期有效,可以加--ttl=0表示永不过期,但建议不要在正式环境这么干,安全风险不值得。token过期问题是我在离线项目里遇到频率最高的管理性小问题,每次去现场扩容第一件事就是先刷新token。

6.5 坑五:SELinux导致的挂载或日志问题

症状:Pod创建失败,日志里报Permission denied;或者节点上的kubelet日志目录无法写入,kubelet直接退出。

原因:麒麟V10默认SELinux可能是enforcing,而k8s组件和容器运行时不总在SELinux策略覆盖的范围内。

处理:内网环境我建议直接setenforce 0并修改/etc/selinux/config为permissive。虽然"关SELinux"不是最优解,但在离线环境里排查SELinux策略的成本太高,permissive模式已经能保证功能正常,同时还能保留SELinux的日志审计能力。

6.6 坑六:系统中残留的docker或旧版本containerd

症状:crictl info报错,或者kubelet连接不上containerd,sock文件路径对不上。

原因:有些镜像系统或者前期试验环境里装过docker,dockerd自身的containerd使用了/run/dockerd/containerd.sock或者别的路径,导致你新装的containerd的/run/containerd/containerd.sock没有创建,或者被旧进程占用。

处理:先确认当前生效的containerd进程:

systemctl status containerd ss -xl | grep containerd

如果发现sock文件属于dockerd,建议把dockerd停掉或禁用,让k8s专属的containerd接管/run/containerd/containerd.sock。在交付环境里,我倾向于让一台机器上只有一套容器运行时,避免dockerd和containerd抢sock、抢资源、抢cgroup这种破事。

最后再分享一点个人经验

离线安装最磨人的地方,其实不是某个命令敲错,而是各种"环境假设不一致"。比如打包机架构跟内网不一致、镜像tag前缀没有统一、containerd的SystemdCgroup忘了改、内网网段和Calico默认网段冲突,这些问题的根源都可以追溯到准备阶段。所以我现在做离线交付时,会先用一晚时间,把物料清单列成一张表:rpm包列表、镜像列表、镜像tag、版本号、校验和,全部记录下来再进内网。

另一个小技巧是:每次导入完镜像,先跑一遍

crictl images | grep v1.32.11

快速核对镜像名称和tag是否齐全,再去执行kubeadm init。这一步虽然看起来多余,但它能帮你把第4章和第6章大部分问题直接挡在爆发之前。离线环境没有百度可查,最可靠的依赖就是自己的checklist和充分的准备。这套流程跑过两三遍之后,你会觉得离线装k8s其实也就那么回事,真正宝贵的是那份耐心和次序感。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/28 22:26:49

Agent-native:从传统系统到智能体优先架构的落地实践

做AI应用两年多&#xff0c;我经手过的Agent项目少说也有十几个&#xff0c;最深的感触是&#xff1a;Agent能不能发挥价值&#xff0c;七成取决于系统架构&#xff0c;三成才取决于模型。今天想聊的agent-native&#xff0c;本质上就是回答一个问题——你是否愿意把Agent当成系…

作者头像 李华
网站建设 2026/9/28 22:26:43

Superpowers实战指南:Java项目中的AI代码生成与重构落地

最近圈子里不少人在聊 superpowers&#xff0c;我第一次看到这个名字&#xff0c;心里想的其实是&#xff1a;又一个花里胡哨的 AI 插件&#xff1f;后来真在 Java 项目里跑通一条完整链路&#xff0c;我才发现这东西跟我想的不太一样。它不单纯是"补全加强版"&#…

作者头像 李华
网站建设 2026/9/28 22:25:20

车辆重识别实战:YOLOv5+ReID从检测到匹配的完整管线

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/28 22:24:47

基于MADDPG的车联网频谱共享与功率控制实战

简介&#xff1a;这份资源面向车联网通信与深度强化学习方向的研究生、算法工程师及科研人员&#xff0c;聚焦高速移动场景下V2I与V2V链路频谱共享中的功率控制与资源分配难题。针对车辆高移动性导致信道快速变化、集中式管理受限的问题&#xff0c;项目将资源共享建模为多智能…

作者头像 李华
网站建设 2026/9/28 22:23:45

Substrate区块链开发框架:从Runtime到Pallet的造链实践

如果你在技术社区里搜索“substrate”&#xff0c;大概率会同时撞见好几个完全不同的东西&#xff1a;材料科学里它是衬底&#xff0c;生物化学里它是底物&#xff0c;而在区块链圈子&#xff0c;Substrate 是一个几乎绕不开的开发框架——Parity Technologies 团队用 Rust 写的…

作者头像 李华
网站建设 2026/9/28 22:23:13

上位机与下位机架构实战:C#/.NET与C++分工及通信协议选型

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华