首页 / 资讯中心 / 文章详情

ARM64+openEuler+iSula Kubernetes集群搭建实战

ARM64+openEuler+iSula Kubernetes集群搭建实战 ★ FEATURED ARTICLE
我这次要分享的是在 ARM64 平台上用 openEuler 系统配合 iSula 容器引擎把一套 Kubernetes 集群从零开始搭起来的完整过程。不是说搭建本身有多难而是这套组合比较特殊网上能搜到的资料大多数是 x86 架构 Ubuntu/CentOS Docker 的组合真正针对 ARM64 openEuler iSula 的实战内容非常少。如果你手头正好是这类环境这篇文章会帮你省下至少一整个周末的踩坑时间。先交代一下背景我是在一批准 ARM 架构的服务器上做的部署系统装的是 openEuler要求统一使用 iSula 作为容器运行时上层跑 Kubernetes。这个需求并不罕见很多国产化替代、边缘计算、信创场景都会遇到。但问题是这套组合涉及到的兼容性细节非常多从内核参数到容器运行时插件的配置再到镜像架构的拉取策略任何一个环节没有对齐最后集群都会以各种诡异的方式“半死不活”。这篇文章我会把整个部署链路拆开讲清楚决策前的架构判断、openEuler 系统的初始配置、iSula 的正确安装方式、kubelet 与 iSula 之间的 CRI 插件打通、用 kubeadm 拉集群、常见坑的完整排查思路以及集群跑起来之后的验证和维护建议。每一步都有具体的命令和配置也有我在实际环境中遇到的报错和对应的处理思路。文章会比较长但保证每一段都是你复制粘贴就能用的干货。1. 部署之前先把三个决策想清楚很多人上手就装结果装了三天还在折腾。我不建议这么干。在敲第一条命令之前有三件事必须想清楚为什么选这套组合、架构上有没有隐藏的兼容性坑、CRI 的链路要怎么打通。这三件事想明白了后面的操作其实就是流程化执行。1.1 为什么 openEuler iSula 值得选先说 openEuler。这是一个面向服务器场景的 Linux 发行版从内核到用户态工具链都做了不少企业级优化对 ARM64 的支持也明显比很多“顺手支持 ARM”的系统要好。它不是那种社区里几个人攒出来的玩具发行版而是有完整的软件仓库、更新体系和运维工具的。在需要做国产化、合规化验收的环境里openEuler 往往是“说了算”的那个选项这没什么好回避的。再说 iSula。它是与 openEuler 同生态的容器引擎套件包含了容器运行时、镜像构建工具等组件。对于纯容器运行这个需求来说iSula 的设计思路是轻量和高效常驻内存开销比 Docker 那一套要小不少。在资源受限的边缘节点上这个差异是可以感知到的。更关键的是iSula 提供了 CRI Plug这是一个把容器运行时接入 Kubernetes CRI 标准的桥梁。没有这个插件Kubernetes 根本没办法统一管理 iSula 创建的容器。所以这套组合的核心逻辑是系统层用 openEuler 保证底座的稳定性和生态兼容容器层用 iSula 提供轻量运行时Kubernetes 通过 CRI 标准来调度和管理它。三层各自的职责清晰集成方式也有官方路径可走不像有些组合需要靠第三方 hack 才能跑通。1.2 ARM64 架构兼容性是第一步检查项很多人觉得既然 kubeadm 支持 ARM64那直接装就行。理论上确实是这样但实际会遇到很多隐性问题。最典型的就是镜像架构。Kubernetes 控制面组件、CoreDNS、网络插件这些镜像多数都发布了 multi-arch 版本但也有不少第三方插件或者某个特定 tag 的镜像只有 amd64 版本。如果你的节点是 ARM64拉下来一个 amd64 的镜像容器起来就是exec format error表现出来就是 Pod 反复 CrashLoopBackOff。所以第一步不是装软件而是先确认你的环境uname -m cat /etc/os-release nproc free -h我会用上面这几条命令拿到最基础的架构、系统版本、CPU 和内存信息。接下来还要确认内核版本和必要模块uname -r modprobe br_netfilter cat /proc/sys/net/bridge/bridge-nf-call-iptablesARM64 平台上内核默认不一定加载了br_netfilter模块IPv4 转发也可能没有开启这些不提前处理好后面 flannel 或者 Calico 的 Pod 网络一定起不来。别问我怎么知道的我在第一套环境里就栽在这上面网络插件 Pod 卡在ContainerCreating一整夜。最后一个要确认的是 SELinux 的状态。openEuler 默认开启了 SELinux而 iSula 与 SELinux 的配合关系比较微妙。你可以用getenforce查看当前状态如果是Enforcing要么提前规划好策略要么临时切到Permissive。对于内网测试环境我建议先切 Permissive 跑通全流程后续再按安全要求收紧。生产环境怎么做要结合安全规范和运维策略来决定不要把测试环境的做法直接搬到生产。还有一点容易被忽略主机名的规划。Kubernetes 集群内的节点主机名不能重复而且不能有大写字母。你最好在部署前就把所有节点的主机名统一改成类似node-01、node-02这种格式并把它们的 IP 和主机名映射写进/etc/hosts。如果你不管后面 kubeadm join 时经常会出现 TLS 证书里的主机名和实际主机名对不上报错信息会让你查半天。2. 从裸机到可用集群的完整搭建链路2.1 系统准备内核参数、交换分区与主机名openEuler 默认安装完之后有几项系统参数是需要调整的。每一条都要弄明白为什么调不然过两周回头看配置文件会有种“这段代码是谁写的”的困惑。第一关闭交换分区。Kubernetes 官方建议将 swap 关闭因为 kubelet 在 swap 开启的情况下无法稳定地做内存管理。虽然新版本提供了--fail-swap-onfalse参数可以绕过但生产环境不建议这么干。处理方式swapoff -a sed -i /swap/s/^/#/ /etc/fstab第二设置内核网络参数。Kubernetes 的 Pod 网络依赖 iptables 对网桥流量的处理。如果不开启桥接流量的 iptables 过滤集群内 Service 的 DNAT 规则可能不生效表现就是跨 Pod 访问不通。配置方法cat EOF | tee /etc/modules-load.d/k8s.conf br_netfilter nf_conntrack overlay EOF cat EOF | tee /etc/sysctl.d/k8s.conf 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第三主机名规划。我在这套环境里用的命名规则是master-01、node-01这种简单好记。每个节点执行hostnamectl set-hostname master-01 echo 192.168.1.10 master-01 /etc/hosts这里有个细节kubeadm 初始化时会生成证书证书里的 CN 和 SAN 包含主机名和 IP。如果你之后改了主机名证书就会对不上导致 API Server 无法访问。所以我们提前把所有规划都定死后面不轻易变动。2.2 安装 iSula不要装成一个“残缺版 Docker”openEuler 的官方软件源里就有 iSula 的安装包直接用包管理器安装即可dnf install -y isula isula-build装完之后先启动 isuladsystemctl enable --now isulad systemctl status isulad这里我要多说一句安装本身很简单真正容易出问题的是“你以为 iSula 装好了其实它并没有完整地接入 Kubernetes”。isulad 是一个独立的容器运行时服务它自己有一套管理容器的命令入口isula 命令但它默认不主动去监听 Kubernetes 发来的请求。要让 kubelet 通过 CRI 标准调用它需要让 isulad 开启 CRI 支持并通过 criplug 这个组件把 CRI 的 socket 暴露出来。所以不要学 Docker 时代的习惯装完运行时先 docker pull 一下试试就去搞 kubeadm。我们需要确认 isulad 的 CRI 插件服务状态systemctl status criplug.socket正常的话会显示active (listening)。如果没起来大概率是配置文件里的路径或者 sock 文件权限不对这个我们下一节细说。2.3 criplug 配置kubelet 能连上 isulad 的桥梁这一段是整个部署过程中“文档里查不到”的内容也是很多人卡住最久的地方。iSula 与 Kubernetes 的对接是通过criplug这个插件完成的。它的物理形态是一个 systemd 服务和一个 socket 文件。默认情况下这个插件在安装 isula 之后就会装上但它需要满足几个条件才能正常工作。首先确保 socket 被正确创建。默认路径是/var/run/criplug.sock。这个名字不是系统本来就有的是 criplug 自己监听出来的。你可以用ls -l /var/run/criplug.sock看看有没有这个文件。如果没有重启一下 criplug 服务systemctl restart criplug.socket systemctl restart criplug其次确认 kubelet 调用的 CRI socket 路径一致。kubeadm 默认去找 Docker 的 socket默认/var/run/dockershim.sock或者 containerd 的/var/run/containerd/containerd.sock这对 iSula 是不适用的。我们需要在 kubeadm 初始化时明确指定kubeadm init --cri-socketunix:///var/run/criplug.sock ...如果你的环境里开了防火墙还要确认本机的 TCP 和 Unix socket 访问没有被拦截。很多时候不是技术问题是安全策略挡了路。最后验证 iSula 的 CRI 接口是否响应crictl --runtime-endpoint unix:///var/run/criplug.sock info如果这条命令能正确返回运行时信息说明 CRI 链路已经通了。到这一步kubelet 才真正“看得见” iSula 管理的容器。2.4 kubeadm 初始化版本、镜像、时区一并搞定安装 kubeadm、kubelet、kubectl 这些组件比较直接从 openEuler 的 Kubernetes 软件源或者网上大家常用的源拉就行dnf install -y kubeadm kubelet kubectl systemctl enable --now kubelet这里最值得操作的是 kubeadm 的配置文件。很多人都直接kubeadm init一把梭但在这套组合里必须明确写入 CRI socket否则初始化会直接失败报错信息会告诉你找不到 runtime# kubeadm-config.yaml apiVersion: kubeadm.k8s.io/v1beta4 kind: ClusterConfiguration kubernetesVersion: v1.28.5 networking: podSubnet: 10.244.0.0/16 serviceSubnet: 10.96.0.0/12 --- apiVersion: kubeadm.k8s.io/v1beta4 kind: InitConfiguration nodeRegistration: criSocket: unix:///var/run/criplug.sock执行初始化kubeadm init --configkubeadm-config.yaml --cri-socketunix:///var/run/criplug.sockARM64 平台还需要提前关注一个问题pause 镜像的架构。kubeadm 初始化时要拉取pause镜像作为每个 Pod 的“根容器”如果你用的镜像仓库里只有 amd64 版本初始化就会失败。建议提前确认你指定的 pause 镜像是否支持 arm64或者准备一个私有镜像仓库手动把 arm64 版本的镜像推上去。初始化成功后会生成一串kubeadm join命令务必存好。如果要拼接 worker 节点就用这串命令加上同样的--cri-socket参数去执行。2.5 网络插件选 flannel 还是 Calico 要按架构来kubeadm 初始化完成之后控制面节点会进入 NotReady 状态因为还没有网络插件。我在这套 ARM64 环境里先尝试了 flannel因为它的镜像相对精简配置也直观。但很快发现 flannel 的镜像在网络受限的环境中拉取速度慢而且对内核模块的依赖也需要额外确认。后来切换到了 Calico整体更稳定资源占用也可以接受。如果你只是测试环境flannel 够用如果面向生产Calico 的灵活性和网络策略支持更好。以 Calico 为例kubectl apply -f https://raw.githubusercontent.com/projectcalico/calico/v3.27/manifests/calico.yaml注意 yaml 里的镜像地址。因为网络原因国内环境经常拉不到 Calico 官方仓库的镜像需要提前改镜像。有私有仓库的环境建议直接sed替换成私有仓库地址sed -i s#docker.io/#registry.internal/# calico.yaml网络插件 Pod 的状态是判断集群健康度的关键指标。不要只看 Pod 变成 Running 就算完还要确认每个节点上的 CNI 配置文件已经生成到位。如果你发现calico-nodePod 报了Error排查的时候一定要看节点上的内核是否加载了nf_conntrack等模块。这些模块在 ARM64 的 openEuler 上默认未必加载而 Calico 的 felix 需要它们来管理 iptables 规则。3. 部署中真正折磨人的三个坑这一节我想把我在部署过程中遇到的最棘手的三个问题完整复盘一遍不只是给结论而是把整个排查链路展示出来。因为很多问题你上网搜不到任何记录只能靠排查思路来破局。3.1 报错“exec format error”镜像架构与节点不匹配现象某个 Pod 创建之后一直 CrashLoopBackOffkubectl logs没有任何输出kubectl describe拉到的事件也只是正常的 Kill、BackOff。查了 Confirm 阶段的日志才发现问题。排查过程先看 Pod 里容器的启动命令是什么。有些镜像的入口脚本带有架构判断我们这次用的镜像看起来是 multi-arch但里面的 binary 实际上只有 amd64 版本。手动在这个节点上用crictl pull拉取同一镜像再用crictl run跑了一下报错信息明确指向了架构不匹配。确认问题后替换为正确支持 arm64 的镜像 tag或者重新构建。还有个隐藏坑有时候镜像名称没有带架构后缀仓库会根据请求的架构返回对应的 manifest但如果仓库服务端不支持按架构分发所有节点都会拿到同一个 amd64 镜像。所以遇到exec format error先不要怀疑是不是权限问题、配置问题先把镜像的架构标签和节点架构对照一遍用二进制级别的确认来代替猜。一条命令就能确认docker manifest inspect 镜像名 | grep architecture如果手里没有 docker也可以用crictl和skopeo这类工具。总之先确认镜像的 manifest 列表里到底有没有 arm64 这一项没有就别装了。3.2 节点状态 Ready 但 Pod 一直 Pendingcgroup 驱动不一致现象kubeadm 初始化成功网络插件也安装了但所有 Pod包括控制面的 Pod全部卡在 Pending节点状态明明是 Ready。我当时的排查链路可以整理成四步首先怀疑的是资源不足。kubectl describe node看可分配资源CPU 和内存都充足tolerance 也允许调度排除。接着怀疑 kubelet 没有正确上报状态。查看systemctl status kubelet一切正常。再怀疑调度器问题查看了 scheduler 日志也没有发现异常。最后实在没招了去翻了 isulad 的系统日志发现它在与 kubelet 通信时不断在报错。最终定位kubelet 默认使用systemd作为 cgroup 驱动而 isulad 默认可能使用的不是systemd两者不一致导致 kubelet 无法准确获取容器的状态进而无法正常对 Pod 做资源统计和调度决策。解决办法有两类统一让 iSula 使用 systemd cgroup 驱动。在 isulad 的配置文件通常是/etc/isulad/daemon.json中设置cgroup-manager: systemd然后重启 isulad。或者反过来让 kubelet 使用容器运行时默认的 cgroup driver。但这样不太好因为多数的监控和运维组件默认会去看 systemd 的 cgroup 树。这个坑为什么难排查因为它不是直接报错而是让所有 Pod 处于 Pending 状态表面看起来一切正常但调度器就是不愿意把 Pod 放上去。如果你也遇到类似情况优先看看 kubelet 和容器运行时的 cgroup 驱动是不是同一个这一步检查成本极低排除成本极高。3.3 网络插件起不来缺失的的内核模块现象Calico 的calico-nodePod 在某个节点上始终是CrashLoopBackOff日志里提示缺少某些 iptables 相关的支持。排查过程先查看该节点的内核模块列表lsmod | grep br_netfilter发现没有加载。在架构为 ARM64 的 openEuler 上br_netfilter和nf_conntrack默认可能不在已加载模块列表里。手动modprobe br_netfilter再重启 Calico Pod状态恢复但这只是临时方案因为节点重启后又会丢。最后通过/etc/modules-load.d/k8s.conf文件把它们固定下来再执行sysctl --system加载参数。这个坑我觉得很值得写出来ARM64 环境的内核模块加载策略跟传统 x86 发行版确实有差异不要想当然认为“标准内核就带这些”每一台节点都要实际检查一遍。把模块加载和系统参数固化到配置文件的方案至少保证节点重启后还能正常加入集群不会出现“集群白天好好的晚上一重启就废了”的尴尬局面。4. 集群跑通之后怎么验证和维护4.1 功能验证三板斧集群搭完不是终点要证明它能干活至少做三件事第一跑一个简单的无状态应用kubectl create deployment nginx-test --imagenginx:alpine kubectl scale deployment nginx-test --replicas3 kubectl get pods -o wide如果 Pod 分布在多个节点上说明调度正常。然后检查 Service 的联通性kubectl expose deployment nginx-test --port80 --typeNodePort kubectl get svc nginx-test curl http://任意节点IP:NodePort第二步验证 DNS 解析。在集群内创建一个测试 Pod进入后nslookupKubernetes 的 Service 名称如果解析正常说明 CoreDNS 工作正常这是集群内部服务发现的基础。第三步验证跨节点通信。让 Pod 分布在两个不同节点上从一个 Pod 直接 ping 另一个 Pod 的 IP或者通过 Service 的 ClusterIP 访问。只有这些打通了才能说网络插件真的合格了。4.2 证书过期与 etcd 备份提醒kubeadm 签发的证书默认有效期是 1 年。这个时间说长不长说短不短但如果不提前做规划到时间点集群会突然之间不可用。建议在集群部署完成的那一刻起就在日历上标注证书到期日提前 2-4 周安排更新。查看证书有效期kubeadm certs check-expiration更新命令kubeadm certs renew all kubeadm init phase kubeconfig --configkubeadm-config.yaml systemctl restart kubelet再一个不能拖的事情是 etcd 备份。我见过太多人觉得单节点集群不用备 etcd结果一次误操作把核心资源删了之后无从恢复。kubeadm 控制面组件和 etcd 都跑在容器里etcd 的数据目录默认挂载在宿主机的/var/lib/etcd建议用定时任务把整个目录做快照cp -r /var/lib/etcd /backup/etcd-$(date %F)条件允许的话尽量用 etcd 自带的 snapshot 接口做一致性快照etcdctl snapshot save /backup/etcd-snapshot-$(date %F).db4.3 日常运维常用命令对照表在跑这套集群维护了几个月之后我总结了一套自己的“巡检清单”。这张表建议收藏尤其是刚接触 iSula 的朋友场景Docker 习惯命令openEuler iSula 命令查看所有容器docker ps -aisula ps -a查看容器详情docker inspectisula inspect进入容器docker exec -itisula exec -it查看日志直接看容器内进程docker logsisula logs查看 Pod 对应的容器状态kubectl describe podcrictl --runtime-endpoint unix:///var/run/criplug.sock ps清理临时镜像docker image pruneisula image prune 需要根据 isula 版本确认集群巡检时我常用的顺序是kubectl get nodes -o wide先看节点状态和版本是否统一kubectl get pods -A -o wide看全局 Pod 状态是否有非 Running 项systemctl status isulad和journalctl -u isulad --no-pager | tail -50看容器引擎自身的健康状况df -h /var/lib注意 isulad 默认存储路径的空间使用率这是最容易不知不觉打满的隐患。我得说一句大实话在 ARM64 平台上Kubernetes 集群跑起来只是开始真正的维护难点在于“检查项和 x86 环境的差异点”。举个例子x86 环境你可能从来不用关心内核加载了哪些模块但在 ARM64 的 openEuler 上这些差异会被放大。所以在巡检时一定要把架构相关的检查项单独列出来每次都确认一遍。5. 最后分享一个省心小技巧在连续搭了三套同样的环境之后我养成一个习惯把整套部署过程沉淀成一个标准化的脚本和一份检查清单。脚本负责装软件、改配置、拉镜像检查清单负责在每执行完一个阶段后用那几条我上面反复强调的关键命令去验证状态。这样做的原因是手动敲命令部署一次可能只要两小时其中最耗时间的部分并不是敲命令本身而是卡在某个坑里没有头绪地排查。脚本化之后这个时间可以压缩到 20 分钟以内。另一个值得做的是镜像仓库的提前准备。如果你所在的内网访问外网有限提前准备好了 arm64 相关的所有镜像并推到私有仓库部署时会省掉无数等待时间。我甚至会把 pause、coredns、calico 这些组件的镜像全部固化到一个离线镜像包里用 iSula 自带的镜像导入命令批量导入。这一步做完之后后续扩容新节点时就会非常顺滑不再需要在网络上反复等待。每次给新环境装这套集群我都会被同事吐槽“你怎么又要重来一遍”但恰恰是这种不厌其烦的重复让我把整套流程里的所有不确定因素都变成了已知项。现在再遇到 ARM64 openEuler iSula 的环境我基本不需要看文档了因为坑都踩平了。这篇文章的内容就是这些坑和应对办法的完整记录希望它也能帮你把这条路走平。
阅读完成 · 觉得有帮助?
咨询建站