tuneplane 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: tuneplane
3
+ Version: 0.0.1
4
+ Summary: Client library for the TunePlane large-model post-training platform.
5
+ Author: TunePlane Contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # tuneplane
15
+
16
+ Reserved initial client package for the TunePlane large-model post-training platform.
17
+
18
+ The first release intentionally contains only package metadata and a placeholder client namespace.
@@ -0,0 +1,727 @@
1
+ # 内网 GPU 集群 Kubernetes v1.36 部署教程
2
+
3
+ > 3 × 控制平面(Ubuntu 26.04 LTS,堆叠 etcd)+ H200(Rocky Linux 9.7)+ B300(Ubuntu 26.04 LTS)
4
+ > containerd 2.3 LTS · Cilium(CNI + LB-IPAM + L2 通告 + Gateway API)· cert-manager · NVIDIA GPU Operator 26.7 · NVIDIA Network Operator 26.7 · HAMi 2.10
5
+ > 节点无公网、无 DNS;Nexus 3.95 可访问公网,节点通过内网 Nexus(`https://mirrors.gcoreinc.com`)下载依赖。
6
+ > 本轮全流程审查、修复与待确认项见 [部署审查记录](docs/deployment-review.md),上线前操作清单见 [运维与验收](docs/operations.md)。
7
+
8
+ | 节点 | IP | 系统 | 角色 |
9
+ |---|---|---|---|
10
+ | k8s-cp1 | 192.168.222.10 | Ubuntu 26.04 | 控制平面 #1(首个 init) |
11
+ | k8s-cp2 | 192.168.222.11 | Ubuntu 26.04 | 控制平面 #2 |
12
+ | k8s-cp3 | 192.168.222.12 | Ubuntu 26.04 | 控制平面 #3 |
13
+ | gpu-h200 | 192.168.222.2 | Rocky 9.7 | GPU 节点(H200,Intel X710 10GbE) |
14
+ | gpu-b300 | 192.168.222.3 | Ubuntu 26.04 | GPU 节点(B300,Broadcom BCM57414 25GbE RoCE) |
15
+ | k8s-api(VIP) | 192.168.222.100 | — | 控制面 VIP,kube-vip 在三台 CP 间 ARP 漂移;`controlPlaneEndpoint` 指向它 |
16
+ | LB 地址池 | 192.168.222.200–239 | — | 集群内服务对外暴露用;.200 为共享 Gateway(HTTPS 统一入口,`*.k8s.internal`) |
17
+
18
+ 组件版本、节点 IP 和 Nexus 地址在 [`versions.env`](versions.env)。含 `__变量__` 的 YAML 必须先经 `bash scripts/render-config.sh <文件>` 渲染;安装脚本已接入。业务示例镜像与第二网络网卡/子网仍需单独维护。修改版本不等于完成升级,还要重新制作物料、核查兼容性并按第 16 节逐节点升级。
19
+
20
+ ---
21
+
22
+ ## 0. 先读:结论与几个必须知道的事实
23
+
24
+ ### 0.1 版本选型(2026-09-08 从官方源核实)
25
+
26
+ | 组件 | 版本 | 依据 |
27
+ |---|---|---|
28
+ | Kubernetes | **v1.36.4**(2026-08-11) | 1.36.5 计划 09-15 发布;1.36 EOL 2027-06-28。kubeadm 1.36 期望 `pause:3.10.2`、etcd 3.6.8、CoreDNS 1.14.2 |
29
+ | containerd | **2.3.5**(2026-09-04) | 2.3 是首个年度 LTS,EOL 2028-04-30;containerd 官方给 K8s 1.36 推荐 2.3.0+ / 2.2.0+ |
30
+ | runc / CNI plugins / crictl | v1.5.1 / v1.9.1 / v1.36.0 | 2.x 起不再有 `cri-containerd-cni` 捆绑包,三者需分别下载 |
31
+ | Helm | **v3.21.4** | Helm 4.2.4 已是主线,但 GPU Operator 平台支持页仍只列 Helm v3;3.x 安全修复到 2027-02-10 |
32
+ | Cilium | 1.20.1 / CLI v0.20.0 | 最低内核 5.10,Rocky 9.7 的 5.14 满足 |
33
+ | GPU Operator | **v26.7.0**(2026-08-21) | 支持 K8s 1.33–1.37、containerd 2.0–2.3、Ubuntu 26.04、Rocky 9.7、HGX/DGX B300;toolkit v1.20.0、device-plugin v0.20.0、NFD v0.19.0 |
34
+ | Network Operator | **26.7.0**(2026-08-28) | 支持 K8s 1.32–1.36、Ubuntu 26.04、RHEL 9.x(Rocky 未单列) |
35
+ | HAMi | **v2.10.0**(2026-08-21) | 官方 Lab 在 K8s 1.36 验证;2.10 已适配 1.36 的 GangScheduling feature gate;libvgpu.so(HAMi-core)内置于镜像 |
36
+ | 操作系统 | Ubuntu 26.04(内核 7.0,**已移除 cgroup v1**)、Rocky 9.7(内核 5.14.0-611) | kubelet 1.35+ 默认 `failCgroupV1=true`,全部节点必须 cgroup v2 |
37
+
38
+ ### 0.2 网络层面的坦率评估(决定了 Network Operator 的角色)
39
+
40
+ - **H200 节点的 Intel X710 不支持任何 RDMA**(Intel 官方 RoCE/iWARP 支持列表只有 X722 / E810 / E830)。
41
+ - **B300 节点的 Broadcom BCM57414 支持 RoCEv2**(内核 `bnxt_en` + `bnxt_re`,rdma-core 自带 `libbnxt_re`),内核 6.6+ 的 `bnxt_re` 支持 dma-buf,因此理论上可做 GPUDirect RDMA,但 NVIDIA / Broadcom 都没有 "B300 + BCM57414" 的官方验证组合。
42
+ - **NVIDIA Network Operator 只支持 ConnectX / BlueField**。已核对 v26.7.0 源码:`rdmaSharedDevicePlugin`、`sriovDevicePlugin`、`ofedDriver` 的 DaemonSet 都硬编码 `nodeSelector: feature.node.kubernetes.io/pci-15b3.present=true`(Mellanox 厂商码),在 Broadcom / Intel 节点上**永远不会调度**。
43
+
44
+ 因此本教程中 Network Operator 的定位是:
45
+
46
+ 1. 部署 Operator + Multus + CNI plugins + NV-IPAM(这三者不带厂商选择器),提供第二网络能力,并为将来换 ConnectX 网卡预留路径;
47
+ 2. B300 的 RoCE 通过**独立部署**同款 `k8s-rdma-shared-dev-plugin` 暴露(可选,[manifests/rdma/](manifests/rdma/));
48
+ 3. 两台 GPU 机之间只有 10GbE TCP(受 X710 限制),跨机同步训练可能严重受带宽限制,是否可用需按实际模型、通信量与训练批次压测——把 H200 和 B300 当作两个独立的训练域(各自 NVLink 内部通信),K8s 的价值在统一调度、HAMi 切分给仿真/开发任务、以及为后续加 ConnectX 机器做准备。
49
+
50
+ ### 0.3 GPU Operator 与 HAMi 共存的方式
51
+
52
+ - 驱动已在宿主机 → `driver.enabled=false`(官方明确此模式下允许 Rocky/Ubuntu 混合节点)。
53
+ - GPU Operator 25.10+ 默认开启 CDI,**不再把 `nvidia` 设为 containerd 默认 runtime**;HAMi 官方 Troubleshooting 对此的方案是 `devicePlugin.runtimeClassName=nvidia`,本教程按此配置。
54
+ - HAMi 自带 device-plugin 上报 `nvidia.com/gpu`,与官方 device-plugin 同节点冲突 → 装 HAMi 前把 GPU Operator 的 `devicePlugin.enabled` 改为 false(DCGM Exporter、GFD 保留)。
55
+ - 部署顺序:先用官方 device-plugin 验证整卡可用,再切到 HAMi,出问题时能分清是运行时问题还是 HAMi 问题。
56
+
57
+ ### 0.4 节点没有 DNS:影响与处理
58
+
59
+ 五台节点都没有 `/etc/resolv.conf`,只能靠 `/etc/hosts`。这在 K8s 里有三处会踩坑,教程已内置处理:
60
+
61
+ | 问题 | 处理(脚本自动完成) |
62
+ |---|---|
63
+ | `mirrors.gcoreinc.com` 无法解析 | `versions.env` 填 `NEXUS_IP`,`01` 脚本写入 `/etc/hosts`;containerd / helm / curl 都走 `/etc/hosts` |
64
+ | kubelet 以 `/etc/resolv.conf` 为 Pod DNS 基础,文件不存在时报 `failed to create pod sandbox ... open /etc/resolv.conf: no such file or directory` | `01` 脚本生成一个**不含 nameserver** 的 `/etc/resolv.conf`;主机行为不变,Pod(ClusterFirst)由 kubelet 注入 CoreDNS 地址 |
65
+ | kubeadm 默认 Corefile 有 `forward . /etc/resolv.conf`,没有 nameserver 时 CoreDNS 报 `no nameservers found` 反复崩溃 | `04` 脚本替换为 [`manifests/coredns/coredns-configmap.yaml`](manifests/coredns/coredns-configmap.yaml):删掉 `forward`,用 `hosts` 插件写入 Nexus、VIP 与五台节点,`hosts` 未命中必须 `fallthrough` 到 Kubernetes 插件,否则集群 Service 域名也会解析失败;未配置域名在无上游解析器时返回 SERVFAIL,不保证 NXDOMAIN |
66
+
67
+ 以后新增内网服务域名:同时改各节点 `/etc/hosts` 和 CoreDNS ConfigMap 的 `hosts` 段,然后 `kubectl -n kube-system rollout restart deploy/coredns`。
68
+
69
+ ### 0.5 Nexus Repository Community Edition 3.95 的几个约束
70
+
71
+ - **Docker 仓库访问形式**:3.83+ 新增 "Path-Based Routing"(`https://<nexus>/v2/<repo>/<image>`),传统的 `https://<nexus>/repository/<repo>/v2/<image>` 仍可用。`02` 脚本会先按 `repository` 风格写 `hosts.toml` 并用 `crictl pull` 验证,失败再切 `pathrouting`;确定后可在 `versions.env` 固定 `NEXUS_DOCKER_STYLE`。
72
+ - **匿名拉取**:需要 Nexus 管理端启用 `Docker Bearer Token Realm`(Settings → Security → Realms),并在每个 docker 代理仓库勾选 "Allow anonymous Docker pulls"(3.91 起按仓库独立评估)。containerd 会自动完成匿名 token 流程,不用配凭据。
73
+ - **用量硬上限**:CE 限制 40,000 个组件 / 100,000 次请求每天(`/repository/*` 的所有 HTTP 交互都计数,一次镜像拉取 = 十几次请求)。超限后 Nexus **拒绝缓存新内容**,代理仓库对未缓存镜像会拉取失败,已缓存的仍可用。本集群五节点首装大约几十个镜像、几千次请求,正常不会触顶,但若这台 Nexus 是全公司共用的,部署前看一眼 Usage Center;万一超限,可在一个节点 `ctr -n k8s.io images export` 后拷到其它节点 `ctr -n k8s.io images import`。
74
+ - **内网 CA**:Nexus 若用内网 CA 签发证书,把 PEM 放到仓库并在 `versions.env` 设 `NEXUS_CA_FILE`,`01` 脚本装进系统信任库。
75
+
76
+ ### 0.6 控制平面高可用:3 台 CP + kube-vip VIP
77
+
78
+ 3 台 CP 只解决了 etcd / apiserver 自身的冗余;kubelet、Cilium、kubectl 连的是 `controlPlaneEndpoint` 这一个地址,它必须能在 CP 之间漂移,否则 cp1 一挂全部节点失联。方案:
79
+
80
+ | 组件 | 任一台 CP 宕机后 | 机制 |
81
+ |---|---|---|
82
+ | etcd(堆叠 3 成员) | 正常 | 容忍 1 台故障 |
83
+ | apiserver ×3 / controller-manager / scheduler | 正常 | 各自独立 / leader election |
84
+ | 接入地址 `192.168.222.100:6443` | 5~10 秒内漂到另一台 CP | **kube-vip v1.2.3** 静态 Pod([`configs/kube-vip/kube-vip.yaml`](configs/kube-vip/kube-vip.yaml)),ARP 模式 + Kubernetes Lease 选主,只负责控制面 VIP(`svc_enable=false`,Service 的 LB 交给 Cilium,避免两个 ARP 通告者互相覆盖) |
85
+
86
+ kube-vip 的两个已知坑,脚本已处理:
87
+
88
+ - **K8s ≥ 1.29 的 `admin.conf` 在 `kubeadm init` 结束前没有权限**,kube-vip 会因无法拿 Lease 而起不来、init 卡在等待 VIP。`04` 脚本先用 `super-admin.conf` 渲染静态 Pod,init 完成后重渲染为 `admin.conf`。
89
+ - **cp2/cp3 必须在 join 完成后再放 kube-vip 静态 Pod**(join 期间靠 cp1 持有的 VIP;提前放会因本机还没有 `admin.conf` 而崩)。`06a` 脚本按此顺序执行。
90
+
91
+ 与 Cilium L2 Announcements 一样,VIP 漂移依赖交换机放行 ARP(无 port-security / DAI)。
92
+
93
+ ### 0.7 集群内服务如何暴露给集群外(方案结论)
94
+
95
+ 结论:**三层都用 Cilium 自带能力 + cert-manager,不装 MetalLB、不装 ingress-nginx。**
96
+
97
+ | 层 | 组件 | 作用 |
98
+ |---|---|---|
99
+ | L4:给 Service 一个真实内网 IP | Cilium LB-IPAM + L2 Announcements | `type: LoadBalancer` 自动从 `192.168.222.200-239` 拿 IP,ARP 漂移做高可用 |
100
+ | L7:host / path 路由、gRPC | **Cilium Gateway API**(内置 Envoy,Gateway API v1.6.1) | 一个共享 `Gateway`(IP `.200`,80/443),各业务写 `HTTPRoute` / `GRPCRoute` |
101
+ | TLS:证书签发与自动续期 | **cert-manager v1.21.1** + 集群内部根 CA | 通配证书 `*.k8s.internal`(含网关 IP SAN),90 天自动续签,Envoy 热加载 |
102
+
103
+ 关于"还要不要 ingress-nginx":不需要。Gateway API 是 Kubernetes 官方的 Ingress 继任者(GA 自 2023 年 v1.0),`HTTPRoute` 原生支持 host 匹配、path 前缀/精确/正则、header/query 匹配、URL 重写、重定向、按权重分流、跨命名空间引用(`ReferenceGrant`)——这些在 ingress-nginx 里全靠注解。Cilium 1.20 通过了 Gateway API 全部 Core 一致性测试,支持 `HTTPRoute / GRPCRoute / TLSRoute / BackendTLSPolicy / ListenerSet`,数据面就是它自带的 Envoy。传统 `Ingress` 对象在本集群不再使用(Cilium Ingress 控制器已关闭;某个第三方 chart 只会生成 Ingress 时,把 `ingressController.enabled` 打开即可兼容,或直接写一个 HTTPRoute 替代)。
104
+
105
+ 为什么 L4 选 Cilium L2 而不是 MetalLB:
106
+
107
+ - 你的网络是单个 L2 网段(192.168.222.0/24)、没有 BGP 路由器、没有 DNS、没有云厂商 LB。可选的只有"ARP 漂移 VIP"这一类方案:MetalLB L2 模式,或 Cilium L2 Announcements。功能等价,Cilium 已经在集群里、与 kube-proxy replacement 天然集成,少一个组件。
108
+ - 它就是"软件负载均衡":某个节点通过 ARP 应答持有服务 IP(每个 Service 单独选主,自然分散到不同节点;节点故障 3~7 秒内漂移),进入节点后由 eBPF 分发到所有后端 Pod。单个服务的北向带宽上限 = 持有它的那台节点的网卡(10/25GbE),对 API/UI/推理请求足够。
109
+ - 注意:Cilium 官方仍把 L2 Announcements 标为 Beta(2023 年 1.14 起可用);`externalTrafficPolicy: Local` 不支持;交换机若开了 port-security / IP-MAC 绑定会拦截 ARP 漂移,需要网络侧确认。如不能接受 Beta,把 `l2announcements.enabled` 改 false,换 MetalLB L2(镜像在 quay.io 已代理,chart 需离线上传),Gateway 与 cert-manager 部分不受影响。
110
+
111
+ 关于域名与证书(无 DNS 环境):
112
+
113
+ - 所有 HTTP 服务统一挂在 `<app>.k8s.internal`(`versions.env` 的 `APPS_DOMAIN`,可改成公司域名子域),客户端 hosts 文件写一行 `192.168.222.200 <app>.k8s.internal`;也可以完全不用域名,按 path 路由 `https://192.168.222.200/<app>/`(证书含 IP SAN,不会报名字不匹配)。集群内 Pod 访问这些名字,可把它们加进 CoreDNS 的 `hosts` 段。
114
+ - 没有公网 ACME,证书链是:cert-manager 自签一个 10 年根 CA(`ClusterIssuer internal-ca`)→ 签发 90 天通配证书 → 自动续签。客户端信任一次根 CA(`/root/internal-root-ca.crt`)即可。公司已有内部 CA 时,先确定委派的中间 CA 或签发接口,不能把公司根 CA 私钥直接复制进集群。`10` 脚本发现已导入且不由 Certificate 管理的 CA Secret 会停止,避免自签流程覆盖它;企业 CA 接入需单独配置。
115
+ - 需要按业务单独签证书时,在 Gateway 或 `ListenerSet` 上加注解 `cert-manager.io/cluster-issuer: internal-ca`,cert-manager 会按监听器的 `hostname` 自动签(`tls.mode` 必须是 `Terminate`)。
116
+
117
+ 按工作负载类型的落地方式:
118
+
119
+ | 类型 | 方式 | 例子 |
120
+ |---|---|---|
121
+ | HTTP / HTTPS / gRPC 服务 | 写 `HTTPRoute` / `GRPCRoute`,`parentRefs` 指向 `gateway-system/shared-gateway`,hostname `<app>.k8s.internal` 或 path 前缀;TLS 已由共享网关终结 | Grafana、Hubble UI、MLflow、Jupyter、vLLM / Triton 的 HTTP 与 gRPC 端点 |
122
+ | 非 HTTP 的 TCP / UDP / SSH | `Service type=LoadBalancer`,独立 IP 自动从池分配(或 `lbipam.cilium.io/ips` 固定) | 开发容器 SSH、Ray GCS 端口、自定义 RPC |
123
+ | 分布式训练进程 | `hostNetwork: true`,不经过 Service | torchrun / NCCL 直接用节点 IP |
124
+ | 临时调试 | `NodePort` 或 `kubectl port-forward` | |
125
+
126
+ ### 0.8 已核实但需要你现场确认的点
127
+
128
+ | 事项 | 说明 |
129
+ |---|---|
130
+ | B300 驱动 | Blackwell 只支持 open kernel modules,R580 起支持 HGX B300。`nvidia-smi -q | grep "Kernel Module"` 应为 Open |
131
+ | HAMi 对 B300 | HAMi 官方支持矩阵写 NVIDIA "All",Blackwell 只有 RTX PRO 6000 的测试记录;[HAMi-core#233](https://github.com/Project-HAMi/HAMi-core/issues/233) 指出 CUDA 13.x 新增 Driver API 尚未 hook。在 B300 上先用小任务验证 hami-core 切分,整卡请求仍会注入 hook,不能据此保证 CUDA 13 兼容;仅对已确认独占整卡的任务,可显式设置 `CUDA_DISABLE_CONTROL=true` 绕过 hook,禁止用于显存/算力共享任务 |
132
+ | Nexus IP | `versions.env` 的 `NEXUS_IP` 必填 |
133
+ | VIP 与 LB 地址池 | `192.168.222.100` 与 `192.168.222.200-239` 必须是网段内未被占用的地址(`versions.env` 的 `CP_VIP`、`LB_IP_START/STOP`) |
134
+ | 交换机 ARP 策略 | L2 Announcements 依赖 IP 在节点间 ARP 漂移;开启了 port-security / DAI / IP-MAC 绑定的端口会拦截 |
135
+ | 机器架构 | B300 的 PCI 域是 `0001:`,请在五台机器上 `uname -m` 确认是 x86_64 还是 aarch64;脚本按 `uname -m` 自动选 amd64/arm64,离线包两种架构都打 |
136
+ | 系统软件包 | 节点无公网,`01` 脚本的 apt/dnf 安装是尽力而为;`conntrack / socat / ethtool / iptables` 必须已存在(kubeadm preflight 要求),缺的话拿 .deb/.rpm 拷入安装 |
137
+ | NTP | etcd 与证书对时钟敏感,五台机器需要同一个内网 NTP 源(0.9 节向网络侧申请) |
138
+
139
+ ### 0.9 给网络管理员的开通清单(LB 方案的网络前提)
140
+
141
+ 先说清本集群的负载均衡长什么样,好让网络侧理解要放行什么:
142
+
143
+ - **没有硬件 LB、没有 BGP**。所有"虚 IP"都是 `192.168.222.0/24` 网段里的普通地址,由集群节点用 **ARP 应答 + 免费 ARP(gratuitous ARP)** 宣告;哪台节点持有某个 IP 会随故障切换而变,因此**同一个 IP 会在五台机器的 MAC 之间漂移**。
144
+ - 具体三组地址:
145
+
146
+ | 地址 | 用途 | 谁宣告 | 漂移范围 |
147
+ |---|---|---|---|
148
+ | `192.168.222.100` | Kubernetes API(kubectl / kubelet 接入) | kube-vip | 3 台控制平面(`.10 .11 .12`) |
149
+ | `192.168.222.200` | 共享 Gateway:HTTP 80 / HTTPS 443 | Cilium L2 Announcements | 5 台节点任意一台 |
150
+ | `192.168.222.201–239` | 其它 `type=LoadBalancer` 服务(按需分配,端口随服务) | Cilium L2 Announcements | 5 台节点任意一台 |
151
+
152
+ - 流量进入持有 IP 的节点后,由节点内 eBPF 转到后端 Pod(可能在另一台节点,走节点间 `192.168.222.x` 互通,Pod 源地址会做 SNAT 成节点 IP)。
153
+ - 节点之间 Pod 网络是 **native routing**:节点间直接互发目的地址为 `10.244.0.0/16` 的 IP 包(不封装 VXLAN)。
154
+
155
+ 需要网络侧确认/开通的项,按重要性排序:
156
+
157
+ | # | 事项 | 具体要求 | 不做的后果 |
158
+ |---|---|---|---|
159
+ | 1 | **地址预留** | `192.168.222.100`、`192.168.222.200–239` 从 DHCP 池排除,不分配给任何设备 | IP 冲突,API/服务时通时断 |
160
+ | 2 | **接入交换机放行 ARP 漂移** | 五台节点所在端口:关闭 port-security(sticky MAC / 每端口 MAC 数限制)、DAI(Dynamic ARP Inspection)、IP Source Guard、"ARP 防攻击/防欺骗"类功能;允许一个 IP 对应的 MAC 变化、允许 gratuitous ARP | VIP 切换后新持有者被交换机丢包,故障时集群/服务不可达(这是最常见的坑) |
161
+ | 3 | **同一 L2 广播域** | 五台节点 + 上述虚 IP 必须在同一 VLAN(`192.168.222.0/24`),中间不能有路由/防火墙分隔 | ARP 方案完全失效 |
162
+ | 4 | **网关(三层交换机 SVI / 路由器)的 ARP 表** | 若有集群外网段的客户端要访问这些 IP,网关需接受 gratuitous ARP 或把该 VLAN 的 ARP 老化时间调短(建议 ≤ 60s;Cisco 默认 4 小时) | 故障切换后跨网段客户端要等网关 ARP 过期才恢复(可能几小时) |
163
+ | 5 | **节点间互通不做过滤** | `192.168.222.2 / .3 / .10 / .11 / .12` 之间所有 TCP/UDP/ICMP 放行,且**允许源/目的地址为 `10.244.0.0/16` 的 IP 包在节点之间转发**(uRPF / 反欺骗策略要放开这个网段) | Pod 跨节点不通、健康检查失败 |
164
+ | 6 | **集群外访问集群的端口** | 到 `.100:6443/TCP`(kubectl);到 `.200:80,443/TCP`;到 `.201–239` 按服务端口(通常 22 / 8000–9000 / 自定义);到节点 IP 的 `30000–32767/TCP+UDP`(NodePort,备用) | 用户无法访问 |
165
+ | 7 | **节点出向** | 到 Nexus `mirrors.gcoreinc.com:443`;到内网 NTP 服务器 `123/UDP`(请提供地址) | 拉不到镜像;时钟漂移导致 etcd/证书问题 |
166
+ | 8 | MTU | 全路径 1500(如启用 Jumbo 需五台一致),不要在中间做分片 | 大包丢失、TLS 握手失败 |
167
+ | 9 | 可选:DNS 记录 | 若公司 DNS 可加记录:`k8s-api → .100`,`*.k8s.internal → .200`(或改用公司域名子域) | 没有的话客户端手工维护 hosts 文件 |
168
+
169
+ 节点间端口清单(用于第 5 项若网络侧坚持按端口开放):`6443`(apiserver)、`2379-2380`(etcd,仅 CP 之间)、`10250`(kubelet)、`10257/10259`(controller-manager/scheduler,仅 CP)、`4240`(Cilium 健康检查)、`4244-4245`(Hubble)、`9962-9965`(Cilium 指标)、`2112`(kube-vip 指标)、ICMP;Pod 到 Pod 端口不可枚举,必须整段放行 `10.244.0.0/16`。
170
+
171
+ ---
172
+
173
+ ## 1. 目录结构
174
+
175
+ ```
176
+ deploy_k8s/
177
+ ├── versions.env # 全部版本 / IP / Nexus 地址
178
+ ├── scripts/
179
+ │ ├── 00-make-offline-bundle.sh # 联网机器:下载 GitHub 二进制 + Helm Chart,上传到 Nexus raw
180
+ │ ├── 01-node-prep.sh # 五节点:OS 准备(hosts / resolv.conf / cgroup v2 / 防火墙 ...)
181
+ │ ├── 02-install-containerd.sh # 五节点:containerd 2.3 + runc + CNI + crictl + 镜像代理
182
+ │ ├── 03-install-kube-binaries.sh # 五节点:kubeadm/kubelet/kubectl
183
+ │ ├── kube-vip-manifest.sh # 渲染 kube-vip 静态 Pod(04/06a 调用)
184
+ │ ├── 04-init-control-plane.sh # cp1:kube-vip + kubeadm init + CoreDNS 改造 + Helm + 拉离线 Chart
185
+ │ ├── 05-install-cilium.sh # cp1:Cilium + LB 地址池 + L2 通告策略
186
+ │ ├── 06a-join-control-plane.sh # cp2/cp3:以控制平面身份 join(堆叠 etcd)
187
+ │ ├── 06-join-gpu-node.sh # GPU 节点:join
188
+ │ ├── 07-install-gpu-operator.sh # cp1:GPU Operator
189
+ │ ├── 08-install-network-operator.sh # cp1:Network Operator(+ 可选 B300 RDMA 插件)
190
+ │ ├── 09-install-hami.sh # cp1:切 HAMi 模式 + 安装 HAMi
191
+ │ └── 10-install-cert-manager-gateway.sh # cp1:cert-manager 内部 CA + 共享 Gateway(HTTPS 入口)
192
+ ├── configs/
193
+ │ ├── containerd/config.toml # containerd v3 配置
194
+ │ ├── systemd/{kubelet.service,10-kubeadm.conf}
195
+ │ ├── kubeadm/kubeadm-config.yaml
196
+ │ ├── kube-vip/kube-vip.yaml # 控制面 VIP 静态 Pod 模板
197
+ │ ├── cert-manager/values.yaml
198
+ │ ├── cilium/values.yaml
199
+ │ ├── gpu-operator/{values.yaml,values-hami-mode.yaml}
200
+ │ ├── network-operator/values.yaml
201
+ │ └── hami/values.yaml
202
+ └── manifests/
203
+ ├── coredns/coredns-configmap.yaml # 无上游 DNS 的 Corefile
204
+ ├── cilium/{lb-ipam-pool.yaml,l2-announcement-policy.yaml} # 服务暴露:LB 地址池 + ARP 通告
205
+ ├── network-operator/{nicclusterpolicy.yaml,secondary-network-b300.yaml}
206
+ ├── rdma/rdma-shared-dev-plugin-b300.yaml
207
+ ├── cert-manager/cluster-issuer.yaml # 自签根 CA -> ClusterIssuer internal-ca
208
+ ├── gateway/{shared-gateway.yaml,http-redirect.yaml} # 共享 Gateway + 通配证书(可选 80->443 重定向)
209
+ └── test/{cuda-vectoradd.yaml,nvidia-smi-per-node.yaml,hami-vgpu-demo.yaml,lb-service-demo.yaml,gateway-demo.yaml}
210
+ ```
211
+
212
+ 把整个目录拷到五台机器的 `/root/deploy_k8s`(或任意路径,脚本用相对路径定位 `versions.env`)。
213
+
214
+ 执行顺序总览:`00`(联网机)→ `01/02/03`(五台全部)→ `04`(cp1)→ `06a`(cp2、cp3,依次)→ `05`(cp1)→ `06`(两台 GPU)→ `07` → `08` → `09` → `10`(cp1)。
215
+
216
+ ## 2. 阶段一:离线包与镜像代理准备
217
+
218
+ ### 2.1 Nexus 仓库与上游的对应关系
219
+
220
+ | 上游 | Nexus 地址 | 用途 |
221
+ |---|---|---|
222
+ | `registry.k8s.io` | `/repository/k8s/` | 控制面镜像、pause、NFD、kube-scheduler(HAMi) |
223
+ | `docker.io` | `/repository/docker-hub/` | HAMi、busybox、certgen |
224
+ | `nvcr.io` | `/repository/nvcr/` | GPU Operator / Network Operator 全部组件 |
225
+ | `quay.io` | `/repository/quay/` | Cilium、cert-manager(quay.io/jetstack) |
226
+ | `ghcr.io` | `/repository/ghcr/` | kube-vip(ghcr.io/kube-vip/kube-vip) |
227
+ | `dl.k8s.io` | `/repository/k8s-release/` | kubeadm / kubelet / kubectl 二进制 |
228
+ | `get.helm.sh` | `/repository/helm-get/` | Helm 二进制 |
229
+ | raw hosted | `/repository/content/` | GitHub Releases 二进制、Helm Chart、Gateway API CRD(需自行上传) |
230
+
231
+ 容器镜像**不改名**:所有 values 里保留官方镜像地址,由每台机器 containerd 的 `hosts.toml` 统一转到 Nexus。这样 Operator 内部硬编码的镜像引用也能命中代理。
232
+
233
+ ### 2.2 在一台联网机器上制作离线包
234
+
235
+ 需要 `curl` 和 `helm`(联网机器上任意版本 helm 3/4 都可以,只用来 `helm pull`)。
236
+
237
+ ```bash
238
+ export NEXUS_USER=<有写权限的账号> NEXUS_PASS=<密码>
239
+ bash scripts/00-make-offline-bundle.sh
240
+ ```
241
+
242
+ 脚本会下载并上传到 `https://mirrors.gcoreinc.com/repository/content/k8s-offline/v1.36.4/`:
243
+
244
+ - `bin/`:Helm 3.21.4、containerd 2.3.5、runc 1.5.1、cni-plugins 1.9.1、crictl 1.36.0、cilium CLI 0.20.0(amd64 + arm64)
245
+ - `systemd/containerd.service`
246
+ - `charts/`:cilium-1.20.1、gpu-operator-v26.7.0、network-operator-26.7.0、hami-2.10.0、cert-manager-v1.21.1(OCI chart,`helm pull oci://quay.io/jetstack/charts/cert-manager`)
247
+ - `crds/gateway-api-v1.6.1/`:Gateway API standard 通道的 8 个 CRD(Cilium 1.20 要求 v1.6.1)
248
+ - `SHA256SUMS`
249
+
250
+ 没有直接上传权限时 `UPLOAD=0 bash scripts/00-make-offline-bundle.sh`,把 `./offline` 目录拷进内网后再上传,或直接散到五台机器上并把 `versions.env` 里的 `NEXUS_RAW` 改成 `file://` 路径。
251
+
252
+ ### 2.3 验证 Nexus 各代理可用(任意已在 `/etc/hosts` 写好 Nexus 的内网机器)
253
+
254
+ ```bash
255
+ N=https://mirrors.gcoreinc.com/repository
256
+ curl -fsI $N/k8s-release/release/v1.36.4/bin/linux/amd64/kubeadm | head -1
257
+ curl -fsI $N/helm-get/helm-v3.21.4-linux-amd64.tar.gz | head -1
258
+ curl -fsI $N/content/k8s-offline/v1.36.4/SHA256SUMS | head -1
259
+ # Docker v2 API:返回 200,或 401 且带 "Www-Authenticate: Bearer realm=..." 都算正常(匿名 token 流程由 containerd 完成)
260
+ curl -sI $N/docker-hub/v2/ | grep -E "^HTTP|Www-Authenticate"
261
+ curl -sI https://mirrors.gcoreinc.com/v2/docker-hub/ | grep -E "^HTTP|Www-Authenticate" # 3.83+ Path-Based Routing 形式
262
+ ```
263
+
264
+ 真正的端到端验证在 `02` 脚本里用 `crictl pull` 完成(自动在两种 URL 形式间切换)。若都失败,请 Nexus 管理员核对 0.5 节的三项设置;实在只能用仓库连接器端口时,把 `02` 脚本 `mirror_url` 里的地址改成 `https://mirrors.gcoreinc.com:<port>` 即可。
265
+
266
+ ## 3. 阶段二:五台节点操作系统准备
267
+
268
+ > 五台机器都执行,root。GPU 机器上的驱动、CUDA 保持不动。
269
+ > 执行前先在 `versions.env` 填好 `NEXUS_IP`(以及内网 CA 时的 `NEXUS_CA_FILE`)。
270
+
271
+ ```bash
272
+ cd /root/deploy_k8s
273
+ # 控制平面(各自的主机名)
274
+ bash scripts/01-node-prep.sh k8s-cp1 # 192.168.222.10
275
+ bash scripts/01-node-prep.sh k8s-cp2 # 192.168.222.11
276
+ bash scripts/01-node-prep.sh k8s-cp3 # 192.168.222.12
277
+ # H200 (Rocky 9.7)
278
+ bash scripts/01-node-prep.sh gpu-h200
279
+ # B300 (Ubuntu 26.04)
280
+ bash scripts/01-node-prep.sh gpu-b300
281
+ ```
282
+
283
+ 脚本做的事:设置主机名与 `/etc/hosts`(含 Nexus 域名);可选安装内网 CA 并 curl 探活 Nexus;**生成不含 nameserver 的 `/etc/resolv.conf`**(见 0.4);关闭 swap;加载 `overlay`/`br_netfilter`;写 sysctl;**校验 cgroup v2**(不是 v2 直接退出);Ubuntu 关 ufw / Rocky 关 firewalld 并把 SELinux 设为 permissive;尽力安装 chrony、conntrack、socat、ipset 等依赖(apt/dnf 仓库不可达时跳过,但 `conntrack / socat / ethtool / iptables / curl / tar` 缺任何一个会直接退出);执行 `chronyc waitsync`,未同步会报错停止(须先准备时间源,见运维文档);GPU 机器上打印 `nvidia-smi` 并启用 `nvidia-persistenced`。
284
+
285
+ 补充检查(GPU 机器):
286
+
287
+ ```bash
288
+ nvidia-smi -q | grep -E "Driver Version|Kernel Module" # B300 必须是 Open
289
+ lsmod | grep -E "^nvidia" # 有 nvidia, nvidia_uvm
290
+ # Rocky 9.7:确认 dnf 不会自动升级内核把驱动搞坏
291
+ grep -E "^exclude" /etc/dnf/dnf.conf || echo 'exclude=kernel* nvidia*' >> /etc/dnf/dnf.conf
292
+ # Ubuntu:apt-mark hold 内核与驱动
293
+ apt-mark hold "linux-image-*" "linux-headers-*" "nvidia-*" 2>/dev/null | tail -1
294
+ ```
295
+
296
+ ## 4. 阶段三:安装 containerd 2.3 LTS(五台节点)
297
+
298
+ ```bash
299
+ bash scripts/02-install-containerd.sh
300
+ ```
301
+
302
+ 脚本做的事:
303
+
304
+ 1. 从 Nexus raw 下载 containerd/runc/CNI/crictl 并安装到 `/usr/local/bin`、`/usr/local/sbin/runc`、`/opt/cni/bin`;
305
+ 2. 写入 [`configs/containerd/config.toml`](configs/containerd/config.toml)(config version 3),要点:
306
+ - `SystemdCgroup = true`
307
+ - `pinned_images.sandbox = registry.k8s.io/pause:3.10.2`(与 kubeadm 1.36 一致)
308
+ - `registry.config_path = /etc/containerd/certs.d`
309
+ - `imports = ['/etc/containerd/conf.d/*.toml']` —— GPU Operator 的 toolkit 默认把 nvidia runtime 写成 drop-in `/etc/containerd/conf.d/99-nvidia.toml`
310
+ 3. 为 `docker.io / registry.k8s.io / nvcr.io / quay.io / ghcr.io` 各生成一份 `hosts.toml`,形如:
311
+
312
+ ```toml
313
+ server = "https://mirrors.gcoreinc.com/repository/nvcr/v2"
314
+
315
+ [host."https://mirrors.gcoreinc.com/repository/nvcr/v2"] # pathrouting 风格则是 https://mirrors.gcoreinc.com/v2/nvcr
316
+ capabilities = ["pull", "resolve"]
317
+ override_path = true
318
+ ```
319
+
320
+ `override_path = true` 表示 URL 里已经是 API 根路径,containerd 直接在后面拼 `/<image>/manifests/<tag>`,不再自行追加 `/v2`。`server` 也指向 Nexus,失败时不回退公网。
321
+ 4. 启动 containerd,先按 `repository` 风格 `crictl pull docker.io/library/busybox`,失败则改写为 `pathrouting` 风格重试;成功后再拉 pause 与 Cilium 镜像验证 `registry.k8s.io` / `quay.io` 代理。两种都失败时打印 Nexus 侧需要检查的设置并退出。
322
+
323
+ 验证:
324
+
325
+ ```bash
326
+ containerd --version # containerd github.com/containerd/containerd/v2 v2.3.5
327
+ crictl images
328
+ crictl pull nvcr.io/nvidia/cuda:12.8.1-base-ubuntu24.04 # 走 nvcr 代理
329
+ journalctl -u containerd -n 20 --no-pager
330
+ ```
331
+
332
+ ## 5. 阶段四:安装 kubeadm / kubelet / kubectl(五台节点)
333
+
334
+ ```bash
335
+ bash scripts/03-install-kube-binaries.sh
336
+ ```
337
+
338
+ 不用发行版软件仓库(`pkgs.k8s.io` 没有代理,且两种发行版包管理不同),统一从 `dl.k8s.io` 代理下载 v1.36.4 二进制,先在临时目录校验 sha256 再安装,另写入 `20-node-ip.conf` 将 kubelet 绑定到 `versions.env` 中的本机地址,避免多网卡自动选错;基础 systemd 单元内容等同 `kubernetes/release` 仓库的 `kubelet.service` + `10-kubeadm.conf`(ExecStart 指向 `/usr/local/bin/kubelet`)。kubelet 此时 enable 但不启动,等 kubeadm 生成配置后自动拉起。
339
+
340
+ ## 6. 阶段五:控制平面(cp1 init → cp2/cp3 join)
341
+
342
+ > 先把三台控制平面凑齐再装 Cilium:Cilium operator 是双副本且有反亲和,只有一个节点时第二个副本会 Pending,`cilium status --wait` 过不去。控制平面 join 不依赖 CNI。
343
+
344
+ ### 6.1 初始化 k8s-cp1
345
+
346
+ ```bash
347
+ bash scripts/04-init-control-plane.sh
348
+ ```
349
+
350
+ 关键点(见 [`configs/kubeadm/kubeadm-config.yaml`](configs/kubeadm/kubeadm-config.yaml)):
351
+
352
+ - 脚本在 `kubeadm init` **之前**把 kube-vip 静态 Pod 写到 `/etc/kubernetes/manifests/`(自动探测持有 `192.168.222.x` 的网卡名、先挂 `super-admin.conf`),kubelet 一起来就把 VIP `192.168.222.100` 挂到本机;init 完成后重渲染为 `admin.conf` 并校验 `https://192.168.222.100:6443/healthz`
353
+ - `kubeadm.k8s.io/v1beta4`,`kubernetesVersion: v1.36.4`,`controlPlaneEndpoint: 192.168.222.100:6443`(VIP)
354
+ - `certSANs` 写入 VIP、三台 CP 的 IP/主机名与 `k8s-api`,排障时直连任一台 apiserver 也能过证书校验
355
+ - `kubeadm init --upload-certs`:把 CA 私钥加密存入集群,cp2/cp3 用 `--certificate-key` 加入即可,不用手工拷 `/etc/kubernetes/pki`(key 2 小时有效,过期在 cp1 执行 `kubeadm init phase upload-certs --upload-certs` 重新生成)
356
+ - `podSubnet 10.244.0.0/16`,`serviceSubnet 10.96.0.0/12`
357
+ - `cgroupDriver: systemd`,`failCgroupV1: true`
358
+ - `resolvConf: /etc/resolv.conf`(`01` 脚本已保证文件存在)
359
+ - `--skip-phases=addon/kube-proxy`:kube-proxy 由 Cilium 替代
360
+ - init 完成后立即用 [`manifests/coredns/coredns-configmap.yaml`](manifests/coredns/coredns-configmap.yaml) 覆盖 CoreDNS 配置(无 forward,hosts 段含 Nexus、VIP 与五节点)
361
+ - 脚本在 init 前先下载 Helm、五个 Chart、Gateway API CRD 与 Cilium CLI,并按离线包 `SHA256SUMS` 校验,避免初始化后才发现缺包;init 后打印两条 join 命令:带 `--control-plane --certificate-key` 的给 cp2/cp3,不带的给 GPU 节点
362
+
363
+ 如果首次 `kubeadm init` 失败,先保留日志并排查;仅当确认没有业务/已有 etcd 数据且决定重建该节点时,按第 15 节备份后 reset,再修正重试(reset 会连 `manifests/kube-vip.yaml` 一起清掉,`04` 脚本会重新生成)。init 卡在 `waiting for the kubelet to boot up the control plane` 时另开终端看 `crictl ps -a | grep kube-vip` 与 `ip -4 addr | grep 192.168.222.100`:VIP 没出现多半是 kube-vip 镜像没拉到(ghcr 代理)或网卡名探测错。
364
+
365
+ ### 6.2 k8s-cp2 / k8s-cp3 以控制平面加入
366
+
367
+ 用 `04` 脚本末尾打印的**带 `--control-plane --certificate-key`** 的那条命令(也可在 cp1 重新生成:`kubeadm init phase upload-certs --upload-certs` 得到 key,再 `kubeadm token create --print-join-command --certificate-key <key>`):
368
+
369
+ ```bash
370
+ bash scripts/06a-join-control-plane.sh "kubeadm join 192.168.222.100:6443 --token xxxx --discovery-token-ca-cert-hash sha256:xxxx --control-plane --certificate-key yyyy"
371
+ ```
372
+
373
+ 脚本自动附加 `--apiserver-advertise-address <本机 IP>`、`--cri-socket`、`--node-name`;join 完成后再渲染 kube-vip 静态 Pod(本机开始参与 VIP 选主),打印当前 VIP 持有者(Lease `kube-system/plndr-cp-lock`),并列出 etcd 成员。两台依次执行,不要并行(etcd 成员变更需串行)。此时三台节点都是 `NotReady`(还没有 CNI),属正常。
374
+
375
+ ```bash
376
+ kubectl get nodes # 3 台 control-plane,NotReady
377
+ kubectl -n kube-system get pods -l tier=control-plane -o wide # 3 × etcd / apiserver / controller-manager / scheduler
378
+ kubectl -n kube-system get pods -o wide | grep kube-vip # 3 个
379
+ ```
380
+
381
+ 三台都加入后做一次 VIP 故障切换演练(现在做或业务上线前做都行):
382
+
383
+ ```bash
384
+ kubectl -n kube-system get lease plndr-cp-lock -o jsonpath='{.spec.holderIdentity}{"\n"}' # 当前持有者,如 k8s-cp1
385
+ # 在持有者上:systemctl stop kubelet && crictl stop $(crictl ps --name kube-vip -q) 或直接断电/断网
386
+ # 在另一台 CP 上循环观察,5~10 秒内应恢复:
387
+ while true; do date +%T; kubectl --server=https://192.168.222.100:6443 get nodes --request-timeout=2s >/dev/null && echo OK; sleep 1; done
388
+ # 演练结束后恢复:systemctl start kubelet
389
+ ```
390
+
391
+ ## 7. 阶段六:安装 Cilium(仅 k8s-cp1)
392
+
393
+ ```bash
394
+ bash scripts/05-install-cilium.sh
395
+ ```
396
+
397
+ [`configs/cilium/values.yaml`](configs/cilium/values.yaml):
398
+
399
+ - `kubeProxyReplacement: true`,`k8sServiceHost` 指向 VIP `192.168.222.100`(= controlPlaneEndpoint,CP 故障时 Cilium 不失联),`ipam.mode: kubernetes`,五台机器同一 L2 网段所以用 `routingMode: native + autoDirectNodeRoutes`(无 VXLAN 封装开销)
400
+ - 服务暴露:`l2announcements.enabled: true`(lease 5s/2s/500ms,故障切换约 3~7 秒)、`k8sClientRateLimit 30/60`(每个 LB Service 一个 Lease,QPS ≈ Service 数 ÷ renewDeadline)、`externalIPs.enabled: true`
401
+ - Gateway API:`gatewayAPI.enabled: true`(`enableAlpn` 让 443 协商 HTTP/2,`enableAppProtocol` 支持 h2c 后端),`ingressController.enabled: false`;脚本在 helm install 之前先 `kubectl apply --server-side` Gateway API v1.6.1 的 8 个 CRD(GatewayClass、Gateway、HTTPRoute、GRPCRoute、ReferenceGrant、BackendTLSPolicy、TLSRoute、ListenerSet),Cilium operator 启动时检测到 CRD 才会启用控制器
402
+ - `operator.replicas: 2`,Hubble relay 开启并容忍控制平面污点、UI 关闭;`cni.exclusive: false` 避免删除 Multus 配置
403
+
404
+ 脚本在 Cilium 就绪后 apply [`manifests/cilium/lb-ipam-pool.yaml`](manifests/cilium/lb-ipam-pool.yaml)(普通池 `.201–239`,另有仅匹配共享 Gateway Service 的 `.200` 专用池,防止固定 IP 被其他服务抢占)与 [`manifests/cilium/l2-announcement-policy.yaml`](manifests/cilium/l2-announcement-policy.yaml)(`CiliumL2AnnouncementPolicy`,默认全部节点参与通告;只想从控制平面进流量就打开其中的 `nodeSelector`)。
405
+
406
+ 验证:
407
+
408
+ ```bash
409
+ cilium status --wait
410
+ kubectl get nodes # 三台 CP 全部 Ready
411
+ kubectl -n kube-system get pods # coredns 2/2 Running(不再 CrashLoop),cilium-operator 2/2
412
+ kubectl get ciliumloadbalancerippools # lan-pool DISABLED=false CONFLICTING=False IPS AVAILABLE=40
413
+ kubectl get gatewayclass # cilium ACCEPTED=True(Gateway 本体在阶段十一创建)
414
+ cilium connectivity test --test '!pod-to-world' # 离线跳过外网用例(可选,耗时数分钟)
415
+ ```
416
+
417
+ 脚本末尾会起一个 busybox 做三项 DNS 检查:`kubernetes.default.svc.cluster.local` 解析成功、`mirrors.gcoreinc.com` 解析到 `NEXUS_IP`、`www.example.com` 解析失败(无上游时为 SERVFAIL,不能当作 NXDOMAIN)。
418
+
419
+ ## 8. 阶段七:GPU 节点加入集群
420
+
421
+ 用 `04` 脚本打印的**不带** `--control-plane` 的那条(脚本会附加 `--cri-socket` 与 `--node-name`;token 24h 过期后在任一 CP 上 `kubeadm token create --print-join-command` 重新生成):
422
+
423
+ ```bash
424
+ bash scripts/06-join-gpu-node.sh "kubeadm join 192.168.222.100:6443 --token xxxx --discovery-token-ca-cert-hash sha256:xxxx"
425
+ ```
426
+
427
+ 回到 cp1:
428
+
429
+ ```bash
430
+ kubectl get nodes -o wide
431
+ # NAME STATUS ROLES VERSION OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
432
+ # k8s-cp1 Ready control-plane v1.36.4 Ubuntu 26.04 LTS 7.0.x containerd://2.3.5
433
+ # k8s-cp2 Ready control-plane v1.36.4 Ubuntu 26.04 LTS 7.0.x containerd://2.3.5
434
+ # k8s-cp3 Ready control-plane v1.36.4 Ubuntu 26.04 LTS 7.0.x containerd://2.3.5
435
+ # gpu-b300 Ready <none> v1.36.4 Ubuntu 26.04 LTS 7.0.x containerd://2.3.5
436
+ # gpu-h200 Ready <none> v1.36.4 Rocky Linux 9.7 (...) 5.14.0-611.x containerd://2.3.5
437
+ ```
438
+
439
+ 五个节点 Ready 且 `cilium status` 全绿后再进入下一步。
440
+
441
+ ### 8.1 验证 LoadBalancer IP(L4)
442
+
443
+ ```bash
444
+ kubectl apply -f manifests/test/lb-service-demo.yaml
445
+ kubectl get svc lb-demo # EXTERNAL-IP 应为 192.168.222.201 之类的池内地址
446
+ # 在任意一台内网机器(不必是集群节点)上:
447
+ curl -s http://<lb-demo 的 EXTERNAL-IP>/ | head -3
448
+ # 看当前是哪个节点在应答该 IP 的 ARP:
449
+ kubectl -n kube-system get lease | grep l2announce
450
+ kubectl delete -f manifests/test/lb-service-demo.yaml
451
+ ```
452
+
453
+ 若 `EXTERNAL-IP` 一直 `<pending>`:`kubectl get svc lb-demo -o jsonpath='{.status.conditions}'` 看 LB-IPAM 原因;若有 IP 但 curl 不通:`kubectl -n kube-system exec ds/cilium -- cilium-dbg shell -- db/show l2-announce` 看是否在通告、并确认交换机未拦截 ARP。HTTP 路由与 TLS 的验证在阶段十一。
454
+
455
+ ## 9. 阶段八:GPU Operator v26.7.0(仅 k8s-cp1)
456
+
457
+ ```bash
458
+ bash scripts/07-install-gpu-operator.sh
459
+ ```
460
+
461
+ 脚本先给两台 GPU 机打 `gpu=on`(HAMi 需要)和角色标签,再用 [`configs/gpu-operator/values.yaml`](configs/gpu-operator/values.yaml) 安装。values 要点:
462
+
463
+ | 项 | 值 | 原因 |
464
+ |---|---|---|
465
+ | `driver.enabled` | `false` | 宿主机已装驱动;Operator 只装 toolkit / device-plugin / DCGM / GFD / NFD |
466
+ | `cdi.enabled` | `true`(默认) | 26.x 默认;`nvidia` runtime 以 RuntimeClass 形式提供,不改默认 runtime |
467
+ | `toolkit.env` | `CONTAINERD_CONFIG=/etc/containerd/config.toml`、`RUNTIME_DROP_IN_CONFIG=/etc/containerd/conf.d/99-nvidia.toml`、`RUNTIME_CONFIG_SOURCE=command,file` | 与阶段三的 containerd 配置对应 |
468
+ | `devicePlugin.enabled` | `true` | 本阶段用官方插件验证整卡;阶段十切 HAMi 时关掉 |
469
+ | `migManager / gds / gdrcopy` | `false` | 本集群不用 MIG(HAMi 切分)、不用 GDS |
470
+ | `validator.plugin.env WITH_WORKLOAD=false` | | 跳过内置工作负载校验,改用下方手工验证 |
471
+
472
+ 等待全部就绪(Rocky 与 Ubuntu 节点各一套 operands):
473
+
474
+ ```bash
475
+ kubectl -n gpu-operator get pods -o wide
476
+ # 期望:gpu-operator、nfd-master/worker、gpu-feature-discovery、nvidia-container-toolkit-daemonset、
477
+ # nvidia-device-plugin-daemonset、nvidia-dcgm-exporter、nvidia-operator-validator 均 Running / validator Completed
478
+ kubectl get runtimeclass nvidia
479
+ kubectl describe node gpu-b300 | grep -E "nvidia.com/gpu(\.product|\.count|:)"
480
+ # 宿主机上确认 drop-in 已生成
481
+ ssh gpu-b300 cat /etc/containerd/conf.d/99-nvidia.toml
482
+ ```
483
+
484
+ 功能验证(整卡):
485
+
486
+ ```bash
487
+ kubectl apply -f manifests/test/nvidia-smi-per-node.yaml
488
+ kubectl logs smi-h200 ; kubectl logs smi-b300 # 各自输出 nvidia-smi 表格
489
+ kubectl apply -f manifests/test/cuda-vectoradd.yaml
490
+ kubectl logs cuda-vectoradd # Test PASSED
491
+ kubectl delete -f manifests/test/nvidia-smi-per-node.yaml -f manifests/test/cuda-vectoradd.yaml
492
+ ```
493
+
494
+ Rocky 节点若 toolkit Pod 报找不到 `nvidia-smi`:26.7.0 已修复 `/usr/bin` 为符号链接的情况;再不行检查 `hostPaths.rootFS` 与驱动实际安装位置。
495
+
496
+ ## 10. 阶段九:Network Operator 26.7.0(仅 k8s-cp1)
497
+
498
+ ```bash
499
+ bash scripts/08-install-network-operator.sh
500
+ # 需要 B300 RoCE 设备插件时:
501
+ ENABLE_B300_RDMA=1 bash scripts/08-install-network-operator.sh
502
+ ```
503
+
504
+ 安装内容:
505
+
506
+ 1. Operator 本体([`configs/network-operator/values.yaml`](configs/network-operator/values.yaml)):`nfd.enabled=false`(用 GPU Operator 那份 NFD),不装 SR-IOV Operator、Maintenance Operator、admission controller;
507
+ 2. [`NicClusterPolicy`](manifests/network-operator/nicclusterpolicy.yaml):只有 `secondaryNetwork.multus`、`secondaryNetwork.cniPlugins`、`nvIpam`。不写 `ofedDriver`/`nicFeatureDiscovery`/`rdmaSharedDevicePlugin`(原因见 0.2 节);
508
+ 3. 可选 [`manifests/rdma/rdma-shared-dev-plugin-b300.yaml`](manifests/rdma/rdma-shared-dev-plugin-b300.yaml):用 Network Operator 同款镜像 `nvcr.io/nvidia/mellanox/k8s-rdma-shared-dev-plugin:network-operator-v26.7.0`,`nodeSelector` 固定到 gpu-b300,selector `vendors: ["14e4"]`,把 Broadcom RoCE 设备以 `rdma/roce_shared` 暴露。
509
+
510
+ B300 宿主机前置(RoCE 才需要):
511
+
512
+ ```bash
513
+ apt install -y rdma-core ibverbs-providers ibverbs-utils rdmacm-utils
514
+ modprobe bnxt_re && echo bnxt_re > /etc/modules-load.d/bnxt_re.conf
515
+ rdma link show # bnxt_re0/1 ... state ACTIVE
516
+ ibv_devinfo | head # hca_id: bnxt_re0
517
+ ```
518
+
519
+ 验证:
520
+
521
+ ```bash
522
+ kubectl get nicclusterpolicy -o wide # STATE ready
523
+ kubectl -n nvidia-network-operator get pods # network-operator、cni-plugins-ds、kube-multus-ds、nv-ipam-controller/node
524
+ ssh gpu-b300 ls /etc/cni/net.d/ # 出现 00-multus.conf,Cilium 配置被 multus 代理
525
+ # 需要第二网络时,改 manifests/network-operator/secondary-network-b300.yaml 里的接口名与网段后:
526
+ kubectl apply -f manifests/network-operator/secondary-network-b300.yaml # 先手动填写网卡、子网与 MTU
527
+ kubectl get network-attachment-definitions -A
528
+ ```
529
+
530
+ 将来加装 ConnectX 网卡的机器时:在 NicClusterPolicy 加回 `ofedDriver`(DOCA 3.5.0,`nvcr.io/nvidia/mellanox/doca-driver:doca3.5.0-26.07-0.7.7.0-0`)、`rdmaSharedDevicePlugin`、`nicFeatureDiscovery`,并把 `nfd.deployNodeFeatureRules` 打开或手工创建 `pci-15b3` NodeFeatureRule,其余不用动。
531
+
532
+ ## 11. 阶段十:HAMi v2.10.0(仅 k8s-cp1)
533
+
534
+ ```bash
535
+ bash scripts/09-install-hami.sh
536
+ ```
537
+
538
+ 脚本顺序:
539
+
540
+ 1. `helm upgrade gpu-operator ... -f values.yaml -f values-hami-mode.yaml` 关闭官方 device-plugin,并确认 `nvidia-device-plugin-daemonset` 已消失;
541
+ 2. 给 `kube-system / gpu-operator / nvidia-network-operator / hami-system / cert-manager` 打 `hami.io/webhook=ignore`;
542
+ 3. 安装 HAMi([`configs/hami/values.yaml`](configs/hami/values.yaml)),要点:
543
+
544
+ | 项 | 值 | 原因 |
545
+ |---|---|---|
546
+ | `scheduler.kubeScheduler.image` | `registry.k8s.io/kube-scheduler`,tag 留空 | chart 默认是阿里云镜像站,内网只代理了 `registry.k8s.io`;tag 留空自动取集群版本 v1.36.4 |
547
+ | `devicePlugin.runtimeClassName` | `nvidia` | GPU Operator 25.10+ CDI 模式下 nvidia 不是默认 runtime;webhook 会给使用 HAMi 资源的 Pod 自动补上 `runtimeClassName` |
548
+ | `devicePlugin.gpuOperatorToolkitReady.enabled` | `true` | initContainer 等待 `/run/nvidia/validations/toolkit-ready` |
549
+ | `devicePlugin.deviceListStrategy` | `envvar` | hami-core 模式依赖 `NVIDIA_VISIBLE_DEVICES`,不要改 CDI |
550
+ | `devicePlugin.nodeConfiguration` | gpu-h200 `devicesplitcount=10`、gpu-b300 `devicesplitcount=4` | H200 更多用于仿真/开发共享,B300 主要跑训练 |
551
+ | `scheduler.nodeSelector/tolerations` | 控制平面 | scheduler + webhook 不占 GPU 机 |
552
+
553
+ 验证:
554
+
555
+ ```bash
556
+ kubectl -n hami-system get pods -o wide # hami-scheduler、hami-device-plugin x2 Running
557
+ kubectl get node gpu-h200 -o jsonpath='{.status.allocatable.nvidia\.com/gpu}' # 物理卡数 × 10
558
+ kubectl apply -f manifests/test/hami-vgpu-demo.yaml
559
+ kubectl get pod -o wide hami-vgpu-a hami-vgpu-b # schedulerName=hami-scheduler,可能落到同一张卡
560
+ kubectl logs hami-vgpu-a # nvidia-smi 显示显存上限 8192MiB
561
+ kubectl exec hami-vgpu-a -- nvidia-smi --query-gpu=uuid --format=csv
562
+ kubectl exec hami-vgpu-b -- nvidia-smi --query-gpu=uuid --format=csv # 相同 UUID = 共享同一张卡
563
+ kubectl delete -f manifests/test/hami-vgpu-demo.yaml
564
+ ```
565
+
566
+ 使用方式:
567
+
568
+ ```yaml
569
+ resources:
570
+ limits:
571
+ nvidia.com/gpu: 1 # vGPU 数(每个对应一张物理卡的切片)
572
+ nvidia.com/gpumem: 24576 # MiB;或 nvidia.com/gpumem-percentage: 50
573
+ nvidia.com/gpucores: 50 # 算力百分比
574
+ ```
575
+
576
+ - 大模型整卡训练:只写 `nvidia.com/gpu: 8` 时仍需验证 HAMi 默认资源分配和 hook 行为;不写 gpumem/gpucores **不会自动移除 hook**。已确认独占整卡且无需 HAMi 限制时才显式配置 `CUDA_DISABLE_CONTROL=true`,并验证 CUDA 运算,不能只看 `nvidia-smi`。
577
+ - 3D 仿真 / 推理 / 开发环境:加 gpumem / gpucores 切分。
578
+ - 不能用 `nodeName` 指定节点(绕过 hami-scheduler 会分配失败),用 `nodeSelector`。
579
+ - 命名空间排除 HAMi webhook 会绕过资源注入;不能把忽略 webhook 当作正常获取共享 GPU 的方式。HAMi 服务设为 ClusterIP,webhook 使用 `failurePolicy: Fail`,故障时未排除命名空间的新 Pod(含普通 Pod)会被阻止,恢复服务后再提交。
580
+
581
+ ## 12. 阶段十一:cert-manager + 共享 Gateway(HTTPS 统一入口,仅 k8s-cp1)
582
+
583
+ ```bash
584
+ bash scripts/10-install-cert-manager-gateway.sh
585
+ ```
586
+
587
+ 脚本做的事:
588
+
589
+ 1. 安装 cert-manager v1.21.1([`configs/cert-manager/values.yaml`](configs/cert-manager/values.yaml):`crds.enabled`,`config.gatewayAPI.enabled=true` 让它监听 Gateway 资源;组件全部落在控制平面)。
590
+ 2. apply [`manifests/cert-manager/cluster-issuer.yaml`](manifests/cert-manager/cluster-issuer.yaml):`selfsigned-bootstrap` → 10 年根 CA(Secret `cert-manager/internal-root-ca`)→ `ClusterIssuer internal-ca`;把根 CA 导出到 `/root/internal-root-ca.crt`。
591
+ 3. apply [`manifests/gateway/shared-gateway.yaml`](manifests/gateway/shared-gateway.yaml):命名空间 `gateway-system`、通配证书 `apps-wildcard-tls`(`*.k8s.internal`、`k8s.internal`、IP `192.168.222.200`,90 天自动续签)、`Gateway shared-gateway`(class `cilium`,`infrastructure.annotations.lbipam.cilium.io/ips` 固定 IP,监听 80 HTTP + 443 HTTPS Terminate,允许所有命名空间的 Route 挂接)。
592
+ 4. 用 [`manifests/test/gateway-demo.yaml`](manifests/test/gateway-demo.yaml) 验证 host 路由(`https://web-demo.k8s.internal/`)、path 路由(`https://192.168.222.200/demo/`)和证书链,然后删除。
593
+
594
+ ### 12.1 业务接入模板
595
+
596
+ 服务方只需在自己的命名空间写一个 HTTPRoute(不需要碰证书和 Gateway):
597
+
598
+ ```yaml
599
+ apiVersion: gateway.networking.k8s.io/v1
600
+ kind: HTTPRoute
601
+ metadata:
602
+ name: mlflow
603
+ namespace: mlops
604
+ spec:
605
+ parentRefs:
606
+ - name: shared-gateway
607
+ namespace: gateway-system
608
+ hostnames: ["mlflow.k8s.internal"] # host 路由;去掉这行 + 加 path 匹配即为 path 路由
609
+ rules:
610
+ - backendRefs:
611
+ - name: mlflow
612
+ port: 5000
613
+ ```
614
+
615
+ gRPC(如 Triton 8001 / vLLM gRPC)用 `GRPCRoute`,后端 Service 的端口加 `appProtocol: kubernetes.io/h2c`,客户端走 `https://<host>:443`(ALPN 协商 h2):
616
+
617
+ ```yaml
618
+ apiVersion: gateway.networking.k8s.io/v1
619
+ kind: GRPCRoute
620
+ metadata:
621
+ name: triton-grpc
622
+ namespace: inference
623
+ spec:
624
+ parentRefs: [{name: shared-gateway, namespace: gateway-system, sectionName: https}]
625
+ hostnames: ["triton.k8s.internal"]
626
+ rules:
627
+ - backendRefs: [{name: triton, port: 8001}]
628
+ ```
629
+
630
+ 常用能力对照(都在 HTTPRoute 里,不用注解):
631
+
632
+ | 需求 | 写法 |
633
+ |---|---|
634
+ | path 前缀转发并去前缀 | `matches.path: {type: PathPrefix, value: /demo}` + `filters: URLRewrite.path.replacePrefixMatch: /` |
635
+ | 80 → 443 重定向 | `kubectl apply -f manifests/gateway/http-redirect.yaml`(全局,可选) |
636
+ | 金丝雀分流 | 同一 rule 下两个 `backendRefs` 带 `weight` |
637
+ | 加/删请求头、CORS | `filters: RequestHeaderModifier` / `ResponseHeaderModifier` |
638
+ | 超时、重试 | `rules[].timeouts.request`(v1 GA);重试属 experimental 通道,本集群未装 |
639
+ | 跨命名空间引用 Service | 目标命名空间放 `ReferenceGrant` |
640
+ | 某业务独立域名+独立证书 | 该业务命名空间建 `ListenerSet` 挂到 `shared-gateway`,注解 `cert-manager.io/cluster-issuer: internal-ca`,`hostname` 写业务域名 |
641
+ | 后端本身是 HTTPS | `BackendTLSPolicy` 指向后端 Service,`validation.caCertificateRefs` 引用 CA ConfigMap |
642
+
643
+ ### 12.2 客户端侧
644
+
645
+ - hosts 文件:`192.168.222.200 mlflow.k8s.internal grafana.k8s.internal ...`(Windows `C:\Windows\System32\drivers\etc\hosts`);或纯 IP + path。
646
+ - 信任根 CA:把 `/root/internal-root-ca.crt` 导入系统/浏览器信任库;命令行 `curl --cacert internal-root-ca.crt`;Python `REQUESTS_CA_BUNDLE=/path/internal-root-ca.crt`。根 CA 有效期 10 年并不代表永不续期或轮换;轮换须提前分发新旧信任链,并重新签发叶证书。正常叶证书续期在 CA 未变化时不影响客户端。
647
+ - 集群内部 Pod 想用同样的域名访问:把 `192.168.222.200 <app>.k8s.internal` 加进 [`manifests/coredns/coredns-configmap.yaml`](manifests/coredns/coredns-configmap.yaml) 的 `hosts` 段并重新渲染并 apply。
648
+
649
+ ## 13. 全集群最终检查清单
650
+
651
+ ```bash
652
+ kubectl get nodes -o wide
653
+ kubectl get pods -A -o wide | grep -v Running | grep -v Completed # 应为空
654
+ kubectl -n kube-system get pods -o wide | grep -E 'kube-vip|etcd|apiserver' # 各 3 个
655
+ kubectl -n kube-system get lease plndr-cp-lock -o jsonpath='{.spec.holderIdentity}{"\n"}' # VIP 持有者
656
+ kubectl get ciliumloadbalancerippools; kubectl -n gateway-system get gateway,certificate # LB 池 / 共享网关 PROGRAMMED / 证书 Ready
657
+ kubectl get clusterissuer # internal-ca READY
658
+ cilium status
659
+ kubectl -n gpu-operator get pods
660
+ kubectl -n nvidia-network-operator get pods
661
+ kubectl -n hami-system get pods
662
+ kubectl get runtimeclass
663
+ kubectl describe node gpu-b300 | sed -n '/Allocatable/,/Events/p'
664
+ helm list -A
665
+ ```
666
+
667
+ ## 14. 常见问题
668
+
669
+ | 现象 | 排查 |
670
+ |---|---|
671
+ | `crictl pull` 报 404 / 401 / `failed to resolve reference` | 检查 `/etc/containerd/certs.d/<registry>/hosts.toml` 的 URL 风格(0.5 节);`curl -sI <mirror-url>/` 应为 200 或带 `Www-Authenticate: Bearer` 的 401;401 且无 Bearer 头 = Nexus 未启用 Docker Bearer Token Realm 或未允许匿名拉取;`journalctl -u containerd` 看具体响应 |
672
+ | `crictl pull` 报 `dial tcp: lookup mirrors.gcoreinc.com: no such host` | `/etc/hosts` 缺 Nexus 条目(`getent hosts mirrors.gcoreinc.com`);重跑 `01` 脚本 |
673
+ | Pod 一直 ContainerCreating,kubelet 日志 `open /etc/resolv.conf: no such file` | 该节点 `/etc/resolv.conf` 被删或 systemd-resolved 又被启用;重跑 `01` 脚本 |
674
+ | CoreDNS CrashLoop,日志 `plugin/forward: no nameservers found` | ConfigMap 被 kubeadm upgrade 复原成默认;重新 `kubectl apply` 已渲染的 CoreDNS 清单 并 rollout restart |
675
+ | Nexus 返回 4xx 且 Usage Center 报超限 | CE 40,000 组件 / 100,000 请求/天 硬上限;等待回落或清理代理仓库缓存;镜像可在节点间 `ctr -n k8s.io images export/import` |
676
+ | kubelet 起不来,日志 `cgroup v1 ... failCgroupV1` | `stat -fc %T /sys/fs/cgroup` 必须是 `cgroup2fs`;Ubuntu 26.04 无 v1,Rocky 9 默认 v2,被改过就恢复内核参数 |
677
+ | kubeadm 报 sandbox image 不匹配 | `kubeadm config images list` 输出与 `config.toml` 的 `pinned_images.sandbox` 对齐(1.36.4 = pause:3.10.2) |
678
+ | Rocky 节点 Pod 卡 ContainerCreating,报 SELinux | 确认 `getenforce` = Permissive;或保持 Enforcing 并在 `config.toml` 设 `enable_selinux = true` 且安装 `container-selinux` |
679
+ | `kubeadm init` 卡在等待 control plane,VIP 未出现 | `crictl ps -a \| grep kube-vip` 与 `crictl logs`:镜像拉不到(ghcr 代理)/ 网卡名不对(`ip -4 addr` 对照 manifest 的 `vip_interface`)/ 静态 Pod 挂的是 `admin.conf` 而非 `super-admin.conf`(K8s ≥1.29 init 期间无权限) |
680
+ | cp2/cp3 上 kube-vip CrashLoop,日志 `admin.conf: no such file` 或 `Unauthorized` | 静态 Pod 放早了;join 完成后重跑 `bash scripts/kube-vip-manifest.sh /etc/kubernetes/admin.conf` |
681
+ | 关掉持有 VIP 的 CP 后集群长时间不可达 | 其它 CP 是否也有 kube-vip Pod;交换机是否拦截 ARP 漂移(在别的机器上 `arping -c3 192.168.222.100` 看 MAC 是否切换);Lease `plndr-cp-lock` 是否易主 |
682
+ | LB Service 一直 `<pending>` | `kubectl get svc <name> -o jsonpath='{.status.conditions}'`:`no_pool` = 池未创建或 selector 不匹配;池 `CONFLICTING=True` = 与其它池重叠 |
683
+ | `kubectl get gatewayclass` 为空 / Gateway `PROGRAMMED=False` | CRD 是否在 Cilium 之前 apply(`kubectl get crd gateways.gateway.networking.k8s.io`);补装后 `kubectl -n kube-system rollout restart deploy/cilium-operator ds/cilium`;`kubectl -n gateway-system describe gateway shared-gateway` 看 conditions |
684
+ | HTTPRoute `Accepted=False`,`NotAllowedByListeners` / `NoMatchingParent` | Gateway 监听器 `allowedRoutes.namespaces.from` 是否 `All`;`parentRefs.namespace` 写了 `gateway-system`;hostname 与监听器 hostname(本集群不限)不冲突 |
685
+ | Certificate 一直 `Ready=False` | `kubectl -n <ns> describe certificaterequest`;`ClusterIssuer internal-ca` 是否 Ready(根 CA Secret 存在);cert-manager webhook Pod 是否 Running |
686
+ | 浏览器报证书不受信 | 客户端未导入根 CA `/root/internal-root-ca.crt`;访问的 host 不在 `*.k8s.internal` / IP SAN 之内 |
687
+ | cert-manager 日志 `no matches for kind "Gateway"` | Gateway API CRD 未装就开了 `config.gatewayAPI.enabled`;先 apply CRD 再重启 cert-manager |
688
+ | LB IP 分配了但访问不通 | `kubectl -n kube-system exec ds/cilium -- cilium-dbg shell -- db/show l2-announce` 是否有该 IP;Service 是否用了 `externalTrafficPolicy: Local`(不支持,改 Cluster);交换机 ARP 策略 |
689
+ | GPU Operator toolkit Pod CrashLoop | `kubectl -n gpu-operator logs ds/nvidia-container-toolkit-daemonset`;宿主机 `containerd config dump` 能否执行;`/etc/containerd/conf.d` 是否存在且 `imports` 生效 |
690
+ | validator 报 driver validation failed(宿主机驱动) | 26.7.0 已处理 `/usr/bin` 软链接问题;`nvidia-smi` 是否在 `/usr/bin`;`nvidia-persistenced` 是否运行 |
691
+ | HAMi Pod 里看不到 GPU / `libvgpu.so` 报错 | 宿主机 `/usr/local/vgpu/libvgpu.so` 是否存在;Pod 是否带 `runtimeClassName: nvidia`(webhook 未生效时 ns 是否被 ignore);在 B300 上先加 `CUDA_DISABLE_CONTROL=true` 环境变量确认是否 hook 兼容问题 |
692
+ | HAMi kube-scheduler 镜像拉取失败 | 默认是阿里云镜像站,确认 values 已改为 `registry.k8s.io/kube-scheduler`,tag 与 `kubectl version` 一致 |
693
+ | Network Operator 的 rdma-shared-device-plugin 一直不出现 | 预期行为——DaemonSet 需要 `pci-15b3.present=true`,Broadcom/Intel 节点没有;用 `manifests/rdma/` 的独立部署 |
694
+ | 两台 GPU 机之间 NCCL 很慢 | X710 是 10GbE 且无 RDMA,跨机只能 TCP;训练任务限制在单机内 |
695
+
696
+ ## 15. 卸载 / 重置
697
+
698
+ **仅用于确认可丢弃数据的实验集群,禁止用于故障排查或生产回滚。** 以下会删除 etcd、集群配置与 GPU 运行时配置。先备份并验证恢复,控制平面逐台操作,至少保持两个健康 etcd 成员;不要在五台机器同时执行。删除 cert-manager 命名空间或 CA Secret 会丢失签发密钥。
699
+
700
+ ```bash
701
+ # 控制平面
702
+ helm -n hami-system uninstall hami
703
+ helm -n nvidia-network-operator uninstall network-operator
704
+ helm -n gpu-operator uninstall gpu-operator
705
+ kubectl delete -f manifests/gateway/shared-gateway.yaml --ignore-not-found
706
+ helm -n cert-manager uninstall cert-manager # crds.keep=true,CRD 与 Certificate 对象保留;彻底清理再 kubectl delete crd -l app.kubernetes.io/name=cert-manager
707
+ helm -n kube-system uninstall cilium
708
+ # 任一节点彻底重置
709
+ kubeadm reset -f
710
+ rm -rf /etc/cni/net.d /var/lib/cni /var/lib/etcd /etc/kubernetes $HOME/.kube \
711
+ /etc/containerd/conf.d/99-nvidia.toml /usr/local/vgpu
712
+ ip addr del 192.168.222.100/32 dev "$(ip -4 -o addr show | awk '$4 ~ /^192\.168\.222\./ {print $2; exit}')" 2>/dev/null || true # CP 节点若仍残留 VIP
713
+ systemctl restart containerd
714
+ ```
715
+
716
+ ## 16. 后续升级建议
717
+
718
+ - K8s 补丁(1.36.5 起):改 `versions.env` 的 `K8S_VERSION`,先备份,再升级首台 CP 的 kubeadm 并执行 `kubeadm upgrade plan/apply`;其余 CP 升级 kubeadm 后执行 `kubeadm upgrade node`。各节点需 cordon/drain(训练任务先停或保存 checkpoint),再更换 kubelet/kubectl、重启、验收并 uncordon。初装脚本会拒绝已入群节点,不能当作升级脚本重跑。升级后检查 CoreDNS ConfigMap 是否仍是自定义版本(`kubectl -n kube-system get cm coredns -o yaml | grep -c forward` 应为 0),否则重新渲染并 apply。
719
+ - 控制平面升级顺序:先升非 VIP 持有者,最后升持有者(仅停止 kubelet **不会停止现有 kube-vip 容器**;需要切换时按 6.2 的维护演练步骤单独停止 kube-vip,并从另一台 CP 确认 VIP 恢复)。`kubeadm upgrade` 不会动 `/etc/kubernetes/manifests/kube-vip.yaml`;升 kube-vip 只需改 `versions.env` 的 `KUBE_VIP_VERSION` 并在每台 CP 重跑 `scripts/kube-vip-manifest.sh`。
720
+ - Gateway API CRD 版本跟 Cilium 走(1.20.x = v1.6.1),升级 Cilium 大版本前先看其升级指南里的 Gateway API 段落,再 `kubectl apply --server-side` 新 CRD;cert-manager 独立升级(`helm upgrade`,CRD 由 chart 管),根 CA 不变则客户端无感。
721
+ - containerd 2.3.x 补丁:重跑 `00` 上传新包,先逐节点停任务并 drain,按运行时升级流程停止服务、校验并更换二进制后启动;不得直接重跑初装 `02`。
722
+ - GPU Operator / Network Operator / HAMi:`helm pull` 新 chart 上传后 `helm upgrade`,先在 H200 节点验证再动 B300。
723
+ - 如果换成 Helm 4:GPU Operator 官方明确支持后再切;Helm 4 新安装默认 Server-Side Apply,已存在的 release 沿用旧方式。
724
+
725
+ ---
726
+
727
+ 主要一手来源:[Kubernetes releases](https://kubernetes.io/releases/) · [kubeadm 1.36 constants](https://github.com/kubernetes/kubernetes/blob/release-1.36/cmd/kubeadm/app/constants/constants.go) · [containerd releases](https://containerd.io/releases/) · [containerd hosts.md](https://github.com/containerd/containerd/blob/release/2.3/docs/hosts.md) · [GPU Operator 26.7 Platform Support](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/26.7/platform-support.html) · [GPU Operator 26.7 Getting Started](https://docs.nvidia.com/datacenter/cloud-native/gpu-operator/26.7/getting-started.html) · [Network Operator 26.7 Platform Support](https://docs.nvidia.com/networking/display/kubernetes2670/platform-support.html) · [network-operator v26.7.0 源码](https://github.com/Mellanox/network-operator/tree/v26.7.0) · [HAMi v2.10.0](https://github.com/Project-HAMi/HAMi/releases/tag/v2.10.0) · [HAMi Troubleshooting](https://project-hami.io/docs/troubleshooting) · [Cilium kube-proxy free](https://docs.cilium.io/en/stable/network/kubernetes/kubeproxy-free/) · [Cilium LB IPAM](https://docs.cilium.io/en/stable/network/lb-ipam/) · [Cilium L2 Announcements](https://docs.cilium.io/en/stable/network/l2-announcements/) · [kube-vip Static Pods](https://kube-vip.io/docs/installation/static/) · [Cilium 1.20 Gateway API 安装要求](https://github.com/cilium/cilium/blob/v1.20.1/Documentation/network/servicemesh/gateway-api/installation.rst) · [Gateway API v1.6.1](https://github.com/kubernetes-sigs/gateway-api/releases/tag/v1.6.1) · [cert-manager Gateway 用法](https://cert-manager.io/docs/usage/gateway/) · [kubeadm HA 集群](https://kubernetes.io/docs/setup/production-environment/tools/kubeadm/high-availability/) · [Ubuntu 26.04 release notes](https://documentation.ubuntu.com/release-notes/26.04/) · [Intel 700 系列 RDMA 支持说明](https://www.intel.com/content/www/us/en/support/articles/000031905/ethernet-products/700-series-controllers-up-to-40gbe.html)
@@ -0,0 +1,5 @@
1
+ # tuneplane
2
+
3
+ Reserved initial client package for the TunePlane large-model post-training platform.
4
+
5
+ The first release intentionally contains only package metadata and a placeholder client namespace.
@@ -0,0 +1,21 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tuneplane"
7
+ version = "0.0.1"
8
+ description = "Client library for the TunePlane large-model post-training platform."
9
+ readme = "README_PYPI.md"
10
+ requires-python = ">=3.9"
11
+ license = {text = "MIT"}
12
+ authors = [{name = "TunePlane Contributors"}]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "License :: OSI Approved :: MIT License",
17
+ "Programming Language :: Python :: 3",
18
+ ]
19
+
20
+ [tool.setuptools]
21
+ packages = ["tuneplane"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,62 @@
1
+ """Local regression checks; requires PyYAML, never contacts a cluster."""
2
+ from pathlib import Path
3
+ import shutil
4
+ import subprocess
5
+ import tempfile
6
+ import unittest
7
+ import yaml
8
+
9
+ ROOT = Path(__file__).resolve().parents[1]
10
+
11
+ class ConfigTests(unittest.TestCase):
12
+ def setUp(self):
13
+ self.tmp = tempfile.TemporaryDirectory()
14
+ self.addCleanup(self.tmp.cleanup)
15
+ self.root = Path(self.tmp.name)
16
+ for name in ('scripts', 'configs', 'manifests'):
17
+ shutil.copytree(ROOT / name, self.root / name)
18
+ self.env = (ROOT / 'versions.env').read_text()
19
+ self.set_env(self.env.replace('NEXUS_IP=""', 'NEXUS_IP="192.168.222.50"'))
20
+
21
+ def set_env(self, text):
22
+ (self.root / 'versions.env').write_text(text)
23
+
24
+ def render(self, path, check=True):
25
+ return subprocess.run(['bash', str(self.root / 'scripts/render-config.sh'),
26
+ str(self.root / path)], text=True, capture_output=True, check=check)
27
+
28
+ def test_all_manifests_parse(self):
29
+ for base in ('configs', 'manifests'):
30
+ for path in (self.root / base).rglob('*.yaml'):
31
+ if path.name in ('kube-vip.yaml', 'secondary-network-b300.yaml'):
32
+ continue # Host NIC / kubeconfig need separate rendering.
33
+ with self.subTest(path=path):
34
+ list(yaml.safe_load_all(self.render(path).stdout))
35
+
36
+ def test_changed_settings_reach_consumers(self):
37
+ self.set_env(self.env.replace('NEXUS_IP=""', 'NEXUS_IP="172.22.10.50"')
38
+ .replace('192.168.222.', '172.22.10.')
39
+ .replace('10.244.0.0/16', '10.200.0.0/16').replace('v1.36.4', 'v1.36.5'))
40
+ init, cluster, kubelet = yaml.safe_load_all(self.render('configs/kubeadm/kubeadm-config.yaml').stdout)
41
+ self.assertEqual(init['localAPIEndpoint']['advertiseAddress'], '172.22.10.10')
42
+ self.assertEqual(cluster['kubernetesVersion'], 'v1.36.5')
43
+ self.assertEqual(cluster['controlPlaneEndpoint'], '172.22.10.100:6443')
44
+ cilium = yaml.safe_load(self.render('configs/cilium/values.yaml').stdout)
45
+ self.assertEqual(cilium['ipv4NativeRoutingCIDR'], cluster['networking']['podSubnet'])
46
+ self.assertEqual(cilium['k8sServiceHost'], '172.22.10.100')
47
+ pools = list(yaml.safe_load_all(self.render('manifests/cilium/lb-ipam-pool.yaml').stdout))
48
+ self.assertEqual(pools[0]['spec']['blocks'][0]['start'], '172.22.10.201')
49
+ self.assertEqual(pools[1]['spec']['blocks'][0]['start'], '172.22.10.200')
50
+ corefile = yaml.safe_load(self.render('manifests/coredns/coredns-configmap.yaml').stdout)['data']['Corefile']
51
+ self.assertIn('172.22.10.50 mirrors.gcoreinc.com', corefile)
52
+ self.assertIn('fallthrough', corefile.split('hosts {', 1)[1])
53
+
54
+ def test_missing_value_fails_without_partial_output(self):
55
+ self.set_env(self.env)
56
+ result = self.render('manifests/coredns/coredns-configmap.yaml', check=False)
57
+ self.assertNotEqual(result.returncode, 0)
58
+ self.assertEqual(result.stdout, '')
59
+ self.assertIn('NEXUS_IP', result.stderr)
60
+
61
+ if __name__ == '__main__':
62
+ unittest.main()
@@ -0,0 +1,4 @@
1
+ """TunePlane client package placeholder."""
2
+
3
+ __version__ = "0.0.1"
4
+
@@ -0,0 +1,18 @@
1
+ Metadata-Version: 2.4
2
+ Name: tuneplane
3
+ Version: 0.0.1
4
+ Summary: Client library for the TunePlane large-model post-training platform.
5
+ Author: TunePlane Contributors
6
+ License: MIT
7
+ Classifier: Development Status :: 3 - Alpha
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Requires-Python: >=3.9
12
+ Description-Content-Type: text/markdown
13
+
14
+ # tuneplane
15
+
16
+ Reserved initial client package for the TunePlane large-model post-training platform.
17
+
18
+ The first release intentionally contains only package metadata and a placeholder client namespace.
@@ -0,0 +1,9 @@
1
+ README.md
2
+ README_PYPI.md
3
+ pyproject.toml
4
+ tests/test-configs.py
5
+ tuneplane/__init__.py
6
+ tuneplane.egg-info/PKG-INFO
7
+ tuneplane.egg-info/SOURCES.txt
8
+ tuneplane.egg-info/dependency_links.txt
9
+ tuneplane.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ tuneplane