K3s 离线部署昇腾 MindCluster 简记

发表于 1 天前  27 次阅读


文章目录

内网麒麟 V10(ARM64)+ 昇腾 910 环境,没有外网,要把 K3s 和 MindCluster 26.0.0 全套(Volcano、ClusterD、Ascend Operator、Device Plugin 等)拉起来。整套流程我拆成了 11 个脚本,从联网侧下载到集群验证全覆盖,跑通后集群已稳定运行。本文按执行顺序过一遍,顺带记录踩过的坑。

一、整体思路

整个部署分两个阶段:

  1. 联网机器(x86_64 或 ARM64 的 Linux/Mac 都行):下载 K3s 离线包、MindCluster 组件包、导出容器镜像、下载麒麟 RPM 依赖,打成一个小目录整体拷进内网;
  2. 内网目标机(麒麟 V10 SP2 ARM64 + 昇腾 910):系统准备 → 装 K3s → 配 containerd → 导镜像 → 建用户目录 → 部署组件 → 打标签 → 验证。

拷进内网的离线包就一个目录,结构如下(脚本都按相对路径找兄弟目录,别打散):

offline-k3s-mindcluster/
├── k3s/                # k3s 二进制 + airgap 镜像 + install.sh
├── mindcluster/        # 7 个组件 zip(volcano/clusterd/...)
├── images/             # master-*.tar / worker-*.tar
├── rpms/               # 麒麟 V10 aarch64 RPM 依赖
└── scripts/            # 00 ~ 08 + worker-join-guide

版本组合:K3s v1.35.5+k3s1、MindCluster v26.0.0(组件内 Volcano 为 v1.9.0)、麒麟 V10 SP2。

前置条件(不在脚本范围内,但必须先有):NPU 节点已装好昇腾驱动/固件,npu-smi info 能看到卡;Ascend-Docker-Runtime 已安装且 /usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime 存在,HwHiAiUser 组已创建。

二、联网侧:一键下载 K3s 与 MindCluster 组件

00-download-all.sh 干三件事:下载 K3s 离线资源、下载 MindCluster 组件 zip、拉取并导出容器镜像。

K3s 走 gh-proxy 加速,MindCluster 组件从 gitcode 的 mind-cluster 仓库拿:

K3S_VERSION="v1.35.5+k3s1"
K3S_URL="https://gh-proxy.org/https://github.com/k3s-io/k3s/releases/download/${K3S_VERSION}"

GITCODE_BASE="https://gitcode.com/Ascend/mind-cluster/releases/download/v26.0.0"

declare -A PACKAGES=(
  ["volcano"]="Ascend-mindxdl-volcano_26.0.0_linux-aarch64.zip"
  ["clusterd"]="Ascend-mindxdl-clusterd_26.0.0_linux-aarch64.zip"
  ["ascend-operator"]="Ascend-mindxdl-ascend-operator_26.0.0_linux-aarch64.zip"
  ["infer-operator"]="Ascend-mindxdl-infer-operator_26.0.0_linux-aarch64.zip"
  ["noded"]="Ascend-mindxdl-noded_26.0.0_linux-aarch64.zip"
  ["npu-exporter"]="Ascend-mindxdl-npu-exporter_26.0.0_linux-aarch64.zip"
  ["device-plugin"]="Ascend-mindxdl-device-plugin_26.0.0_linux-aarch64.zip"
)

容器镜像一共 8 个,按节点角色分两组,docker pull 后立刻 docker save 成 tar。注意 volcano 镜像的 tag 是双版本号 v1.9.0-v26.0.0——前面是 Volcano 内核版本,后面是 MindCluster 版本:

角色 镜像
Master vc-scheduler:v1.9.0-v26.0.0、vc-controller-manager:v1.9.0-v26.0.0、clusterd:v26.0.0、ascend-operator:v26.0.0、infer-operator:v26.0.0
Worker noded:v26.0.0、npu-exporter:v26.0.0、ascend-k8sdeviceplugin:v26.0.0

镜像源是 swr.cn-south-1.myhuaweicloud.com/ascendhub/。下载机架构自适应:ARM64 直接拉,x86_64 自动加 --platform linux/arm64——所以这台联网机是 x86 的也没关系,QEMU 的事交给 Docker。

三、联网侧:麒麟 V10 RPM 依赖离线下载

K3s 和 MindCluster 在麒麟上跑需要一堆系统依赖(conntrack、iptables、nftables、nfs-utils、openssh、selinux-policy 等),这些包在 CentOS/openEuler 源里凑不齐,得用麒麟自己的源。

思路:用 Docker 跑一个麒麟 V10 SP2 ARM64 容器(x86_64 下载机上靠 QEMU binfmt 模拟),容器里配麒麟官方源 + 华为 Ascend 源,yumdownloader --resolve 一把梭,把 rpm 全量下出来拷回宿主机。

# 容器内配置的源(Dockerfile 片段)
[kylin-base]
name=Kylin V10 SP2 Base aarch64
baseurl=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/base/aarch64/

[kylin-update]
name=Kylin V10 SP2 Updates aarch64
baseurl=https://update.cs2c.com.cn/NS/V10/V10SP2/os/adv/lic/updates/aarch64/

[ascend-docker]
name=Ascend Docker CE aarch64
baseurl=https://ascend-repo.obs.cn-east-2.myhuaweicloud.com/docker-ce/linux/centos/7/aarch64/stable/

麒麟基础镜像按顺序尝试 macrosan/kylin:v10-sp2v10-sp1kylinv10/kylin:b09。这里有两个坑,都写进了脚本:

  1. iptables-ebtables 这个包名在麒麟源里不存在,yumdownloader 会直接报错缺包。正确写法是 ebtables。这个坑不修,整条下载链路就断在半路。
  2. 麒麟镜像拉不到时千万不要回退 openEuler——源不兼容,命令显示"成功",实际 0 个 rpm。脚本里直接 exit 1 禁止回退,并加了三层校验:容器内查 /rpmsdocker cp 后复查、宿主机统计 rpm 数量,任何一层发现 0 个立刻报错退出。

另外 docker build --no-cache 强制全新下载,开工前清掉旧 rpm,避免旧缓存层骗人。sshpass、inotify-tools 两个特殊包走单独的 wget(一个来自 ascend-repo,一个来自华为云 epel 镜像)。

四、目标机:系统准备(01-system-prepare.sh)

所有节点执行。内容是标准 K8s 前置:关 SELinux、关 firewalld、关 swap、加载内核模块、设 sysctl,最后把 rpms/ 里的包一口气装上。

# 内核模块
cat > /etc/modules-load.d/k3s.conf <<EOF
br_netfilter
overlay
ip_vs
ip_vs_rr
ip_vs_wrr
ip_vs_sh
nf_conntrack
EOF

# 内核参数
cat > /etc/sysctl.d/k3s.conf <<EOF
net.bridge.bridge-nf-call-iptables = 1
net.bridge.bridge-nf-call-ip6tables = 1
net.ipv4.ip_forward = 1
net.ipv4.conf.all.forwarding = 1
net.ipv6.conf.all.forwarding = 1
EOF

# 离线安装依赖(rpms 目录由第三步产出)
cd rpms && rpm -ivh *.rpm --nodeps --force

跑完建议重启一次再继续。

五、目标机:安装 K3s(02-install-k3s.sh)

纯离线三板斧:放二进制、放 airgap 镜像包、跑 install.sh(跳过下载)。

cp k3s-arm64 /usr/local/bin/k3s && chmod +x /usr/local/bin/k3s
cp k3s-airgap-images-*.tar.zst /var/lib/rancher/k3s/agent/images/

# Master
INSTALL_K3S_SKIP_DOWNLOAD=true bash install.sh server \
  --write-kubeconfig-mode 644 \
  --disable traefik

# Worker(在另一台机器上)
K3S_URL=https://<MASTER_IP>:6443 K3S_TOKEN=<node-token> \
  INSTALL_K3S_SKIP_DOWNLOAD=true bash install.sh agent

--disable traefik 是为了内网少一批用不上的镜像。Master 装完脚本会自动把加入命令(含 IP 和 token)打印出来,worker-join-guide.sh 也是干这个的,忘了 token 随时补看:cat /var/lib/rancher/k3s/server/node-token

六、目标机:containerd 配置昇腾 Runtime(03-configure-containerd-runtime.sh)

这一步不做,NPU 容器起来也挂不上设备——设备注入靠 Ascend Docker Runtime 完成。K3s 的做法是写一份 config.toml.tmpl,把默认 runtime 直接切成 ascend:

[plugins.cri.containerd]
  snapshotter = "overlayfs"
  default_runtime_name = "ascend"

[plugins.cri.containerd.runtimes.runc]
  runtime_type = "io.containerd.runc.v2"

[plugins.cri.containerd.runtimes.ascend]
  runtime_type = "io.containerd.runc.v2"
  [plugins.cri.containerd.runtimes.ascend.options]
    BinaryName = "/usr/local/Ascend/Ascend-Docker-Runtime/ascend-docker-runtime"
    SystemdCgroup = false

写到 /var/lib/rancher/k3s/agent/etc/containerd/config.toml.tmpl,然后 systemctl restart k3s(worker 是 k3s-agent)生效。

七、目标机:导入镜像并重打标签(04-import-images.sh)

把第二阶段导出的 tar 导进 K3s 自带的 containerd:

# Master 节点
for f in images/master-*.tar; do
  k3s ctr -n k8s.io images import "$f"
done
# Worker 节点导 worker-*.tar,同理

打标签这步不能省。MindCluster 的 yaml 里 image 写法五花八门:volcanosh/vc-scheduler:v1.9.0docker.io/volcanosh/...docker.io/library/... 都有。在线环境无所谓,registry 会兜底;离线环境 yaml 引用哪个名字,本地 containerd 里就必须存在哪个名字。脚本把每个镜像按三种前缀各 tag 了一遍:

k3s ctr -n k8s.io images tag \
  swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-scheduler:v1.9.0-v26.0.0 \
  volcanosh/vc-scheduler:v1.9.0
k3s ctr -n k8s.io images tag \
  swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-scheduler:v1.9.0-v26.0.0 \
  docker.io/volcanosh/vc-scheduler:v1.9.0
k3s ctr -n k8s.io images tag \
  swr.cn-south-1.myhuaweicloud.com/ascendhub/vc-scheduler:v1.9.0-v26.0.0 \
  docker.io/library/volcanosh/vc-scheduler:v1.9.0

不加这步,Pod 全部卡 ImagePullBackOff,日志里只有一句 pull access denied,非常误导。

八、目标机:创建用户与日志目录(05-create-user-dirs.sh)

MindCluster 组件按华为规范跑在专用用户下:

useradd -d /home/hwMindX -u 9000 -m -s /sbin/nologin hwMindX
usermod -a -G HwHiAiUser hwMindX   # HwHiAiUser 由 NPU 驱动创建

日志目录按节点角色创建,属主是有讲究的:

  • 所有节点(root 属主):/var/log/mindx-dl/{devicePlugin,noded,npu-exporter}
  • Master 另加(hwMindX 属主):ascend-operatorclusterdvolcano-controllervolcano-scheduler

权限统一 750。目录缺失时组件会写日志失败,症状是 Pod 反复重启但 kubectl logs 没内容。

九、Master:部署 MindCluster 组件(06-deploy-mindcluster.sh)

主流程四步:建命名空间 → 解压组件包 → 统一修 imagePullPolicy → 依次 apply。

命名空间两个:mindx-dl(MindCluster 组件)和 cluster-system(clusterd)。解压后先把所有 yaml 的 imagePullPolicy 统一改成 IfNotPresent——离线环境 Always 必挂(每次都去拉 registry),Never 又强依赖镜像名完全匹配,IfNotPresent 两头兼顾:

find "$MC_DIR" -name "*.yaml" -exec grep -l "imagePullPolicy" {} \; | while read yaml; do
  sed -i 's/imagePullPolicy: Never/imagePullPolicy: IfNotPresent/g' "$yaml"
  sed -i 's/imagePullPolicy: Always/imagePullPolicy: IfNotPresent/g' "$yaml"
done

部署顺序:Volcano → ClusterD → Ascend Operator → Infer Operator → 可选的 NodeD / NPU Exporter / Device Plugin。

这里有个本次部署最大的坑:Volcano 的 zip(Ascend-mindxdl-volcano_26.0.0_linux-aarch64.zip)里同时打包了 volcano-v1.7.0volcano-v1.9.0 两套 yaml。最初的写法是:

VOLCANO_YAML=$(find "$MC_DIR/volcano/extracted" -name "volcano-v*.yaml" -type f | head -1)

find | head -1 按目录序取第一个,拿到的是 v1.7.0——和导入的 v1.9.0 镜像不匹配。修复后版本写法:精确优先 + 版本号兜底:

VOLCANO_VERSION="v1.9.0"

pick_yaml() {
  find "$1" -name "$2" -type f 2>/dev/null | sort -V | tail -1
}

VOLCANO_YAML=$(pick_yaml "$MC_DIR/volcano/extracted" "volcano-${VOLCANO_VERSION}.yaml")
if [ -z "$VOLCANO_YAML" ]; then
  VOLCANO_YAML=$(pick_yaml "$MC_DIR/volcano/extracted" "volcano-v*.yaml")
fi
kubectl apply -f "$VOLCANO_YAML"

其余组件(clusterd、ascend-operator 等)也统一走 pick_yaml 取最高版本,杜绝同类问题。

十、Master:节点打标签(07-label-nodes.sh)

MindCluster 的 DaemonSet(noded、npu-exporter、device-plugin)靠标签做 nodeSelector,不打标 Pod 一个都起不来:

# Master 侧
kubectl label nodes -l node-role.kubernetes.io/control-plane \
  masterselector=dls-master-node --overwrite

# Worker 侧(单节点集群给 master 也补上)
kubectl label nodes -l '!node-role.kubernetes.io/control-plane' \
  workerselector=dls-worker-node --overwrite

# 芯片标签
kubectl label nodes -l workerselector=dls-worker-node \
  accelerator=huawei-Ascend910 --overwrite

打完等几分钟再查 Pod,DaemonSet 按 label 陆续调度。

十一、Master:验证(08-verify.sh)

验证脚本覆盖七项:节点状态、全量 Pod、volcano-system / mindx-dl / kube-system 三个命名空间、DaemonSet 副本数、异常 Pod 过滤、containerd 镜像核对,最后是NPU 资源检查——device-plugin 正常的标志是节点 capacity 里出现昇腾资源:

kubectl get nodes -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.capacity}{"\n"}{end}' \
  | grep -o "huawei.com/ascend[0-9]*[^ ]*"

能看到 huawei.com/ascend910-XXnpu-smi info 里的卡数对上,整条链路(驱动 → runtime → device-plugin → K8s 资源模型)才算真正打通。

十二、Worker 节点加入流程

Worker 侧不用动 Master 上的东西,按顺序跑五个脚本即可(worker-join-guide.sh 会把 Master IP 和 token 直接打出来,照抄就行):

  1. 01-system-prepare.sh
  2. 02-install-k3s.sh worker
  3. 03-configure-containerd-runtime.sh
  4. 04-import-images.sh worker
  5. 05-create-user-dirs.sh

全部加入后,回 Master 补 07-label-nodes.sh08-verify.sh

十三、踩坑速查

现象 解法
Volcano zip 内含 v1.7.0 / v1.9.0 两套 yaml find | head -1 按目录序选中旧版 版本变量精确匹配 + sort -V 取最高版本
iptables-ebtables 包不存在 yumdownloader 缺包中断 改用 ebtables
麒麟镜像拉不到回退 openEuler 命令"成功"但 0 个 rpm(源不兼容) 禁止回退 + 三层空结果校验快速失败
yaml 的 imagePullPolicy 为 Always/Never 离线拉取失败,Pod 卡 Pending 统一 sed 成 IfNotPresent
yaml 引用的镜像名与导入名不一致 ImagePullBackOff,pull access denied 按三种前缀(volcanosh/、docker.io/、docker.io/library/)各 tag 一份
NPU 容器挂不上设备 容器内看不到卡 containerd 默认 runtime 切 ascend(config.toml.tmpl)
load average 很高但 CPU 空闲 误判机器过载 昇腾驱动常驻线程(dev*_sq_task 等)D 状态计入 load,正常现象

结语

整套东西固化成脚本之后,重跑成本非常低:新节点入集群就是 01→02→03→04→05 五连,Master 侧组件重部署一个 06 全搞定。离线部署的复杂度全在"名字对得上"和"版本选得对"这两件事上,把这两处用代码钉死,剩下的就是等 Pod 变绿。

以上,简记。

本站文章基于国际协议BY-NA-SA 4.0协议共享;
如未特殊说明,本站文章皆为原创文章,请规范转载。

0

scanz个人博客