简介:面向国产化麒麟操作系统与ARM架构CPU,以containerd作为运行时部署Kubernetes 1.26.15多主多从集群的完整资源包。压缩包共四十七个文件、约六百二十二兆,涵盖十三个离线镜像包(含Calico、CoreDNS、Pause等)、十一个系统依赖、五个操作脚本、四个配置文件、三个编排清单以及服务管理、高可用负载均衡、集群初始化等核心组件,适合在无外网环境下直接安装使用。已有二百五十四人学习下载。资源面向需要在国产化系统上落地K8S的运维开发人员,尤其适合在非x86平台实践容器编排的读者。内含高可用keepalived配置、主节点初始化与工作节点加入集群的yml模板、镜像批量加载脚本、Calico网络插件声明式清单等,覆盖从环境准备、containerd配置、etcd部署到验证集群状态的完整链路,并附带kubelet服务配置与kubeadm初始化参数文件,能够帮助读者快速搭建高可用集群,规避ARM平台镜像兼容与组件版本不匹配等常见问题。整体目录清晰,便于按阶段对照实践。
1. 麒麟V10 ARM上部署K8S 1.26.15:多主多从从选对运行时开始
在麒麟Kylin V10 ARM架构服务器上部署Kubernetes 1.26.15多主多从集群,最麻烦的往往不是kubeadm本身,而是离线环境下的运行时选型和你手上有没有匹配架构的镜像。这套资源合集把从containerd 1.7.2安装、系统依赖rpm、K8s 1.26.15全套控制面镜像到Calico v3.26.4、keepalived加kube-lb高可用入口都按离线交付的方式打包好了。它对应的是至少三台master加若干worker的真实生产布局,不是单节点demo。适合正在做信创迁移、内网隔离环境交付的运维,以及想在ARM非x86硬件上验证K8s高可用的云原生工程师。我拆这套包时踩了不少ARM架构特有的坑,下面按实际部署顺序讲透。
2. 离线资源包盘点:镜像包、rpm依赖、配置文件各管哪一段
第一次打开这个资源包,如果直接解压,最直观的感受是“什么都有但不知道先干嘛”。我把包里的物料拆成三类:一是用于初始化集群的kubeadm配置文件(first-master、join-master、join-node三个yaml),二是容器镜像tar包(k8s-v1.26.15.tar.gz加上calico镜像tar和pause-3.9.tar.gz),三是离线的系统依赖(pkgs目录的rpm和libseccomp)。建议先把这三类分开,因为部署顺序就是“装依赖→导镜像→跑kubeadm”,顺序反了,后面排查时你会分不清是镜像问题还是依赖问题。
2.1 先摸清三类物料
| 文件/目录 | 身份 | 在部署中的作用 |
|---|---|---|
| k8s-v1.26.15.tar.gz | 控制面组件镜像包 | 包含kube-apiserver、kube-controller-manager、kube-scheduler、kube-proxy、etcd 3.5.10-0、coredns v1.9.3、pause 3.9 |
| calico-cni-v3.26.4.tar.gz 等 | 网络插件镜像 | 提供CNI、Pod网络与NetworkPolicy |
| pkgs目录 | rpm依赖 | ipvsadm、conntrack、ebtables、socat、ipset、libseccomp等,kube-proxy ipvs模式与containerd的前置 |
| kubeadm-first-master.yml | 集群引导配置 | 第一个master初始化 |
| kubeadm-join-master.yml | 控制面扩容配置 | 后续master接入 |
| kubeadm-join-node.yml | 工作节点接入配置 | worker节点接入 |
| kube-lb.conf / kube-lb.service | 高可用入口 | 提供VIP:6443的四层转发 |
| keepalived master/backup | VIP漂移机制 | 让VIP跟着健康的主节点走 |
| get_images.sh / load_images.sh | 镜像打包/导入脚本 | 离线镜像备份与还原 |
对照k8s-v1.26.15.tar.gz内的镜像文件名,v1.26.15配套的etcd是3.5.10-0,coredns是v1.9.3,pause是3.9,这几个tag和kubeadm默认期望是对得上的。如果你从网上的x86教程直接找deb/rpm装kubelet,版本一旦差一个小版本,kubeadm init时就会出现组件版本不一致或者连不上apiserver的问题。离线环境里版本锁死反而是优势,少了很多“顺便升级”的冲动。
2.2 get_images.sh 与 load_images.sh:镜像怎么打包、怎么还原
资源包里的get_images.sh,作用是在一台能联网的机器上把镜像打成tar;load_images.sh则是在内网节点上批量导入。两个脚本的实际内容大致如下。
#!/bin/bash # get_images.sh 在联网机器上拉取并导出镜像 IMAGES=( "registry.k8s.io/kube-apiserver:v1.26.15" "registry.k8s.io/kube-controller-manager:v1.26.15" "registry.k8s.io/kube-scheduler:v1.26.15" "registry.k8s.io/kube-proxy:v1.26.15" "registry.k8s.io/etcd:3.5.10-0" "registry.k8s.io/coredns:v1.9.3" "registry.k8s.io/pause:3.9" ) for img in "${IMAGES[@]}"; do ctr -n k8s.io images pull "$img" done ctr -n k8s.io images export k8s-v1.26.15.tar.gz "${IMAGES[@]}"这里的-n k8s.io是指定containerd的命名空间。containerd默认命名空间是default,而kubelet通过CRI创建容器时用的是k8s.io命名空间。如果拉镜像时没带-n k8s.io,后面导入的镜像会落在default里,kubelet照样看不到,这是非常隐蔽的坑。IMAGES数组里的tag必须和资源包文件名一致,不要手滑改成latest,离线环境一旦不一致,crictl images里看到的列表会让你怀疑人生。
#!/bin/bash # load_images.sh 内网节点批量导入离线镜像 for archive in k8s-v1.26.15.tar.gz calico-cni-v3.26.4.tar.gz pause-3.9.tar.gz; do ctr -n k8s.io images import "$archive" done导入逻辑很简单,循环遍历tar包并执行ctr import。需要留意的是:import是整体导入,重复导入会提示already exists,不影响后续使用。导入完成后用ctr -n k8s.io images list看一眼,确认每个镜像的repo:tag都出现在列表里。不要用docker load,这套环境根本没有docker,用了反而会误导自己。
2.3 系统依赖:先装rpm再动kubelet
pkgs目录里的rpm覆盖了以下几类:sysstat用于性能排查,ipvsadm用于kube-proxy的ipvs模式,conntrack和ebtables负责流量追踪相关的内核交互,socat是kubeadm做端口连通性检查时依赖的命令,ipset是IP集合管理,libseccomp则是containerd运行容器时做系统调用过滤的底层库。Kylin V10的base源如果在内网根本连不上,直接用本地rpm装是最稳的。
# 在每台节点上提前装好,假设pkgs与当前目录同级 cd pkgs yum localinstall -y ./*.rpmlocalinstall的意义在于只解析本地目录的rpm依赖,不会去访问外网仓库。装完依赖后,还要确认内核模块和系统参数。常见做法是检查overlay与br_netfilter模块是否加载,并在/etc/sysctl.d/里配置net.ipv4.ip_forward=1,因为后续Calico的转发依赖这个开关。Kylin V10默认内核一般没问题,但离线环境最忌讳想当然,每条节点都执行一遍求证掉。
3. containerd 1.7.2:先把容器运行时这层立住
Kubernetes 1.26之后,kubelet走CRI直接对接容器运行时,docker在那个链条里的位置已经被替代。多主多从环境里每台节点多跑一个docker daemon,就多一个进程要盯着,也多一层转换失败的可能。这套资源包选用containerd 1.7.2的ARM64版本,是我推荐的生产路子之一。
3.1 为什么用containerd而不是docker
docker要接入K8s,必须通过cri-dockerd这个中间层把docker的API翻译成CRI,多一层服务就多一个黑匣子。containerd从1.1版本开始内置CRI插件,kubelet可以直接和它通信,少一跳链路意味着少一类“插件版本不匹配”的报错。更重要的是,cri-containerd-cni-1.7.2-linux-arm64.tar.gz解压后自带CNI plugin二进制,Calico只需要提供配置文件,不需要额外装一遍flannel或其他CNI工具。
ARM环境下还有个现实问题:自己编译containerd要处理Go交叉编译和一堆依赖,官方发布ARM64 tar包直接用就行,省掉一个巨大编译现场。安装命令很简单,但路径要盯住。
tar -C /usr/local -xzf cri-containerd-cni-1.7.2-linux-arm64.tar.gz systemctl daemon-reload systemctl enable --now containerdtar包解压会把containerd二进制放到/usr/local/bin,CNI插件放到/opt/cni/bin,并把containerd.service写到/etc/systemd/system。enable --now是开机自启加立即启动。装完后用containerd --version验证版本号,如果输出里带的版本不是1.7.2,说明系统里可能残留了旧版二进制,常见原因是/usr/bin和/usr/local/bin同时存在两份,需手动删掉旧的。
3.2 config.toml只动两处:sandbox_image与SystemdCgroup
containerd的配置文件默认在/etc/containerd/config.toml,有些版本的tar包会把它放在/usr/local/etc/containerd/config.toml。先确认路径再改,别用find凭感觉。这份配置里对K8s最关键的就是两个点。
version = 2 [plugins."io.containerd.grpc.v1.cri"] sandbox_image = "registry.k8s.io/pause:3.9" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc] runtime_type = "io.containerd.runc.v2" [plugins."io.containerd.grpc.v1.cri".containerd.runtimes.runc.options] SystemdCgroup = truesandbox_image必须和资源包里的pause-3.9.tar.gz保持一致。kubelet创建每个Pod的时候先要拉一个pause容器作为沙箱,拉取地址就是这里指定的值。如果tar包里tag是registry.k8s.io/pause:3.9,配置里也必须是这个,不能写docker.io/pause:3.9。写错的结果是crictl images列表里明明有pause,kubelet却一直报image not found,因为它在找的是另一个仓库前缀。
SystemdCgroup = true是让containerd使用systemd的cgroup驱动,和kubelet的cgroup driver对齐。K8s 1.26官方推荐这种统一方式。如果不设,containerd默认用cgroupfs,kubelet却按systemd驱动管理,节点Node状态可能是Ready,但Pod调度上去后会在ContainerCreating卡很久,最后以cgroup driver mismatch收场。这是离线部署里出现频率最高的隐性坑,改完配置一定要重启containerd。
systemctl daemon-reload systemctl restart containerd crictl images重启后立刻用crictl images确认镜像列表。如果列表是空的,回看上一章的错误做法:导入镜像时没用-n k8s.io。crictl默认连接的正是k8s.io命名空间,所以ctr导入时必须带-n k8s.io。
3.3 crictl配置与验证
crictl是K8s配套的调节工具,它不走docker,直接对containerd发指令。先把客户端配置文件写好。
runtime-endpoint: "unix:///run/containerd/containerd.sock" image-endpoint: "unix:///run/containerd/containerd.sock" timeout: 10 debug: false这个yaml写到/etc/crictl.yaml。runtime-endpoint让crictl知道该连哪个socket;image-endpoint与runtime-endpoint保持一致;timeout和debug不多讲。配置完后,crictl ps -a能看到所有运行中的sandbox容器,crictl logs可以代替docker logs看Pod日志。如果某台机器上crictl连不上,优先查socket路径是否存在以及containerd是否真的起来了。
4. 多主多从控制面:kubeadm配置、VIP入口与两个不同的join
多主多从的高可用布局,核心不是把kubeadm init跑三遍,而是让每台master都成为控制面的一员,同时对外提供一个统一入口。资源包里的kube-lb和keepalived就是干这件事的。
4.1 高可用入口:VIP加四层转发
标准的布局是:三台master节点上都跑apiserver、controller-manager、scheduler和etcd,三者通过etcd选举和lease机制形成共识。在它们前面,kube-lb承担四层负载均衡角色,把VIP上的6443流量转发到各台master的6443。keepalived的两个配置(master/backup)决定VIP当前落在哪台机器上,哪台master的kube-lb不健康,VIP就自动漂到下一台。
worker节点完全不用关心哪台master在实际服务,它们只需要固定访问VIP:6443,这就是控制面高可用的入口。kubeadm配置里的controlPlaneEndpoint就是给这个入口用的。
4.2 kubeadm-first-master.yml关键字段
资源包里的kubeadm-first-master.yml是第一个master的初始化依据。我拆出来核心字段大致如下。
apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.26.15 controlPlaneEndpoint: "192.168.10.200:6443" networking: podSubnet: "10.244.0.0/16" serviceSubnet: "10.96.0.0/12" apiServer: certSANs: - "192.168.10.200" - "127.0.0.1"controlPlaneEndpoint必须填VIP的地址和端口,这个值会写进apiserver的证书SAN里,其他组件连接控制面时统一用这个地址。certSANs里除了本机IP,必须包含VIP的IP,否则kubelet回连apiserver时证书校验会失败,表现为节点Ready后过一会儿又NotReady,证书错误在日志里非常隐晦。podSubnet是给Calico用的地址池,这里设置成10.244.0.0/16,后面Calico的IP池要和它一致,不一致会出现Pod网络冲突,kubectl get nodes倒是正常,但Pod互相ping不通。
kubernetesVersion必须严格写成v1.26.15。kubeadm init时会用这个字段决定拉取哪套镜像tag,离线环境里版本写错,它就会去远程仓库找,直接卡死在拉镜像阶段。
初始化命令:
kubeadm init --config kubeadm-first-master.yml --upload-certs--upload-certs会把控制面证书加密上传到集群,它是后续master join时拿certificate-key的前提。我一般建议加--ttl 0延长证书上传的有效期,避免搭到一半还要重新生成。
4.3 后续master与worker的join差异
第一个master初始化完成后,后面两台master和所有worker节点要分别处理。区别只在一个参数,但错了就是集群架构性错误。
# 后续master加入控制面 kubeadm join 192.168.10.200:6443 \ --token <token> \ --control-plane \ --certificate-key <key> \ --discovery-token-ca-cert-hash sha256:<hash> # worker节点加入 kubeadm join 192.168.10.200:6443 \ --token <token> \ --discovery-token-ca-cert-hash sha256:<hash>后续master的join必须带--control-plane,同时要提供certificate-key;worker节点完全不需要这两项。token默认有效期24小时,超时后join命令直接报token过期。处理办法是用kubeadm token create --print-join-command --ttl 0生成一条永久有效的join指令,里面已经包含token和ca-cert-hash,直接复制到目标节点执行即可。
4.4 kube-lb与keepalived的落地顺序
kube-lb.conf的内容以资源包里的模板为准,常见做法是定义一个vrrp_instance加健康检查脚本。要注意网卡名在ARM服务器上经常是eno1、ens3或者bond0,而不是传统的eth0。keepalived配置里写死eth0的话,启动时会直接报Interface not found,VIP自然也起不来。
服务启动顺序要保证kube-lb先于keepalived。如果keepalived先拿到VIP,流量打到6443时kube-lb还没起来,健康检查脚本会判定失败,VIP马上漂走,形成一种反复横跳的假象。正确做法是把kube-lb设为keepalived启动的前置依赖,或者干脆在健康检查脚本里同时检测本机6443端口是否真的监听。
5. ARM环境排查实录:镜像tag、cgroup驱动与高可用入口的五个坑
拆这套包的过程里,最经典的翻车都集中在“镜像tag对不齐、cgroup驱动不一致、VIP来回漂移”这几类问题上。下面按现象、原因、解决,一段段记录下来。
5.1 镜像加载成功但Pod全部ContainerCreating
现象:ctr import提示导入成功,crictl images也能看到pause:3.9,但所有调度出来的Pod一直ContainerCreating,kubelet反复报告Failed to create pod sandbox,理由是image not found,或者直接超时。
原因:sandbox_image没改对。kubelet创建sandbox用的镜像地址来自containerd config.toml里的sandbox_image,不是crictl里能看到就代表能用。多数情况是kubeadm-first-master.yml里配置了国内仓库前缀,比如registry.aliyuncs.com/google_containers,而tar包里的镜像tag是registry.k8s.io,两者前缀不同,kubelet按yaml里的地址去找镜像当然找不到。
解决:统一镜像tag。要么把config.toml里的sandbox_image改成tar包里的实际tag,要么用ctr打tag把镜像改成yaml里那个地址。我一般选择打tag,因为kubeadm init时拉取控制面组件也需要仓库前缀一致。
# 示例:把pause镜像改tag,使其匹配yaml中的仓库地址 ctr -n k8s.io images tag registry.k8s.io/pause:3.9 registry.aliyuncs.com/google_containers/pause:3.9 systemctl restart containerd打完tag后crictl images里会出现两个相同ID的pause镜像,kubelet用哪个取决于config.toml指向哪个。改完最好用crictl pull registry.aliyuncs.com/google_containers/pause:3.9预拉一次,拉成功了再继续后面的流程。
5.2 keepalived脑裂:VIP出现在两台上
现象:两台master上都用ip addr能看到同一个VIP,客户端访问6443时通时不通,keepalived日志没有明显报错,反复切换。VIP像墙头草一样来回飘。
原因:keepalived默认走VRRP组播,内网交换机或者云环境屏蔽了组播报文,两台机器收不到对方的VRRP通告,都以为自己是运维才允许的。另一种情况是健康检查脚本判定逻辑有误,比如脚本返回0代表正常,但脚本写成返回0代表异常,结果健康节点被误杀,VIP被抢走。如果master和backup的priority差值太小,比如一个是100一个是90,网络抖动时也会频繁抢占。
解决:把VRRP切到单播模式,在配置里显式指定unicast_peer列表,填上各master的实际IP,避免依赖组播。vrrp_script的interval适当调大,比如2秒,脚本内部检测的是本机kube-lb对127.0.0.1:6443的连通性,返回0表示正常。主节点priority设150,备份节点100,差距拉开并开启nopreempt,防止正常节点被误抢。
5.3 ARM上Calico报autodetect失败或一直CrashLoop
现象:calico-node的Pod在ARM节点上反复重启,日志里看到类似Failed to auto-detect an IP address或者Unable to find a usable interface的信息。另一类表现是启动卡在initContainer阶段,提示找不到合适的网卡。
原因:Calico需要选择本机出网网卡来做IP自动检测。ARM服务器的网卡命名通常是eno1、ens3,而Calico默认的IP_AUTODETECTION_METHOD是first-found,它会选到一个没有实际路由的接口甚至回环接口。另一个隐藏问题是ARM节点的内存通常不大,Calico v3.26的felix进程不限制资源的话,内存稍微紧张就被OOM杀掉,表现就是Pod反复重启。
解决:在calico.yaml环境变量里把IP自动检测方式改成接口正则,比如IP_AUTODETECTION_METHOD: interface=ens.*或直接写死网卡名interface=ens3。内存小的节点把felix的request降低,limit去掉,优先保证它能跑起来。
# calico.yaml中Deployment/Node的env片段示意 - name: IP_AUTODETECTION_METHOD value: "interface=ens.*"改完配置重新应用calico.yaml,旧的calico-node Pod会被滚动重建。如果还是起不来,看InitContainer日志,常见的是CNI配置目录权限问题,在ARM麒麟上/opt/cni/bin的owner必须是root。
5.4 kubeadm init卡在等待控制平面
现象:kubeadm init执行到[wait-control-plane]阶段就停住,systemctl status kubelet显示Active,但kubelet日志里不断刷cgroup driver相关报错,或一直提示无法连接localhost:10248。
原因:cgroup驱动不一致。kubelet通过10-kubeadm.conf指定了--cgroup-driver=systemd,而containerd的config.toml还停在默认的cgroupfs。两者驱动不一致,kubelet起不来,控制面组件自然无法上报健康状态。
解决:先停掉kubelet,把containerd的SystemdCgroup改成true,重启containerd,再启动kubelet。如果之前已经反复init失败过,执行kubeadm reset清掉残留状态再重新init。不要只改一个组件然后重复init,那样只是在原地打转。
systemctl stop kubelet # 改好 /etc/containerd/config.toml 后 systemctl restart containerd systemctl start kubelet kubeadm reset -f kubeadm init --config kubeadm-first-master.yml --upload-certsreset是后悔药,但它只在最后一步才值得用。前面检查不完整时,reset了也没用。
5.5 join master与join node用错文件
现象:第二台master用了kubeadm-join-node.yml去加入,命令显示node joined成功,kubectl get node里也能看到这台机器是Ready,但集群的etcd成员只有一个在跑,apiserver还是单实例,这台机器一旦宕机,集群控制面当场不可用。
原因:加入控制平面和加入工作节点走的是完全不同的路径。master join需要获取控制面证书并在本地拉起apiserver、etcd等静态Pod,worker节点join只启动kubelet和kube-proxy。用node的配置加master,等于把它当纯worker用了。
解决:后续master必须使用kubeadm-join-master.yml,并携带--control-plane和--certificate-key参数。如果init时没有打印出certificate-key,在第一台master上执行kubeadm init phase upload-certs --upload-certs重新上传证书,拿新打印的key去join。token过期就重新kubeadm token create --ttl 0 --print-join-command生成。
6. 集群起来后先别急着跑业务:证书期限与高可用自检
集群搭完最怕的是“看起来Ready”,真出问题时无路可退。我每次交付多主多从,都会先跑一轮自检,顺便把证书和token的账算清楚。
6.1 三组命令确认集群真的健康
kubectl get nodes -o wide kubectl get pods -n kube-system -o wide kubectl get --raw='/readyz'第一组看节点角色、内核版本和内部IP;第二组看每台master上的etcd、apiserver、calico是否都在各自节点上拉起了副本,重点检查是否存在大量Evicted或Pending;第三组对apiserver做详细健康检查,返回ok才是真健康。这三组命令都过了,再考虑往集群里跑业务。
6.2 证书期限与token过期不靠玄学
1.26.15控制面证书默认一年,多主多从里每台master的证书独立管理,每年最容易踩坑的就是证书过期。检查命令是固定的:
kubeadm certs check-expiration它会列出apiserver、etcd-server、front-proxy等证书的过期时间和剩余天数。快到期时用kubeadm certs renew all就地续期,然后重启这台上master的kubelet以及apiserver、controller-manager、scheduler静态Pod。token过期则用kubeadm token create --ttl 0 --print-join-command重新生成。
从那以后,我每次搭完多主多从,不论环境多赶,都会强制走一遍kubectl get nodes、readyz检查以及keepalived VIP手动漂移测试,确认答辩完事再交付。这三个动作帮我挡掉了不少事后很难查的隐形故障,希望帮到你。
本文还有配套的精品资源,点击获取