
最近给一批ARM64服务器搭Kubernetes集群操作系统用openEuler容器运行时没有走常规的Docker路线而是选了openEuler生态里的iSula。整套环境从换源、装运行时、到集群Ready前前后后折腾了快两天踩了不少坑很多问题在x86上根本不会出现。这篇就把整个部署过程里最关键的部分拆开讲包括为什么选iSula、CRI socket怎么对接、kubeadm初始化时哪些参数必须显式指定以及ARM64环境下镜像架构和网络插件最容易绊人的几个点。内容和步骤都是实际跑过的直接照着做基本能通适合正在搞国产化ARM基础设施、或者需要在信创项目里落地容器云平台的朋友参考。1. 方案选型为什么是 openEuler iSula 而不是 Docker Family1.1 从 x86 迁到 ARM64容器化为什么是绕不开的一步国产化替代推进到现在ARM64服务器的比重越来越高但很多业务团队还停留在“把二进制拷过去跑一下”的阶段真到上线就发现大量依赖需要交叉编译、动态链接库缺失、发行版不兼容。把业务容器化本质上就是把运行环境连同依赖一起打包让ARM64上的部署从“逐台适配”变成“一次构建、随处运行”。所以在ARM64上先搭一套Kubernetes平台基本是所有迁移工作的前置条件。不过ARM64和x86在基础设施层的差异比很多人想的大。最典型的一点容器镜像是有架构标签的拉一个amd64的镜像到arm64节点上要么直接报exec format error要么启动后行为诡异。另一个差异体现在内核模块和系统组件上像iptables后端、cgroup版本、内核模块是否需要加载这些在x86上一般默认就OK但在某些ARM64板卡和服务器上需要手动处理。这也是为什么选一个跟操作系统深度绑定的容器运行时能省掉不少适配成本。1.2 iSula 与 Docker 架构差异以及它凭什么能对接 K8sDocker大家都很熟了但Docker对接Kubernetes其实一直有个历史问题K8s原生是通过CRI接口跟容器运行时通信的而Docker并不直接实现CRI1.24版本之前靠内置的dockershim做转换之后dockershim被移除得额外起一个cri-dockerd才能继续用。整套链路过长kubelet → cri-dockerd → dockerd → containerd → runc每一层都是状态和开销。iSula不一样。它的核心守护进程叫isulad在设计上直接把CRI接口做成了内置能力启动后直接暴露一个Unix socket供kubelet调用。整体链路是kubelet → isulad → OCI运行时中间不插任何适配层。架构上更扁平资源占用也更低尤其适合2C4G、4C8G这类资源不算宽裕的ARM节点。iSula本身遵循OCI规范标准容器镜像可以直接拉取运行不需要做格式转换所以从Docker迁移到iSula并没有想象中那么痛苦。1.3 什么业务场景适合这套组合这套组合不是要取代通用公有云方案而是有明确的适用场景。国产化要求明确的政企/金融/能源项目硬件平台是ARM64服务器操作系统要求选用openEuler这类国产发行版整体软件栈越统一越容易过合规审查。边缘计算和嵌入式场景节点硬件资源少、规格杂iSula轻量级、启动快的优势能直接转化为部署密度。已有openEuler存量环境的运维团队在openEuler上继续用iSula版本跟随OS一起走升级节奏一致不需要额外维护一套Docker的版本策略。反过来说如果团队已经非常熟悉containerd/Docker并且没有合规约束那继续用成熟路径也没问题。工具是为人服务的方案选型首先考虑团队可运维性。1.4 版本选型参考我这次用的版本组合实测兼容性没问题组件版本说明openEuler22.03 LTS SP3自带iSula及运行时依赖包dnf直接安装iSula2.1.xopenEuler源内版本内置CRI支持Kubernetesv1.28.5kubeadm部署支持CRI socket直接指定Kubernetes镜像源多架构公共仓库需确认仓库镜像包含linux/arm64标签网络插件Calico v3.28默认支持multi-archVXLAN模式在ARM上稳定内核5.10满足overlay、vxlan等模块要求版本定下来之后就不要频繁变动。我见过有团队把K8s从1.26升到1.27后isulad跟CRI接口的兼容性出问题整个集群的pod重新调度了一遍。升级前一定先看版本兼容矩阵。2. 部署前置系统调参与基础环境少做一项都可能在半夜报警2.1 节点基础配置与网络规划先把节点身份规划好。我这边是2个节点1个控制平面master011个工作节点node01IP规划是管理网段和Pod网段分开避免路由冲突。# 设置主机名 hostnamectl set-hostname k8s-master01 # worker节点执行 hostnamectl set-hostname k8s-node01 # 编辑 /etc/hosts保证节点间可以通过主机名解析 192.168.20.10 k8s-master01 192.168.20.11 k8s-node01不要跳过hosts这一步。K8s组件之间大量使用主机名通信尤其是kubelet向apiserver注册节点时如果主机名解析不到直接导致Node无法加入集群。时间同步也提前搞定。证书校验、token过期时间全都依赖时钟偏差超过5分钟kubeadm init大概率会报错而且报错信息很迷惑一会儿是certificate过期一会儿是connection refused。装chrony然后指向内网时间服务器即可dnf install -y chrony systemctl enable --now chronyd chronyc sources时钟同步、hosts解析、主机名规划这三件事建议以统一的表格发放给所有节点执行避免每台机器手工操作造成配置漂移。2.2 内核模块与系统参数Kubernetes的kube-proxy和CNI插件对网络栈有明确要求。需要加载overlay和br_netfilter并且把iptables对bridge流量的处理打开否则节点上的Service转发和Pod间通信会出各种奇怪问题。cat /etc/modules-load.d/k8s.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这里有个ARM64环境比较常见的坑br_netfilter在部分ARM64板卡内核里没有默认编译执行modprobe br_netfilter会报Module not found。碰到这种情况需要先确认内核是否开启该模块没开启就换一个内核版本或者用VXLAN等不需要bridge filter的网络方案。我在一台紧凑型ARM盒子上就遇到过最后换用了带完整内核模块的openEuler内核包才解决。swap也需要关闭。kubelet虽然可以加参数容忍swap但生产环境强烈建议关掉避免内存回收导致Pod性能抖动swapoff -a sed -i /swap/s/^/#/ /etc/fstabfirewalld在测试环境直接停掉最省事生产环境按安全策略放行端口也行。但要注意如果你在openEuler上看到iptables相关报错先检查系统默认的nftables后端是否和你的容器网络方案冲突这个后面在问题排查部分细说。2.3 cgroup 版本与驱动的确定openEuler 22.03默认使用cgroup v2这意味着kubelet和容器运行时的cgroup驱动必须统一。我这边全部采用systemd驱动配置方式在kubelet和isulad两处分别设置对应关系在后面第4章会展开。很多人忽略cgroup驱动一致性结果kubelet启动后直接拒绝工作日志里反复出现Container runtime does not use systemd cgroup driver。这个问题的本质是K8s的CPU、内存QoS能力依赖cgroup路径systemd和cgroupfs各自管理的目录层级不同运行时和kubelet如果各管各的资源限制就完全失效。所以这个不是配置“能不能跑”的问题而是“资源隔离对不对”的问题。3. 容器引擎 iSula 的安装与 CRI 配置3.1 安装 iSula 及启动验证openEuler源里已经带了iSula相关包不需要额外添加第三方源dnf install -y isula systemctl enable --now isulad systemctl status isulad安装完成后先用isula info看一下运行时的基本信息确认当前使用的OCI runtime和存储驱动。如果输出正常再确认socket文件是否生成ls -l /var/run/isulad.sock这个socket文件是后面kubelet对接的关键如果没生成说明isulad的CRI功能没有正确打开需要检查配置。3.2 修改 daemon.json打开 CRI 并锁定 cgroup 驱动iSula的主配置文件在/etc/isulad/daemon.json。这个文件里最核心的三个配置项是是否启用CRI、CRI socket路径、cgroup驱动类型。我这次配置如下{ registry-mirrors: [ https://hub-mirror.example ], cgroup-manager: systemd, cri: { enabled: true, socket: /var/run/isulad.sock }, log-level: INFO }注意registry-mirrors这里只是一个示意实际使用时要替换成你环境能访问的镜像加速地址。配置里的socket路径必须和后面kubelet的--container-runtime-endpoint参数完全一致这是最容易被忽略的隐性关联点。修改配置后重启isuladsystemctl restart isulad用crictl验证CRI接口是否正常响应cat /etc/crictl.yaml EOF runtime-endpoint: unix:///var/run/isulad.sock image-endpoint: unix:///var/run/isulad.sock timeout: 5 EOF crictl version crictl ps -a如果crictl version能正常返回版本信息说明isulad的CRI服务已经就绪。这里我多说一句crictl这个工具在排查K8s问题时是神器crictl ps、crictl logs、crictl inspect能直接绕过kubelet去查运行时状态后面所有CRI相关的疑难杂症都要靠它。3.3 为什么不需要额外安装 cri-dockerd这是iSula对比Docker路线的最大优势。Docker在K8s 1.24之后要接上CRI必须再多起一个cri-dockerd进程而iSula的isulad本身就把CRI能力内置了。你可以把isulad理解为一个“自带CRI的containerd”少一层中间转换排查链路短一截故障点就少一处。更重要的是isulad对ARM64的适配不是后加的而是从设计层面就针对ARM指令集做过优化。在实际压测里同规格的ARM64节点用isulad跑满Pod时内存占用比dockerdcontainerd组合要低不少。3.4 镜像拉取验证确认架构是 arm64配置完iSula后先拉个基础镜像测试一下顺便确认架构isula pull busybox:1.36 isula inspect busybox:1.36 | grep -i arch输出里应该能看到architecture : arm64之类的字段。这一步是ARM64环境下最容易出问题的点。很多镜像仓库里的同一个tag可能有多个架构版本也可能只有amd64版本。如果拉下来的是amd64运行时会直接报exec format error而且这个错误不细看很容易被误判为“系统缺少某个动态库”。拉完镜像再顺手确认当前节点的内核和系统架构uname -m预期是aarch64。如果显示x86_64说明你可能在虚拟机里没把CPU模式调对后面所有工作都会跑偏。4. 使用 kubeadm 初始化 Kubernetes 集群4.1 安装 kubelet / kubeadm / kubectlK8s组件不在openEuler默认源里需要额外配置软件仓库。这里要注意选择合适的aarch64路径。配置方式如下cat /etc/yum.repos.d/kubernetes.repo EOF [kubernetes] nameKubernetes baseurlhttps://mirrors.example.com/kubernetes/yum/repos/kubernetes-el7-aarch64/ enabled1 gpgcheck0 EOF dnf clean all dnf list kubeadm --showduplicates确认能看到软件包后安装指定版本dnf install -y kubelet-1.28.5 kubeadm-1.28.5 kubectl-1.28.5 systemctl enable --now kubelet这个阶段kubelet起不来是正常的因为还没有集群配置。只要systemctl status不是显示unit not found就继续往下走。4.2 kubelet 对接 isulad配置文件里写明 CRI 端点kubelet的启动参数放在/etc/sysconfig/kubelet里。在这里指定container runtime为remote模式并指向isulad的socketcat /etc/sysconfig/kubelet EOF KUBELET_EXTRA_ARGS--container-runtimeremote --container-runtime-endpointunix:///var/run/isulad.sock EOF systemctl daemon-reload systemctl restart kubelet这里就是整个部署里最核心的“连接点”。如果isulad的socket路径和这里不一致kubelet会反复报错failed to connect to endpoint。我在实际排障中发现很多人会在daemon.json里写一个路径kubelet里写另一个路径这种低级错误反而最难发现。另外kubelet默认的cgroup driver就是systemd和前面isulad配置保持一致即可。如果isulad里改成了cgroupfs这里也要用--cgroup-drivercgroupfs对齐。4.3 准备 kubeadm 初始化参数初始化之前先确认要用到的关键参数。--image-repository指定镜像仓库地址这里的核心要求是仓库必须有arm64架构的Kubernetes组件镜像建议先用kubeadm config images list把需要的镜像列出来再逐个确认架构支持。kubeadm config images list然后执行初始化。以单控制平面节点为例kubeadm init \ --kubernetes-versionv1.28.5 \ --control-plane-endpoint192.168.20.10 \ --apiserver-advertise-address192.168.20.10 \ --pod-network-cidr10.244.0.0/16 \ --image-repositoryregistry.example.com/google_containers \ --cri-socketunix:///var/run/isulad.sock几个参数逐个解释一下--control-plane-endpoint控制平面的访问地址单节点就写自己的IP。如果做HA这里应该是一个负载均衡地址而不是某个节点IP。--pod-network-cidrPod网段。这个网段必须和后面安装的CNI插件配置一致否则Pod地址冲突或者路由不可达。--image-repository改成你环境能访问的镜像仓库注意要确认arm64架构。--cri-socket显式告诉kubeadm使用isulad作为CRI。虽然新版kubeadm有自动探测能力但手动指定最稳妥。初始化过程会拉镜像、生成证书、启动控制平面组件。卡住的时候可以另开一个终端执行journalctl -f看kubelet和kubeadm的日志。初始化成功的最后几行会提示你配置kubectl并给出worker节点加入命令。执行提示中的kubectl配置命令mkdir -p $HOME/.kube cp -i /etc/kubernetes/admin.conf $HOME/.kube/config chown $(id -u):$(id -g) $HOME/.kube/config export KUBECONFIG$HOME/.kube/config kubectl get nodes此时master节点状态应该是NotReady这是正常的因为没有安装CNI网络插件。如果看到节点状态异常或者age列不正常先不要急着处理继续装网络组件。4.4 工作节点加入集群在master上生成join命令kubeadm token create --print-join-command把输出的完整命令复制到worker节点执行。注意join命令里也要补上--cri-socketunix:///var/run/isulad.sock这与初始化的逻辑一致kubeadm join 192.168.20.10:6443 \ --token xxxxx.yyyyy \ --discovery-token-ca-cert-hash sha256:xxxx \ --cri-socketunix:///var/run/isulad.sock然后回master确认节点kubectl get nodes -o wide4.5 用 crictl 确认集群组件跑在 isulad 上集群组件是否真的跑在iSula之上可以用crictl验证。回到master节点crictl ps -a crictl pods如果能看到kube-system命名空间下的apiserver、etcd等容器说明CRI链路是通的。这一步不要跳过很多人kubectl get nodes正常了就以为运行时没问题等后面Pod异常时才回头查浪费大量时间。5. 网络插件选型与 ARM64 特有坑5.1 Calico 在 ARM64 上的安装CNI插件我这边选了Calico v3.28。它在ARM64上有官方multi-arch镜像支持而且VXLAN模式在ARM环境比较稳定。下载manifest并应用curl https://raw.githubusercontent.com/projectcalico/calico/v3.28.0/manifests/calico.yaml -O kubectl apply -f calico.yaml默认的Calico manifest用的IP池是192.168.0.0/16如果你kubeadm init里指定的是10.244.0.0/16需要修改calico.yaml中的CALICO_IPV4POOL_CIDR配置保持两者一致。如果节点网卡不是eth0还得改IP_AUTODETECTION_METHOD- name: IP_AUTODETECTION_METHOD value: interfaceeth.*Calico部署完成后观察节点状态kubectl get pods -n kube-system kubectl get nodes节点从NotReady变为Ready说明CNI网络已经打通。这个过程通常在1~2分钟内完成如果超过5分钟还卡着参考后面问题排查部分。5.2 如果Calico太重可以试试Flannel的VXLAN模式在小规模集群或者资源紧张的节点上Calico的BGP模式感觉有点重。Flannel的VXLAN模式也是ARM64上的可靠选择它的数据转发不需要额外依赖BGP协议逻辑更简单。缺点是性能比Calico的IPIP模式略低而且不支持NetworkPolicy。适合对网络策略没有硬性要求的内部业务系统。Flannel的manifest里使用的镜像默认是multi-arch的在ARM64上可以直接拉取。但要注意部分旧版本的Flannel只提供amd64镜像所以版本别选太老。5.3 存储方案从 emptyDir 到本地卷集群搭好之后存储是个躲不开的话题。初始阶段用hostPath或emptyDir就能跑通业务但如果需要持久化建议优先考虑本地卷方案例如通过local-path-provisioner实现动态供给。这个方案对ARM64没有任何特殊要求部署一个DaemonSet就能工作不需要额外存储设备。数据可靠性要求高的场景再接NFS或对象存储。但在ARM64openEuler这套组合里我建议第一步先别上太复杂的存储方案把K8s本身的调度、网络、弹缩先跑稳再逐步引入外部存储。步子太快容易把问题混在一起排障难度倍增。6. 常见问题与排查思路实录下面这些问题是ARM64 openEuler iSula组合里发生率最高的按我的实际排查顺序整理成速查表现象可能原因处理方式kubelet持续报failed to connect to endpointCRI socket路径不匹配核对isulad daemon.json和/etc/sysconfig/kubelet里的socket路径kubelet拒绝启动报cgroup driver不一致isulad和kubelet的cgroup驱动不同统一改为systemd重启两者exec format error拉取了amd64架构镜像改用arm64镜像用isula inspect确认架构字段Pod一直ContainerCreatingCNI未就绪或镜像拉取失败kubectl describe pod看事件用crictl ps -a查运行时状态节点NotReadyCalico CrashLoop内核模块缺失或不符合要求检查br_netfilter、overlay确认内核版本kubeadm init卡在拉镜像仓库无arm64镜像或网络原因kubeadm config images list逐一验证换多架构仓库kube-proxy日志报nftables相关错误和系统防火墙后端冲突关闭firewalld确保iptables工具链正常6.1 镜像架构验证的具体操作上面表格里exec format error是最常见的但排查时要有点耐心。建议在任何节点上先建一个检查脚本把架构信息固定打印出来isula inspect $(isula ps -q | head -1) --format {{.Image.Architecture}}这个命令查看当前isulad里任一容器的架构。如果返回的不是arm64那基本可以断定某个环节拉错镜像了。6.2 CRI连接失败的排查套路遇到failed to connect to endpoint按这个顺序检查systemctl status isulad确认守护进程活着。ls -l /var/run/isulad.sock确认socket文件存在。crictl version确认CRI接口能通。对比daemon.json和KUBELET_EXTRA_ARGS里的路径是否完全一致。有一次我排查了很久最后发现是daemon.json里多了一层目录socket实际在/var/run/isulad/container/sandbox.sock而配置写的是/var/run/isulad.sock。这类问题只靠肉眼看配置很难发现一定要通过实际文件路径去反推。6.3 Calico 部署后节点长时间NotReadyCalico部署后节点迟迟不Ready最常见的两个原因一是Calico的liveness和readiness探针访问localhost:9099端口如果节点上有网络策略或者防火墙拦截探针一直失败会导致Calico pod不断重启。此时关掉firewalld再观察。二是内核模块问题。Calico在IPIP模式下依赖tunnel相关模块VXLAN模式依赖vxlan模块。在部分ARM64板卡上模块不存在或者版本太老会导致隧道接口创建失败。日志里会看到类似failed to create vxlan interface的错误。这种情况建议直接改VXLAN模式并用modprobe vxlan确认模块可用。6.4 isulad 启动失败的兜底方案isulad自身启动失败也不少见。日志一般通过journalctl -u isulad查看。如果报错内容涉及runtime加载失败先确认OCI运行时组件是否完整dnf list installed | grep runc dnf list installed | grep hcc缺少哪个装哪个。openEuler源里这些包都是配套的一条dnf install runc或对应运行时包就能解决。别折腾重装isulad大部分情况是依赖不完整。最后再分享一点心得体会。ARM64 openEuler iSula这套组合本身是稳定可靠的真正让人头疼的从来不是K8s本身而是三个容易被忽略的细节镜像架构、CRI socket路径、cgroup驱动一致性。这三件事在x86上基本不需要花心思在ARM64上却值得在动手前就明文写进部署检查表。我个人到现在都保持着两个习惯一个是每台节点装完isulad后立刻跑一次crictl version确认CRI链路是通的再往下走另一个是在集群跑起来一两天内周期性执行crictl ps观察运行时状态很多偶发性问题在早期阶段就会暴露出来。这套检查动作成本极低但是能帮你省下一整夜的排障时间。希望这篇对正在ARM64上折腾K8s的朋友有点帮助。