简介:面向Kylin V10操作系统与ARM架构CPU的Kubernetes部署场景,这套资源合集提供基于containerd运行时搭建K8S 1.26.15多主多从集群的完整物料,涵盖离线镜像包、依赖RPM包、部署脚本、配置模板和二进制工具,适合云原生运维、麒麟系统管理员以及国产化硬件上的K8S落地实践者。包体共47个文件,压缩后约622MB,主要包含13个gz镜像包、11个rpm依赖包、5个shell脚本,以及yml与yaml配置清单、systemd服务文件、kubeadm、kubectl、kubelet二进制和高可用keepalived配置,可支撑从环境准备、依赖安装到集群验证的整个流程。已有255人浏览学习。借助离线镜像导入脚本,能快速加载apiserver、etcd、coredns、calico等组件镜像,并附nginx、busybox等测试镜像;多主多从的keepalived配置模板与kubeadm初始化、加入示例,有助于在ARM加麒麟环境中规避典型踩坑,快速复制出一套高可用集群并校验运行状态,对国产化基础设施实践有较高参考价值。
1. 多主多从集群在 Kylin V10 与 ARM64 上的真正门槛:不是命令复杂,是运行时与镜像
Kylin V10 配上 ARM 架构 CPU,听起来就是装个 Linux 再跑 kubeadm,但真把 1.26.15 的多主多从拉起来时,你会发现每一步都可能卡住:containerd 的沙箱镜像拉不下来、API Server 证书里没有写 VIP、第二台主节点 join 时报 certificate-key 过期。这些都不是 kubeadm 命令本身的问题,而是 ARM 环境里运行时、镜像架构和网络模型叠加出来的组合坑。
这篇文章把我在模拟项目X里用 containerd 部署三主多从的整套方案整理出来:从版本匹配、kubeadm 配置模板,到 worker 接入、Calico 镜像适配,再到五个高频翻车点和一组体检命令。适合正在 ARM 服务器上做高可用交付、手里只有 Kylin V10 机器、需要直接上手复现的读者。
如果你以为多主多从就是多跑几遍 join,那看完第 5 章的排查顺序,应该能少加一个月的班。
2. 控制面运行时选型与系统准备:为什么必须用 containerd,以及三个容易忽略的关键参数
2.1 为什么 K8S 1.26 把 containerd 当成默认运行时:CRI 链路与版本匹配
Kubernetes 从 1.24 开始移除内置的 dockershim,到了 1.26 这个版本里,如果还想用 Docker 当运行时,就必须额外装 cri-dockerd 把 Docker 的接口翻译回 CRI。等于 kubelet 和容器之间多了一层翻译,x86 环境里多一层可能还能忍,ARM 环境里排查问题就多一个黑匣子。
containerd 本身就实现了 CRI 插件,kubelet 通过 unix socket 直接和它通信,链路最短。多主多从集群里,每个节点上的 kubelet 都要负责拉沙箱、拉业务镜像、给 CNI 提供网络命名空间,运行时越简单,出问题的环节就越少。所以我在服务器上一律直接装 containerd,宿主机上不保留 Docker。
版本匹配我是这么选的:kubeadm、kubelet、kubectl 三者严格锁在 1.26.15-0,用 apt-mark hold 防止误升级;containerd 选 1.7 系列,这是 1.26 时代 kubeadm 官方验证过的稳定分支;pause 沙箱镜像用 3.9。crictl 也建议用 1.26 系列,因为排查时 crictl ps 和 crictl logs 的输出格式和 kubelet 最匹配。
| 组件 | 建议版本 | 说明 |
|---|---|---|
| kubeadm / kubelet / kubectl | 1.26.15-0 | 严格一致并锁版本 |
| containerd | 1.7 系列 | 1.26 的 CRI 主流配套 |
| pause 沙箱镜像 | 3.9 | kubeadm 1.26 默认 sandbox_image |
| crictl | 1.26 系列 | 排查容器状态最顺手 |
| Calico | v3.26 系列 | 官方 manifest 同时带 amd64/arm64 |
离线交付时,以上所有二进制都以 deb 包或镜像包形式先落到内网。不要贪新,比如 containerd 2.x 在没有充分验证前任性上手,只会把问题变成别人都看不懂的玄学。
2.2 在 Kylin V10 ARM64 上安装 containerd:sandbox 镜像、crictl 与 systemd_cgroup
Kylin V10 默认不一定带 /etc/containerd 目录,先创建它,再生成默认配置。安装完成别急着启动,先改三个地方:sandbox_image、SystemdCgroup、确认 cri 插件没被禁用。
# 解压 containerd 与 CNI 插件二进制,路径按你实际介质调整 tar -C /usr/local -xzf containerd-1.7*.tar.gz mkdir -p /opt/cni/bin tar -C /opt/cni/bin -xzf cni-plugins-linux-arm64-*.tgz # 生成默认配置 mkdir -p /etc/containerd containerd config default > /etc/containerd/config.toml # 替换沙箱镜像:registry.example.com 是你内网镜像仓库地址,也可以是公共镜像站 sed -i 's#registry.k8s.io/pause:3.9#registry.example.com/k8s/pause:3.9#' /etc/containerd/config.toml # 打开 systemd_cgroup,和 kubelet 保持一致 sed -i 's#SystemdCgroup = false#SystemdCgroup = true#' /etc/containerd/config.toml # 注册系统服务并启动 systemctl enable --now containerd这里最容易被忽略的是 SystemdCgroup。kubelet 默认用 systemd cgroup driver,如果 containerd 保持 cgroupfs,kubelet 起来后健康检查会一直不过,表现是 init 卡在等待 kubelet 启动。另一个重点是 config.toml 里不要照抄网上某些教程去改disabled_plugins = ["io.containerd.grpc.v1.cri"],那是跑纯镜像仓库用的,关掉 cri 插件后 kubelet 根本连不上运行时。
再写一个 crictl 配置,否则 crictl 默认去找 Docker 的 socket:
cat > /etc/crictl.yaml <<'EOF' runtime-endpoint: unix:///run/containerd/containerd.sock EOF验证安装是否正常,不要跳过:
ctr version ctr -n k8s.io images pull docker.io/library/nginx:1.25-alpine ctr -n k8s.io images list | grep nginx-n k8s.io是 kubelet 实际使用的命名空间,这个细节后面排查会反复用到。如果在ctr images list不带命名空间能看到镜像,但在ctr -n k8s.io images list看不到,说明镜像根本没进 kubelet 的视野。
2.3 三个 master 都要做的系统准备:内核参数、swap 与端口放行
多主多从最容易犯的错是只在 master01 上做了系统准备就开跑。三个控制面节点都要跑同样的内核模块、sysctl 和 swap 关闭,任何一台不一致,后面都会变成间歇性故障。
cat > /etc/modules-load.d/containerd.conf <<'EOF' overlay br_netfilter EOF modprobe overlay modprobe br_netfilter 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 EOF sysctl --system swapoff -a sed -i '/[[:space:]]swap[[:space:]]/d' /etc/fstabsysctl 参数的作用是让 iptables 规则能穿透 bridge,Pod 之间的流量才不会被桥接层绕过;ip_forward 是所有 CNI 做 NAT 的前提。swap 必须关,kubeadm 的预检会直接报错,生产环境如果实在不能关 swap,需要在 kubelet 配置里加--fail-swap-on=false,但这不是好习惯,能不碰就不碰。
| 角色 | 必须放行的端口 |
|---|---|
| master | 6443、2379、2380、10250、10257、10259 |
| worker | 10250、179、4789、30000-32767 |
| 所有节点 | 179(Calico BGP)、4789(VXLAN) |
Kylin V10 默认防火墙可能是 firewalld,产线基线如果要求不能关,就把上表端口加入白名单,否则 etcd 集群在三个 master 之间做选举时,2380 被墙会让你看到非常诡异的“时好时坏”。
3. kubeadm 初始化第一个主控节点:1.26.15 的配置模板、证书 SAN 与 join 命令的生成
3.1 先设计控制面:VIP 的 controlPlaneEndpoint 与 certSANs 怎么写
多主多从和单主最大的区别是 kubeadm 需要一个 controlPlaneEndpoint。裸机环境我一般用 keepalived 在三个 master 上漂一个 VIP,云环境就直接接负载均衡器。本文默认 VIP 已经可用,地址用 192.0.2.100,注意这是文档保留地址,真实环境要换成你自己的网段。
kubeadm 1.26 使用的配置 schema 是 v1beta3,网上很多旧模板还是 v1beta1,直接抄过来会在预检阶段报 schema 解析错误。一个能直接用的模板长这样:
apiVersion: kubeadm.k8s.io/v1beta3 kind: ClusterConfiguration kubernetesVersion: v1.26.15 controlPlaneEndpoint: "192.0.2.100:6443" networking: podSubnet: "10.244.0.0/16" serviceSubnet: "10.96.0.0/12" apiServer: certSANs: - "192.0.2.100" - "192.0.2.11" - "192.0.2.12" - "192.0.2.13" - "master01" - "master02" - "master03" clusterName: "k8s-arm" imageRepository: "registry.example.com/k8s" --- apiVersion: kubeadm.k8s.io/v1beta3 kind: InitConfiguration nodeRegistration: criSocket: "unix:///run/containerd/containerd.sock" name: "master01"certSANs 这一节是最容易漏的。多主多从里 kubectl、kubelet 全都会通过 VIP 访问 apiserver,证书里没写 VIP,TLS 校验直接失败。这里我把三个 master 的 IP 和主机名都加进去,确保后续控制面扩容不会踩证书坑。
imageRepository 指向内网仓库,前提是这个仓库里已经同步了 K8S 1.26.15 的 arm64 镜像。如果暂时没有内网仓库,也可以临时保留官方默认地址,但生产交付建议至少把镜像包提前拷到每台节点上。
3.2 初始化 master01:镜像预热、kubeadm init 与 join 命令的重新生成
先把三个 deb 包装好,Kylin V10 上用 dpkg 安装即可,装完立刻锁版本,防止系统更新把 kubelet 拉走:
dpkg -i kubelet*.deb kubeadm*.deb kubectl*.deb apt-mark hold kubelet kubeadm kubectl初始化之前先拉镜像预热。这一步很多人跳过,结果 kubeadm init 一开始就卡在拉镜像上,还以为是网络断了:
# 列出并逐个拉取,提前暴露镜像仓库的 arm64 匹配问题 kubeadm config images list --config /etc/kubeadm/kubeadm-config.yaml ctr -n k8s.io images pull --platform linux/arm64 registry.example.com/k8s/kube-apiserver:v1.26.15实际操作可以写个循环把列表里的镜像全拉一遍。确认全部成功后执行初始化:
kubeadm init --config /etc/kubeadm/kubeadm-config.yaml --upload-certs | tee kubeadm-init.log--upload-certs会把控制面证书的加密密钥上传到集群,后续加 master02、master03 时必须用到对应的 certificate-key。初始化完成后把 admin.conf 放到 kubeconfig 默认路径:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/configkubeadm init 输出的 join 信息不建议直接复制,因为 token 和 certificate-key 都可能过期。更稳的做法是后面需要时现查现用:
# 查 worker 的 join 命令 kubeadm token create --print-join-command # 重新生成带 --control-plane 的证书密钥 kubeadm init --phase upload-certs --config /etc/kubeadm/kubeadm-config.yaml 2>/dev/null | tail -1要注意 certificate-key 的有效期只有两小时,加后续控制面节点最好在一个维护窗口内连续完成。
3.3 加入 master02 与 master03:--control-plane 的证书密钥
第二个和第三个主节点的 join 命令和 worker 完全不同,必须带--control-plane,否则它会被当成普通 worker 加进集群,之后想再提升为控制面节点就非常麻烦。
kubeadm join 192.0.2.100:6443 \ --token <token> \ --discovery-token-ca-cert-hash sha256:<hash> \ --control-plane \ --certificate-key <cert-key> \ --cri-socket unix:///run/containerd/containerd.sockjoin 完成后,master02 和 master03 上同样需要拷贝 admin.conf:
mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config三个 master 都加入后,etcd 会自动组成三成员集群,不需要手动去配 member 列表。你可以先不装 CNI,直接在 master01 上看一眼节点状态,正常的话三个 master 应该是 NotReady,原因是 NetworkPluginNotReady,这是预期的,等第 4 章 Calico 部署完会恢复。
4. 把三主多从接成一张网:Calico 的 ARM 镜像适配、worker 接入与调度角色规划
4.1 Calico 在 ARM64 上的部署:Pod 网段、IP_AUTODETECTION_METHOD 与架构确认
Calico 的 v3.26 系列官方 manifest 是同时支持 amd64 和 arm64 的,只要镜像仓库没有做过架构拦截,直接 apply 通常能跑起来。但有两个地方必须按环境改。
第一是 Pod 网段。kubeadm 配置里写了 10.244.0.0/16,Calico 默认却是 192.168.0.0/16,不一致会导致跨节点 Pod 路由完全不通。下载 calico.yaml 后先改网段:
# 把 Pod 网段替换成 kubeadm 配置里的一致性网段 sed -i 's#192.168.0.0/16#10.244.0.0/16#' calico.yaml kubectl apply -f calico.yaml第二是网卡自动检测。ARM 服务器常常不止一张网卡,Calico 自带的 IP_AUTODETECTION_METHOD 选错接口后,节点之间 BGP 会话建不起来,表现是 Pod 能创建但跨节点 ping 不通。我一般直接在 calico-node 容器的 env 段加一行,限制它用业务网卡:
- name: IP_AUTODETECTION_METHOD value: "interface=eth0"eth0 要按你实际业务网卡名改,改完保存重新 apply,再删除所有 calico-node Pod 让 DaemonSet 重建:
kubectl delete pod -n kube-system -l k8s-app=calico-node部署完确认镜像架构,这一步在 ARM 环境里必须养成本能:
ctr -n k8s.io images list | grep calico ctr -n k8s.io images inspect docker.io/calico/node:v3.26.4 | grep -A5 '"platform"'如果 inspect 出来 architecture 不是 arm64,说明你的镜像仓库把 amd64 的 manifest 推给了节点。后面第 5 章会专门讲这个坑。
4.2 接入 worker 节点并规划角色:label、污点与 etcd 成员验证
worker 节点的 join 命令比控制面简单,关键是不能带--control-plane。很多翻车就是同一份命令复制到 worker 上执行,结果 worker 变成半个 master。
kubeadm join 192.0.2.100:6443 \ --token <token> \ --discovery-token-ca-cert-hash sha256:<hash> \ --cri-socket unix:///run/containerd/containerd.sockworker 加入后给它打上角色标签,后续再配合污点做精细化调度:
kubectl label node worker01 node-role.kubernetes.io/worker= kubectl label node worker02 node-role.kubernetes.io/worker=默认情况下,kubeadm init 会给控制面节点打上node-role.kubernetes.io/control-plane的 NoSchedule 污点,这是保护机制,别为了省事去掉它。业务负载应该落到 worker 上。想给某个 worker 专门跑大数据任务,就给它单独加污点,然后在工作负载的 Pod 模板里写对应 toleration:
kubectl taint node worker01 bigdata=true:NoSchedule三主多从的最后一步验证是看 etcd 成员,不能只看 kubectl get nodes:
etcdctl --endpoints=https://192.0.2.11:2379,https://192.0.2.12:2379,https://192.0.2.13:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ member list -w table看到三个成员都是 started 状态,控制面才算真正成型。这里证书用的是 etcd 目录下的 server.crt 和 server.key,路径别写成 apiserver 的证书。
5. 部署过程中最容易翻车的 5 个问题:从 containerd 沙箱到 etcd 选举的排查顺序
5.1 kubeadm init 卡在等待 kubelet:先查沙箱镜像再查 cgroup
现象:kubeadm init 一直停留在[wait-control-plane] Waiting for the kubelet to boot,最后超时退出。
原因:最常见的是 containerd 没有打开 SystemdCgroup,kubelet 健康检查一直失败;其次是 sandbox_image 指向的 pause 镜像拉不下来。还有一个隐蔽情况:有些人手动执行过ctr images pull没有带-n k8s.io,镜像落在默认命名空间,kubelet 根本看不到。
解决:先看 kubelet 日志确认方向:
journalctl -u kubelet -l --no-pager | tail -50日志里如果反复出现container runtime is down或failed to connect to containerd,回到第 2 章检查 config.toml 的 cri 插件是否被禁用、SystemdCgroup 是否打开、containerd 是否重启成功;如果日志在报拉取超时,就手动拉一次沙箱镜像验证仓库可达性,不要把问题留给集群去猜。
5.2 控制面节点 join 报证书问题:certificate-key 只有两小时寿命
现象:master02 执行kubeadm join --control-plane时,报 certificate-key 不匹配、过期,或者证书校验失败,退出前提示去 master01 重新提取。
原因:--upload-certs生成的 certificate-key 有效期是两小时。如果你在 master01 上用kubeadm token create --print-join-command拿到的命令,默认只生成 worker join 的信息,里面根本没有--control-plane和--certificate-key,直接照抄必然失败。
解决:在 master01 上重新执行kubeadm init --phase upload-certs --config /etc/kubeadm/kubeadm-config.yaml拿新 key,并确认 join 命令里同时带--control-plane和--certificate-key两个参数。如果反复失败,先把目标节点kubeadm reset,清干净再重试,避免残留的 etcd 成员数据干扰加入流程。
5.3 节点反复 NotReady:NetworkPluginNotReady 的通用排查路径
现象:集群所有节点都是 NotReady,kubectl describe node显示NetworkReady=false reason:NetworkPluginNotReady。
原因:CNI 没装成功。Calico 部署失败时节点会一直卡在这个状态,原因可能是 Pod 网段不一致、镜像架构不对、或者网卡自动检测选错接口。注意 containerd 下 kubelet 拉取镜像的命名空间是 k8s.io,用ctr -n k8s.io images list查不到任何 calico 镜像,说明拉取一直没有成功,不是集群在装傻。
解决:按顺序看 Calico Pod 状态和日志:
kubectl get pods -n kube-system -o wide | grep calico kubectl logs -n kube-system calico-node-xxxxx -c calico-node | tail -50日志里出现Felix is not ready,多半是网卡检测问题,用第 4 章的 IP_AUTODETECTION_METHOD 限制接口后重建 calico-node。
5.4 镜像拉起但容器起不来:Exec format error 与 ARM 架构匹配
现象:Pod 能创建,但容器启动即失败,Events 里出现standard_init_linux.go:xxx exec format error,或者应用起来后执行 uname -m 显示 x86_64。
原因:节点是 ARM64,但拉下来的镜像是 amd64。公共镜像仓库有些 tag 只有 x86 manifest,内网代理仓库更麻烦,第一次被 amd64 节点请求后,代理会把 amd64 manifest 缓存住,后面 ARM 节点来拉,拿到的还是 amd64。
解决:先确认镜像服务器对该镜像到底有没有 arm64 manifest:
ctr -n k8s.io images pull --platform linux/arm64 docker.io/library/nginx:1.25-alpine如果这条命令报 manifest 不匹配,说明该镜像源本来就没有 arm64 版本,换支持多架构的镜像,或者在部署清单里明确写带 arm64 后缀的 tag。这个检查要覆盖 pause、calico、metrics-server 等所有附加组件,不能只检查 kube-apiserver。
5.5 三个 master 只有两个健康:etcd 选举异常与端口放行
现象:三主都显示 Ready,但访问 apiserver 时好时坏,kubectl get nodes偶尔超时,某个 master 上的 apiserver 一直重启。
原因:三台 master 之间的 2379 和 2380 端口被防火墙拦截,etcd 选举无法正常完成。另一种是 /etc/hosts 里节点名解析不一致,etcd 成员对不上地址。
解决:先做两个统一动作:三台 master 的 /etc/hosts 写完全一致的主机名映射;防火墙放行 2379、2380。然后用 etcdctl 查哪个端点不健康:
etcdctl --endpoints=https://192.0.2.11:2379,https://192.0.2.12:2379,https://192.0.2.13:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ endpoint health -w tableunhealthy 的端点对应节点,先看它的 kubelet 和 etcd 服务状态,不要直接重启这一台,因为 etcd 三节点宕掉两台的仲裁风险,是集群事故级别的问题。
6. 集群交工前的体检清单与证书维护:用一组命令证明高可用是真的
6.1 交工前跑一遍这条命令链:节点、组件、etcd 三张表
我每次交付前一定会把下面这组命令完整跑一遍,输出截图放进交付文档,后续维保也按这套节奏走。
# 节点状态 kubectl get nodes -o wide # 控制面与插件的 Pod 分布,确认没有全部挤在一个 master 上 kubectl get pods -A -o wide | grep -E 'kube-system|calico' # apiserver 健康检查,直接打 VIP,验证证书和负载均衡链路 kubectl get --raw='/healthz?verbose' --server=https://192.0.2.100:6443 # etcd 三端点的健康状态 etcdctl --endpoints=https://192.0.2.11:2379,https://192.0.2.12:2379,https://192.0.2.13:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ endpoint health -w table看输出时要确认三件事:第一,三个 master 的 kube-apiserver 和 etcd 都在 Running;第二,calico-node 在每个节点上都有一个 Ready 副本;第三,etcd 三个端点全部 healthy。任何一张表里出现单点,都不叫高可用。
6.2 把证书轮换与故障演练写进维护习惯
证书过期是集群宕机里最常见也最不体面的原因。我用过一个教训,模拟项目X 交付后某天接到告警,打开一看控制面证书只剩 7 天,现场没有维护窗口,最后在凌晨做了 renew 并重启了所有控制面组件,虽然没有丢数据,但那一晚基本没睡。从那以后,kubeadm certs check-expiration就是每月巡检的固定动作。
# 在任一 master 上查看所有证书到期时间 kubeadm certs check-expiration如果发现即将到期,执行续期后必须重启 kubelet 和控制面静态 Pod,才能真正生效:
kubeadm certs renew all systemctl restart kubelet再往后,我会每半年演练一次故障切换:挑一台 master 执行systemctl stop kubelet,观察 VIP 是否漂移、apiserver 是否继续可用,然后恢复。这套动作做完,多主多从才不是拓扑图上的摆设。
希望帮到你。
本文还有配套的精品资源,点击获取