
简介面向麒麟V10与ARM架构下的集群运维与开发人员这份资源合集以containerd为运行时提供K8S 1.26.15多主多从集群的完整离线部署物料。资源包为GZ压缩包共47个文件整体约622MB按功能混合收纳gz容器镜像、rpm依赖包、sh部署脚本、conf配置、yml/yaml编排清单、service系统服务以及kubeadm、kubectl、kubelet等二进制工具配合keepalived高可用配置与calico网络插件覆盖从环境准备、镜像导入、主节点初始化到工作节点加入的完整链路。资源当前已有254人学习下载适合在信创或ARM服务器上快速复现生产级集群也可作为研究K8S组件交互与离线部署细节的样例。相比零散网络教程该合集将二进制、配置、脚本与镜像集中归类明显降低手工收集与兼容性排查成本。1. 从 x86 教程照搬到 ARM 麒麟会卡在哪Kylin V10 containerd 的 K8S 1.26.15 多主多从部署同一套 kubeadm 命令在 x86 的 CentOS 上能 20 分钟跑出集群换到 ARM 架构的 Kylin V10 上却会在 containerd 初始化时连续翻车。标题里的 K8S 1.26.15 是 1.26 系列最后一个补丁版本社区维护已经收敛配合 ARM64 的麒麟系统反而是国产化机房交付时试错成本最低的组合。这篇笔记要解决的是按多主多从规格部署这个集群的资源准备和完整落地路径ARM64 的容器运行时怎么选、K8S 组件和 pause 镜像怎么补、kubeadm 的高可用参数怎么给以及最容易让集群虚假高可用的五个坑。适合拿着国产化服务器清单、需要在离线环境里交付生产集群的运维同学照着配。2. 资源合集到底要准备什么从 ARM64 二进制到离线镜像的五大类物料2.1 五类资源清单与选型理由为什么 containerd 版本卡得这么死先来明确标题里“资源合集”的概念。很多人以为资源合集就是一堆下载链接真正落地时你会发现资源合集的含义是把这套集群从零到多主多从所需的外部依赖按“操作系统与内核、容器运行时、K8S 组件、CNI 插件、kubeadm 配置文件”五类整理成一套可审计的物料。ARM 架构下最坑的地方是镜像包名和 x86 不一致yum 源里搜到的 containerd 经常是 1.4 甚至更老直接导致 kubelet 无法用 CRI 协议和它通信。资源类目建议版本/规格用途说明操作系统Kylin V10 SP2/SP3arm64内核 4.19 或 5.10基础环境uname -m 必须是 aarch64容器运行时containerd 1.6.28 ~ 1.7.xarm64 二进制承担 CRI 接口替代 dockerK8S 组件kubeadm、kubelet、kubectl 1.26.15arm64 二进制与标题版本严格一致不能混CNI 插件Calico v3.26 或支持 arm64 的 flannel 变体提供 Pod 网络基础镜像pause:3.6/3.9、coredns、etcd 的 arm64 版本kubelet/集群组件启动必需版本为什么卡得死K8S 1.26.15 对应的 kubelet 要求 CRI 运行时必须支持 CRI v1alpha2 之后再迁移到 v1containerd 1.5 以下版本对 CRI 的实现不完整表现就是 kubelet 日志里报 “container runtime is down”。另一个隐藏约束是 crictl 版本它要和 containerd 匹配我一般直接用 K8S 1.26.15 发布目录里自带的 crictl避免两个二进制之间的兼容性折腾。2.2 在 Kylin V10 ARM64 机器上配置基础环境yum 源、gcc 与工具包拿到机器后动作要快第一步不是装 K8S而是确认架构和 repo 源。Kylin V10 自带源里虽然能装大部分基础包但 gcc 版本往往偏旧热词里有人搜“kylin v10 编译 gcc 12”。如果你只是想编译小工具建议先用自带源里的 gcc不要去源码编译 gcc 12因为交叉编译和真机编译的坑完全不同ARM 机器上源码编 gcc 12 动辄两三个小时还容易因为缺 gmp/mpfr 依赖中断。只有当你确认某些组件需要新特性时才值得走源码路径。# 检查架构必须是 aarch64如果是 aarch32 说明系统装错了 uname -m # 查看系统版本SP1/SP2/SP3 的镜像源地址有差异 cat /etc/kylin-release # 先装基础工具ipset/ipvsadm 是 kube-proxy 用 ipvs 模式时的依赖 yum install -y tar gcc gcc-c make ipset ipvsadm conntrack-tools net-tools yum install -y nfs-utils # 后续持久化存储一般会用 NFS提前装省事参数说明uname -m 输出 aarch64 时才能继续如果输出 aarch32说明这台机器装的是 32 位用户态containerd 的 arm64 二进制会直接报 “cannot execute binary file”。/etc/kylin-release 里记录了 SP 版本配置 repo 时版本号写错会导致 yum 源识别不到。ipset/ipvsadm 是热词里很多人搜“k8s externalips”时忽略的前置依赖不装的话 kube-proxy 会在启动时把 IPVS 规则刷进去但转发不生效。接着处理资源包的传递。工业环境里机器和 yum 源经常物理隔离我的做法是用一台 x86 跳板机把所有 rpm 和 tar 包下载好打成一个 bundle再用内网 scp 或移动介质拷过去。注意一定不要跨架构复制 rpmx86 的 coredns、etcd 镜像在 ARM 机器上会以 “architecture does not match” 被 containerd 拒绝。# 在跳板机上打好包解压产物保留原始目录结构 tar -czf k8s-arm64-bundle.tar.gz \ ./k8s-bin/ \ ./containerd-arm64/ \ ./images-arm64/ \ ./kubeadm-config/ # 在目标机器上解压释放到约定目录后续所有配置都以这个目录为基准 mkdir -p /data/k8s-bundle tar -xzf k8s-arm64-bundle.tar.gz -C /data/k8s-bundle ls -l /data/k8s-bundle这里把解压路径固定为 /data/k8s-bundle 有实际意义Kylin V10 的 /opt 有时被安全策略挂成只读/usr/local 又容易被后续的服务安装覆盖放 /data 底下用单独的目录隔离后面写 containerd 的 systemd unit 和 kubelet 的 kubelet.service 时引用路径不会冲突。3. containerd 部署与配置ARM 镜像拉取和 CRI 联合调用的先决条件3.1 为什么 K8S 1.26.15 必须用 containerd 而不用 docker旧教程里“先装 docker 再装 k8s”的流程在 1.26.15 上已经不适用。K8S 从 1.24 移除 dockershim 后kubelet 只认 CRI 运行时接口containerd 通过自带的 cri plugin 直接暴露 unix:///run/containerd/containerd.sock 给 kubelet中间没有 docker 那一层转换。在 ARM 麒麟环境里docker 的 arm64 镜像在公共仓库里可用性比较差而 containerd 的 arm64 二进制则可以直接从官方 release 目录拿到这也是题目里特意把 containerd 写进标题的原因。选 containerd 而不是 CRI-O我主要是从离线分发角度考虑。containerd 的命令风格和 docker 接近团队里有人熟悉 docker 的话crictl 的切换学习成本很低同时 containerd 只有单一守护进程没有 docker daemon 的 networking 模块出问题时排查面小systemd 管理也干净。对政企机房来说containerd 的配置项就集中在 /etc/containerd/config.toml 一个文件里审计起来比 docker 的 daemon.json 加 iptables 链快得多。3.2 安装 containerd二进制释放、config.toml 修改与 systemd 托管这一步是整个集群的咽喉大部分初始化失败都发生在 containerd 配置不匹配。先把二进制释放到 /usr/local然后生成默认配置cd /data/k8s-bundle/containerd-arm64 tar -C /usr/local -xzf containerd-1.7.23-linux-arm64.tar.gz # 生成默认配置 containerd config default /etc/containerd/config.toml # 启用 CRI 插件里被默认关掉的 SystemdCgroup # 同时把 sandbox_image 改成内网拉得到的 arm64 pause 镜像 sed -i s/SystemdCgroup false/SystemdCgroup true/g /etc/containerd/config.toml逻辑说明containerd config default 会生成一份包含 cri 插件配置的默认模板不生成直接启动也跑但默认 SystemdCgroup 是 false这会让 kubelet 和 containerd 在 cgroup driver 上不一致pod 起来后随时被 OOM Killer 误杀。sed 只改了 SystemdCgroup 一个开关是因为其他默认值在 1.7 系列已经是合理状态动太多反而引入问题。sandbox_image 也要改默认值是 registry.k8s.io/pause:3.8在 ARM 离线环境经常拉不下来。我一般先把 pause 镜像从内网仓库 pull 到本地再把它转成 containerd 认识的地址格式写进 config.toml# 先确认内网仓库能拉 arm64 的 pause拉下来打 tag crictl pull harbor.internal.example.com/library/pause:3.9 ctr -n k8s.io i tag harbor.internal.example.com/library/pause:3.9 \ registry.k8s.io/pause:3.9 # 再写进配置这一步我在离线环境百试不爽 sed -i s#sandbox_image registry.k8s.io/pause:3.8#sandbox_image registry.k8s.io/pause:3.9#g \ /etc/containerd/config.toml参数说明这里的 tag 动作是把内网镜像名映射回 K8S 默认的 registry.k8s.io 域名这样 kubelet 初始化时按默认名字找 pause 也能命中。sed 里的 # 号是分隔符替代常规 /避免与路径里的斜杠混淆。如果内网仓库没有 pause 的 arm64 变体后续 kubelet 会把 pause 容器反复拉起失败表面现象是 kubelet 报 “pulling image ... no such host”。启动 containerd 并交给 systemd 托管再用 crictl 验证cat /etc/systemd/system/containerd.service EOF [Unit] Descriptioncontainerd container runtime Afternetwork.target [Service] ExecStart/usr/local/bin/containerd Restartalways RestartSec10 [Install] WantedBymulti-user.target EOF systemctl daemon-reload systemctl enable --now containerd写这个 unit 时注意 ExecStart 指向 /usr/local/bin/containerd因为默认 PATH 里不包含 /usr/local/bin 的场景会让 systemd 找不到二进制。Restartalways 不是摆设ARM 机器的风扇和电源故障率比 x86 高containerd 死了以后 kubelet 会把整机的 pod 判成 NotReady有 Restart 兜底能撑到巡检发现。3.3 用 crictl 做 containerd 连通性检查版本、拉取、运行三位一体这个检查建议在 kubeadm init 前做一次因为 kubeadm 的预检报告只会告诉你 containerd 不存在不会告诉你 cgroup driver 配错了。# 指定 socket 后能拿到版本信息说明 CRI 插件已启用 crictl --runtime-endpoint unix:///run/containerd/containerd.sock version # 拉取内网 pause 镜像如果失败则回到上一步的 tag 逻辑 crictl pull harbor.internal.example.com/library/pause:3.9 # 真正跑一个测试容器避免只拉不跑、内存映射等运行时问题被隐藏 ctr -n k8s.io run --rm harbor.internal.example.com/library/pause:3.9 test-pause参数说明crictl 走的是 CRI 接口ctr 走的是 containerd 原生接口两者 namespace 一致时才能看到彼此的资源。这里用 k8s.io namespace 做测试是为了验证 kubelet 默认命名空间下的镜像互通。run --rm 跑完即删避免遗留容器占用磁盘。如果这台机器内存只有 16G建议再开一个 watch kubectl get pods 观察内存曲线ARM 服务器经常因为内存不足把 containerd 的 cri plugin 杀到 “failed to start plugin”。4. 用 kubeadm 初始化多主多从集群1.26.15 的关键参数与 join 流程4.1 主节点 initkubeadm-config.yaml 里决定 HA 命运的六个字段多主多从的成败基本集中在配置文件上。kubeadm init 不带 --config 也能跑但默认生成的集群没有 controlPlaneEndpoint第二台 master 永远加不进来。所以我坚持把配置写文件版本化存档出问题可以回去比对。apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.26.15 controlPlaneEndpoint: 10.0.0.200:6443 apiServer: certSANs: - 10.0.0.200 - 127.0.0.1 - master01 - 192.168.1.11 - 192.168.1.12 - 192.168.1.13 extraArgs: authorization-mode: Node,RBAC networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 imageRepository: harbor.internal.example.com/library etcd: local: dataDir: /var/lib/etcd --- apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration nodeRegistration: criSocket: unix:///run/containerd/containerd.sock name: master01 kubeletExtraArgs: cgroup-driver: systemd关键参数逐一说明controlPlaneEndpoint 必须写负载均衡地址生产里通常是内网 LB 的 VIP三台 master 的 apiserver 都注册到这个地址后面如果没有 LB至少要写第一台 master 的 IP否则 join 无法获得集群信息。certSANs 里必须包含 LB 地址和所有 master 的 IP漏了某个 IP 后 kubectl 访问会报证书校验失败。criSocket 显式指向 containerdkubeadm 跳过自动探测避免误判到不存在的 docker.sock。kubeletExtraArgs 里的 cgroup-driver 和 containerd 的 SystemdCgroup 对应这是第 3 章里铺垫的开关在这里闭环。执行 initcd /data/k8s-bundle/kubeadm-config kubeadm init --config kubeadm-config.yaml --upload-certs --v5--upload-certs 的作用是把 etcd 的 CA 证书加密后上传到集群并把解密密钥打印在 join 命令里。没有这个参数后续 master 加入时只能手动拷贝 /etc/kubernetes/pki 下的证书文件在离线环境里非常容易漏文件。--v5 是日志等级失败时把输出保存下来里面能看到具体是证书问题还是镜像拉取问题。init 成功后按输出提示执行 mkdir -p $HOME/.kube、cp /etc/kubernetes/admin.conf $HOME/.kube/config。4.2 第一台之后安装 CNI多主多从模式下网络选型和版本一致性CNI 是集群从 NotReady 到 Ready 的关键。我建议用 Calico 的原因是它对 ARM64 的镜像支持比 flannel 好而且 BGP 模式下跨 master 的 pod 流量可以聚合。装上 Calico 后要检查它的 CRD 里是否用了与 podSubnet 一致的网段不一致的后果是节点看到两个网段pod 创建后一直处于 ContainerCreating。# 安装 Calico 的 operator 方式更稳离线环境先把镜像导入 kubectl apply -f /data/k8s-bundle/calico/calico.yaml kubectl wait --namespace calico-system \ --forconditionready pod \ --selectorapp.kubernetes.io/namecalico \ --timeout180s参数说明calico.yaml 里包含 CRD 和 DaemonSet 定义apply 后 kubelet 会在每个节点拉取 calico-node 镜像。离线环境建议先在每台节点把 calico-node、calico-cni 的 arm64 镜像 ctr -n k8s.io i import 进去否则 apply 之后三个 master 同时拉镜像会把内网仓库带宽打满。kubectl wait 的 --timeout 设 180s超过时间没 Ready 就去看 pod 事件多半是 IP 池冲突或节点网卡名不在 calico 的 autodetection 规则里。4.3 第二第三台 master 加入从 init 输出里复制的命令和 token 有效期init 成功后会打印两段 join 命令。第一段是带 --control-plane 的 master join第二段是 worker join。复制时注意第一段命令要原样保留 --certificate-key 参数它是解密 uploaded certs 的钥匙。# master02/master03 上执行内容因 token 过期会变化注意变量替换 kubeadm join 10.0.0.200:6443 \ --token 9qrkz.abcdef1234567890 \ --discovery-token-ca-cert-hash sha256:xxxx \ --control-plane \ --certificate-key 1234567890abcdef1234567890abcdef \ --criSocket unix:///run/containerd/containerd.socktoken 默认 24 小时有效--certificate-key 生成的 CA 证书上传结果有效期 2 小时。所以多主集群的交付节奏应该控制在先给第一台 master 配好证书和镜像再快速让第二台、第三台在半小时内 join 完成如果中间被打断超过 2 小时不要重新 init 整个集群只需在 master01 上重新生成新的 discovery token 和 certificate-key。4.4 worker 节点加入批量操作时的 hostname 和端口检查worker join 命令简单但批量添加时有两个细节容易翻车hostname 和 apiserver 地址。Kylin V10 的默认 hostname 可能是 localhost.localdomainkubelet 注册节点时会因为名字冲突失败。我通常在每台 worker 先执行 hostnamectl set-hostname worker01再写一条记录进 /etc/hosts。同时确认 worker 能通过 TCP 访问到 LB 的 6443 端口。# 生成新的 token 与 hashmaster01 上执行 kubeadm token create --print-join-command # worker 节点执行 kubeadm join 10.0.0.200:6443 \ --token 新token \ --discovery-token-ca-cert-hash sha256:hash \ --criSocket unix:///run/containerd/containerd.sock5. 麒麟 ARM 环境部署多主多从五个高频翻车点与排查路径这一章列出我在多个交付环境里真实遇到的故障按现象、原因、解决三步讲透。5.1 kubelet 报容器运行时未就绪cgroup driver 不一致现象kubelet 状态 active (running) 但日志不停刷 “failed to initialize kubelet ... container runtime is down”kubectl get nodes 看不到新节点。原因containerd 的 config.toml 里 SystemdCgroup 是 false而 kubeletExtraArgs 里写了 --cgroup-driversystemd两者在 cgroup 驱动上互不认账。解决回到第 3 章确认 sed 改成功用 grep -n SystemdCgroup /etc/containerd/config.toml 看输出是否为 true。改完 systemctl restart containerd然后 kubeadm reset -f 清掉 kubelet 的状态文件重新 init。这个坑的代价是一次假成功集群看着起来了一压测节点就挂。5.2 pause 镜像反复拉取失败sandbox_image 没有适配 ARM现象kubectl get pods -A 里所有 pod 都卡在 Init:ImagePullBackOffcrictl ps 看到 pause 容器退出。原因sandbox_image 指向的 registry 在 ARM 离线环境拉不动或拉到的镜像没有 arm64 变体pod 的沙箱始终建立不起来。解决把 pause 的 arm64 镜像标签对齐到内网仓库再用 sed 改 sandbox_image 指向该地址。改完后用 crictl pull 手动拉一遍确认能拉后再重启 containerd。这里注意不要只改一小节config.toml 里 cri 插件下可能出现多个 sandbox_image 老配置用 grep -n sandbox_image 全量检查遗漏一个就继续翻车。5.3 节点 NotReady 且 Calico 无法分配 Pod IP网段冲突与网卡探测失败现象kubectl get nodes 显示 master 和 worker 都 NotReadycrictl logs 里看到 calico 报 “BGP is not enabled” 或 “could not find a valid IP address”。原因podSubnet 与 Calico 默认 IP 池不一致或 Calico 自动探测网卡时选到了管理口而不是集群网口。ARM 服务器经常有多个网口Kylin 默认把 eth0 做带外管理Calico 拿到的 IP 不在业务网段。解决在 calico.yaml 里把 IP_AUTODETECTION_METHOD 显式写成 interfaceeth1按实际业务网口改并把 IPPool 的 CIDR 改成和 podSubnet 相同。改完 apply重启 calico-node pod。这条经验对热词里搜“kubesphere 可视化集群管理工具”“集群故障转移”的人都适用——先保证网络层 Ready可视化层才转得起来。5.4 第二台 master 加入超时certSANs 漏了 LB 地址现象kubeadm join --control-plane 卡在 “Failed to request cluster info”master02 上访问 apiserver 超时但 worker 节点能用同一地址 join。原因controlPlaneEndpoint 后面的 apiserver 在 master02 做连接验证时需要校验 LB 地址的证书。如果 init 时 certSANs 没写 LB IPapiserver 拿出的证书里找不到该地址TLS 握手直接失败。解决在 master01 上用 kubeadm init phase 重新给 apiserver 生成证书并包含 LB IP或者最省事的做法在 certSANs 里补齐所有 master IP 和 LB IP 后重新 init 一次。多主多从环境下重来一次的成本远低于后续排查证书的时间。5.5 kube-proxy 使用 ipvs 但没有转发外部访问 Service 失败现象集群内部 pod 能互通但用 NodePort 或 externalIP 访问 Service 不通热词里搜“k8s externalips”多半就是掉进这个坑。原因Kylin V10 默认 net.ipv4.ip_forward 为 0或者安全组件拦截了 IPVS 转发。ipvs 规则刷进去了流量进不来也出不去。解决在三台 master 和 worker 上统一执行 echo 1 /proc/sys/net/ipv4/ip_forward写入 /etc/sysctl.d/99-k8s.conf 让它持久化。如果还是不通检查是否被 Kylin 自带的图形防火墙拦截用 firewall-cmd --list-all 查看端口确保 6443、8472、10250 等端口放通。6. 多主多从集群的验证方法三分钟健康检查与一台 master 下线的故障演练6.1 一条命令看全集群状态节点、运行时、证书有效期kubectl get nodes -o wide kubectl get pods -A -o wide | grep -v Running kubeadm certs check-expiration 21 | grep -E admin.conf|apiserver说明kubectl get nodes -o wide 的 RUNTIME 列会显示 containerd://1.7.23确认所有节点运行时一致。第二行命令过滤出不在 Running 的 pod如果有 CrashLoopBackOff 去 crictl logs 看具体镜像。第三行为证书检查1.26.15 的 kubeadm 会自动轮换证书但 etcd 和 kubelet 的证书仍要盯。6.2 故障演练直接把 master01 断电三个 master 的 HA 是否有效不是看文档拍胸脯而是看动作。我用 systemctl poweroff 把 master01 直接断电然后立即在 master02 上观察 30 秒内集群是否还能接受新 pod 调度、在 worker 节点上申请的 deployment 是否仍然健壮工作。注意 etcd 的 leader 切换在 ARM 机器上可能比 x86 慢一点因为磁盘 IO 吞吐不同所以观察窗口放 60 秒。# 在 master02 上观察节点状态 kubectl get nodes | grep master watch -n 5 kubectl get pods -A # 创建一个测试 deployment 并验证 kubectl create deployment ha-test --imagenginx:arm64 --replicas3 kubectl scale deployment ha-test --replicas6 kubectl get deployment ha-test如果 master01 下线后 coredns 和 calico 的 pod 都能自动迁移再把它重新开机并 kubeadm join 回来整个过程才算完整。我第一次交付时没做这个演练结果生产环境遇到底层维护重启一台 masteretcd 因为没有 quorum 直接整个集群只读加了三天班。建议任何多主多从验收都把这一幕跑透确认后再提交报告。做这套验证时我还习惯把 kubectl 的 alias 固化在 .bashrc 里比如 alias kkubectl并在 /root/.kube/config 外再放一份 admin.conf 备份。ARM 机器的板卡故障率不低真到了需要换节点的时候有备份的 kubeconfig 和资源清单重建速度能快一大截。希望这份从资源准备到故障演练的路线能帮你在 Kylin V10 上少熬几个夜也希望你交付时把每一台节点的 kubelet 日志都留档后面回溯问题时比任何截图都管用。本文还有配套的精品资源点击获取