AI Infra 训练营
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
总览
  • 总览
  • 完整安装
  • 核心 K8s
  • Cilium 网络
  • Longhorn 存储
  • 监控日志
  • CI / GitOps
  • 安全准入
  • CI/CD 实战(MySQL+Go+Vue)
  • HPA/Ingress/Hubble 实战
  • 面试速查 + 真实踩坑
  • Day 0 · 新手接管 Runbook
  • Day 1 · 集群起步 + CNI
  • Day 2 · 控制面 + etcd
  • Day 3 · CRD + Operator + Webhook
  • Day 4 · 存储深度
  • Day 5 · 卷扩容 + 安全
  • Day 6 · 调度 + 可观测
  • Day 7 · Harbor + ArgoCD + Mesh
  • Day 8 · AI Infra
  • Day 9 · Triton + GPU
  • Day 10 · MIG + HPA + 量化
  • Day 11 · AI Agent 端到端
  • Day 12 · 灾备
  • Day 13 · Operator + 联邦 + Mesh + RAG
  • Day 14 · CKA / CKS + 总结
  • LLM 训练手册
  • RAG + Agent 手册
  • 推理优化手册
  • 上下文工程手册
  • Agent 开发手册
  • 面试深度复盘
  • 训练 v2 深度手册
  • 心智模型
  • 看懂命令输出
  • 容器网络底层
  • K8s 网络深入
  • DNS 全套
  • 故障排查方法论
  • 心智模型
  • 容器挂载完整指南
  • K8s Volumes 大全
  • PV/PVC/CSI 深入
  • NFS 深入
  • 分布式存储概览
  • 故障排查 runbook
命令手册
HiHuo 主站
GitHub
  • Day 0 · 环境与硬件

    • Day 0 新手现场接管 Runbook:先看懂,再动手
    • Day 0:5 节点裸 Ubuntu → K8s 装机基线
  • Week 1:K8s 内核 + 周边基础设施

    • Day 1:3 CP HA 集群 + CNI 选型 + DNS 调优
    • Day 2:控制面 deep dive + etcd 内核 + chaos drill
    • Day 3:CRD + Operator (kubebuilder 从 0 写)
    • Day 4:Longhorn 存储 + Cilium 二探(Hubble / NetworkPolicy / L7)
    • Day 5:PVC 在线扩容 + K8s 安全基线(RBAC / PSA / Secret 加密 / Kyverno)
    • Day 6:调度策略 + Prometheus / Loki 观测栈
    • Day 7:Harbor 私有镜像 + ArgoCD GitOps + Cilium WireGuard
  • Week 2:制品 + GitOps + AI Infra + 综合

    • Day 8:k3s 单节点 + NVIDIA Device Plugin + vLLM 跑 Qwen2.5-3B
    • Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)
    • Day 8:AlertManager 真接入 + PrometheusRule 实战
    • Day 8:集群内 CI 闭环 — Gitea + Jenkins + Kaniko
    • Day 9:Triton 多框架推理 + DCGM 跨集群可观测 + vLLM 实测
    • Day 10:MIG 硬切片 + AWQ 量化 + HPA Custom Metrics
    • Day 11:AI 业务端到端 —— chainlit + GitOps + 跨 WAN vLLM
    • Day 12:灾难恢复 + 生产事故注入
    • Day 13:LLM Operator + 多集群联邦 + Ambient Mesh + RAG
    • Day 14:CKA / CKS 真题演练 + 14 天技术栈横向汇总

Day 8(attempt 1):跨 WAN GPU 加入主集群(失败复盘)

主集群在某 IDC 内网 10.0.24.0/24,5 节点都拿到公网 IP;GPU 节点是另一家供应商的 A800-SXM4-40GB 单机,只能通过公网访问。本来想顺着 Day 1 的 per-node HAProxy 姿势,让这台 GPU 节点 kubeadm join 进主集群,scheduler 直接看到 nvidia.com/gpu: 1,省一套独立集群的维护成本。

实际走了 1.5 小时,链路打通到 kubeadm join 成功,最后卡在 Cilium agent 跨 WAN CrashLoopBackOff,并且 k3s install 阶段 SSH 突然断了。综合评估调通 Cilium VXLAN over WAN 的代价(每条路径都是 1-3 小时调试且不一定通),决定放弃这条路线,改走 Day 8-ai-infra.md 里的 k3s 单节点 + 独立运行 方案。

这一篇记录走到一半就放弃的尝试,重点不是「怎么修」,而是「为什么不修」—— 跨 WAN K8s 节点集成什么时候应该硬上,什么时候应该早点止损。


起点条件 / 网络拓扑

┌──────────────────────────────┐        公网          ┌──────────────────────────┐
│ 主集群(IDC A)              │   ←──────────────→   │ GPU 节点(IDC B, NAT 后)│
│   k8s-cp-1  10.0.24.31       │                      │   公网: ***.109.239.32   │
│   k8s-cp-2  10.0.24.29       │                      │   内网: 192.168.122.6    │
│   k8s-cp-3  10.0.24.30       │                      │   GPU: A800-SXM4-40GB    │
│   k8s-w-1   10.0.24.28       │                      │        40GB HBM, Ampere  │
│   k8s-w-2   10.0.24.32       │                      │                          │
│   5 节点都有公网 IP          │                      │                          │
│   Pod CIDR 10.244.0.0/16     │                      │                          │
│   Cilium 1.16 VXLAN          │                      │                          │
└──────────────────────────────┘                      └──────────────────────────┘

关键不对称:

  • 主集群 5 节点在同子网,Cilium VXLAN tunnel 走内网 8472/udp,已稳跑 7 天
  • GPU 节点跟主集群没有 L2 邻居关系,只能 公网 IP ↔ 公网 IP 通信
  • GPU 节点本机 ip addr 看不到自己的公网 IP(在 supplier NAT 后),ens3 只有 192.168.122.6

主集群单 worker 算力够用,加 GPU 节点纯粹是想让 vLLM/Triton 这类 GPU Pod 跑在 K8s 调度框架内(声明 nvidia.com/gpu: 1 即可),而不是裸 docker。


网络可达性验证

kubeadm join 之前先把所有端口走一遍。结果都通:

GPU → cp-1:6443/tcp     OK   (apiserver)
GPU → cp-3:6443/tcp     OK
GPU → cp-1:8472/udp     OK   (Cilium VXLAN data plane)
GPU → Harbor 30002/tcp  OK   (镜像 mirror, 10.0.24.28:30002)
cp-1 → GPU:15128/tcp    OK   (SSH 反向, 用于 kubectl exec/logs 回连)

UDP 测通用的是 nc -uvz + 对端 tcpdump,不能只看 nc 返回码 —— UDP 没有握手,本地 nc 永远 success,必须对端抓到包才算数。

5 个端口 + 2 个方向都通 = 链路层 OK,但这只是 「单数据流跑通」,跨 WAN 真正的难点在 NAT 表的稳定性,后面会复盘。


系统准备

GPU 节点按主集群同款基线装一遍,跟 Day 0 / Day 1 完全对齐:

# 内核基线(同 Day 1 A.2)
swapoff -a; sed -i '/[[:space:]]swap[[:space:]]/s/^/# /' /etc/fstab
modprobe overlay && modprobe br_netfilter
# sysctl br-nf + ip_forward ...

# 运行时(跟主集群版本对齐,避免 sandbox image 跨版本拉不到)
containerd 2.0.0          # 跟主集群一致
runc 1.2.1
CNI plugins 1.6

# K8s 工具
kubeadm/kubelet/kubectl 1.30.14   # 跟主集群完全一致(hold 住)

# 镜像 mirror(公网拉不到 registry.k8s.io)
/etc/containerd/certs.d/
  docker.io/hosts.toml      → docker.m.daocloud.io
  quay.io/hosts.toml        → quay.m.daocloud.io
  10.0.24.28:30002/hosts.toml → 主集群内 Harbor(走公网)

per-node HAProxy 同款 Day 1 模式:

listen 127.0.0.1:16443 → cp-1/cp-2/cp-3 公网 6443 (各家公网 IP)
/etc/hosts:  127.0.0.1 k8s-api

验证:

curl -k https://k8s-api:16443/version
# {"major":"1","minor":"30","gitVersion":"v1.30.14",...}

到这里前置工作没坑,跟集群内 worker join 体感几乎一样。


kubeadm join 成功,但 InternalIP 不对

kubeadm join k8s-api:16443 \
  --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --cri-socket unix:///run/containerd/containerd.sock
# This node has joined the cluster

kubectl get nodes -o wide 出来 GPU 节点 InternalIP 显示的是 192.168.122.6(GPU 本机内网 IP,主集群其他节点完全不可达)。

主集群其他节点想跟 GPU 节点的 kubelet 通信(拉日志、exec 进 Pod),都会走这个 InternalIP 失败。

kubelet --node-ip 的两个隐藏要求

第一个坑:KUBELET_EXTRA_ARGS 的位置。systemd drop-in 习惯写到 /etc/systemd/system/kubelet.service.d/,但 kubeadm 装的 kubelet 用的是 /etc/default/kubelet(EnvironmentFile 路径)。写错位置不会报错,只是不生效。

第二个坑(真正卡住的):即便 --node-ip=***.109.239.32 写对位置,kubelet 校验该 IP 必须在本机网卡上。GPU 节点 ens3 只有 192.168.122.6,公网 IP 在 supplier 的 NAT 网关上,kubelet 找不到对应网卡,fallback 回 192.168.122.6。

绕开方式:给 ens3 加 secondary IP,让 kubelet 在本机能看到这个公网 IP:

ip addr add ***.109.239.32/32 dev ens3
systemctl restart kubelet

这是个 trick —— supplier 那边的 NAT 仍然在工作,GPU 节点对外其实是 ***.109.239.32,但本机 routing table 也声明了一个同名地址,骗过 kubelet 的本地校验。重启 kubelet 后 InternalIP 切到公网 IP,kubectl get nodes 看上去正常了。

到这一步是这次尝试的 局部最高点:节点 Joined、kubelet 健康、InternalIP 正确。接下来 Cilium 一上就崩。


卡在哪:Cilium agent 跨 WAN CrashLoopBackOff

GPU 节点 join 后,主集群 Cilium DaemonSet 会自动调度一个 cilium-agent Pod 过去。13+ 次 restart 后状态稳定为 CrashLoopBackOff:

back-off 1m20s restarting failed container=cilium-agent

CNI 层的报错链(来自 kubelet journal):

plugin type="cilium-cni" failed (add):
  unable to connect to Cilium agent:
  failed to create cilium agent client after 30.000000 seconds timeout:
  Get "http://localhost/v1/config":
    dial unix /var/run/cilium/cilium.sock: connect: no such file or directory

cilium-agent 自己的 log 拉不到具体 fatal —— 启动过程中就退出了,stderr 截断。从配置侧推测有三条可能的根因,都不容易单独证实:

  1. 跨 WAN VXLAN/WireGuard 握手失败:Cilium 默认假设节点同网段,VXLAN UDP 8472 单向通了不代表握手层一定能完成;WireGuard 4244/51871 在 supplier NAT 后行为不确定
  2. NodeIP 跟实际 NAT 地址不一致:cilium-agent 启动时会探测自己的 NodeIP,secondary IP hack 骗得过 kubelet,不一定骗得过 cilium-agent 的探测逻辑
  3. WireGuard mesh 加入失败:主集群 5 节点的 Cilium WireGuard mesh 已经建好,第 6 个 peer 跨 WAN 加入时 handshake 走公网,NAT 设备的 UDP 表项过期/丢包都会让 handshake 超时

VXLAN/WireGuard 单向 UDP 通了 ≠ 双向 handshake 稳。一个数据流测试包过去,回程包能不能在 NAT 表项过期前回来,是另外的问题。


决策点:为什么没继续修

把「调通跨 WAN Cilium」拆成可能的路径:

方向估时通过率(主观)
Cilium 显式配置 tunnel-endpoint 用公网 IP + 关 host firewall1-2h50%
单独建 WireGuard tunnel,绕开 Cilium 自己的 WG mesh2-3h60%(但脆弱)
改用 Cilium ClusterMesh 联邦(GPU 节点单独成集群)3-5h90%,但等同于放弃 join 方案

每条路径都有 supplier NAT 行为这个不可控变量 —— 不是 K8s 层面的问题,是底层网络给不给力。

触发放弃的两个直接事件:

  1. kubeadm reset 之后正要装 k3s 重启思路,curl 灌 install.sh 跑到一半 SSH 断了。k3s install 会大量改 iptables(KUBE-* 链),改的过程中 supplier 的 NAT/防火墙映射被打断,SSH inbound 直接挂。移动 + 电信都 Connection closed,等了 30 分钟才恢复
  2. SSH 断的同时意识到一件事:跨 WAN 集群里,任何一次 iptables 改动都可能让节点失联。哪怕 Cilium 调通了,后续日常运维(升级、加 NetworkPolicy、Cilium agent 自身重启)都是潜在的 SSH 断点

这两件事让 trade-off 翻转:调通的代价是 1-3 小时,但维护一个跨 WAN K8s 节点的长期成本 远大于「独立 k3s + 集群外调用」。

切方案路径:

  1. 主集群 kubectl drain + kubectl delete node GPU 节点
  2. GPU 节点 kubeadm reset -f,清 /etc/kubernetes /etc/cni/net.d /var/lib/etcd
  3. 装 k3s 单节点(INSTALL_K3S_MIRROR=cn),把 GPU Pod 跑在独立集群里
  4. 后续 ArgoCD / observability 通过 集群外 API 调用 接入主集群

完整的 k3s 单节点 + NVIDIA Device Plugin + vLLM 落地见 Day 8 - AI Infra。


教训:跨 WAN K8s 节点什么时候要、什么时候别要

应该硬上的场景:

  • 多 region 高可用是业务硬要求(金融、跨国 SaaS),架构上必须接受跨 WAN K8s 的复杂度
  • 节点数 ≥ 3 才考虑做 ClusterMesh / 联邦,单节点不值得
  • 底层有专线 / VPN tunnel 兜底,不依赖 supplier NAT

不应该硬上的场景(当前这种):

  • 单个 GPU 节点跨 WAN —— 收益就是「scheduler 看得到」,但失去 K8s 假设的「节点同网段 + 低延迟 + 高带宽」前提
  • supplier NAT 行为不可控 —— 任何 iptables 改动都可能误伤 SSH
  • 没有第二通道(VNC / 串口 / 带外管理)—— SSH 是唯一入口的情况下,不要在跨 WAN 节点上跑 curl install.sh | sh 这种「改 iptables 的网络脚本」

K8s 的核心假设是「同子网 + 低延迟 + 高带宽」。一旦把单节点跨 WAN 接进来,Cilium VXLAN / WireGuard / kube-proxy / kubelet 的所有「默认配置」都不再适用,要么走 ClusterMesh 这种为跨地域设计的姿势,要么就别 join,让 GPU 节点保持独立 cluster + 集群外服务调用。

这一轮真正学到的东西

  • kubelet --node-ip 跨 WAN:必须配 secondary IP 在本机网卡上,kubelet 才接受
  • kubeadm join 跨 WAN:本地 HAProxy 转发到 cluster 公网 IP,通过 /etc/hosts 给 k8s-api 别名,姿势上跟集群内 join 完全一致
  • Cilium 跨 WAN 不是配置问题,是设计问题:helm values 改不出来,需要专门的 ClusterMesh 或前置 WireGuard tunnel
  • 改 iptables 的脚本不要在唯一 SSH 通道上裸跑:tmux、screen、带外管理任选其一,否则一次断连 30 分钟起步
  • 「单数据流测通」≠「链路稳定」:UDP 端口走 nc 测通不代表跨 WAN VXLAN/WireGuard handshake 一定稳,NAT 表项过期和丢包是隐藏变量

简历可写一句:在跨地域多 WAN 环境集成单 GPU 节点至主 K8s 集群,通过 per-node HAProxy + 公网 IP secondary 注册 + 双向 NAT 端口映射,将节点 kubeadm join 成功;评估 Cilium VXLAN over WAN 的工程代价后,trade-off 选择独立 k3s 集群 + 集群外 API 调用方案,避免长期运维风险。

在 GitHub 上编辑此页
Prev
Day 8:k3s 单节点 + NVIDIA Device Plugin + vLLM 跑 Qwen2.5-3B
Next
Day 8:AlertManager 真接入 + PrometheusRule 实战