Skip to content

04 集群网络与 VM 直通网络 10.181.91.0/24

本章目标:把每台机器的 eno3/eno4 变成 Harvester 的「VM 专用网络资源池」,并在其上开出 VLAN 91 的 VM 网络 net-91,使 VM 能直接在 10.181.91.0/24 里获得地址、被外部访问。这是整本书技术含量最高、也最容易出问题的一章。


4.0 本章地图:对象、宿主机接口与验证对照

先看清全貌再动手。本章建 3 个 K8s 对象,每个对象在宿主机上对应一个真实网络设备,每个设备都有唯一一条验证命令:

text
K8s 对象层                     宿主机设备层              验证命令(期望结果)
─────────────────────────────────────────────────────────────────────────
① ClusterNetwork cn-vm    →   (只是逻辑资源池)      kubectl get clusternetwork(Ready=True,建好 VlanConfig 后)
        │ 被引用

② VlanConfig vlan-cfg-vm  →   bond 口 cn-vm-bo       cat /proc/net/bonding/cn-vm-bo(Partner MAC 非全 0)
   (eno3+eno4, 802.3ad)     + bridge cn-vm-br       bridge vlan show dev cn-vm-bo(含 91)
        │ 覆盖 12 节点          + 每节点一条 VlanStatus   kubectl get vlanstatus(12 条全 Ready)

③ NAD default/net-91      →   bridge 放行 VLAN 91     kubectl get nad(Ready=True)
   (vlan: 91)               VM 启动后挂 tap 设备      bridge fdb show br cn-vm-br(学到 VM MAC)

可选对象(用到再建):HostNetworkConfig(宿主机自己也进 VLAN 91,生成 cn-vm-br.91 子接口,4.7);IPPool + Managed DHCP addon(让 Harvester 给 VM 发地址,4.8)。

顺序不可逆:②依赖①,③依赖②,VM 依赖③。任何一步的验证没过就停下,不要带着疑问往下走——对象创建成功 ≠ 宿主机网络真的配好了。


4.1 做完之后应该长什么样(目标状态)

text
kubectl get clusternetwork
  NAME    READY
  mgmt    true
  cn-vm   true                      ← 新建

kubectl get vlanconfig
  NAME           CLUSTERNETWORK   READY
  vlan-cfg-vm    cn-vm            true

kubectl get vlanstatus -o wide
  12 条,每条对应一个节点,READY=True

kubectl get nad -A
  NAMESPACE   NAME      TYPE             CLUSTER NETWORK   VLAN   READY   MESSAGE
  default     net-91    L2VlanNetwork    cn-vm             91     True

# 任一节点上
ip -br link | grep cn-vm
  cn-vm-bo    UP   <聚合口>
  cn-vm-br    UP   <bridge>
bridge vlan show dev cn-vm-bo
  cn-vm-bo   1 PVID Egress Untagged
             91                             ← VLAN 91 已放行

# UI: Networks → VM Networks → net-91 → Network connectivity: Active(12/12 节点)
# UI: Virtual Machines → Create → Network 下拉框可选 Management Network 与 net-91

达成上面这些,第 5 章的 VM 创建才有意义。任何一项不满足都不要往下走,先按第 8 章排障。


4.2 前置条件(不满足就别开始)

#前置条件检查命令
112 节点全部 Readykubectl get nodes
2eno3/eno4 未被 Harvester 占用(不在 mgmt bond 里)cat /proc/net/bonding/mgmt-bo | grep 'Slave Interface'
3eno3/eno4 物理链路 upethtool eno3 | grep -i 'link detected'
4交换机对应端口为 trunk 且放行 VLAN 91交换机侧 show interfaces trunk
5若用 802.3ad,交换机已配 LACP 聚合组交换机侧 show etherchannel summary
6VLAN 91 的 SVI/网关 10.181.91.1 已存在从同 VLAN 机器 ping 10.181.91.1
7eno3/eno4 上没有残留 IP/桥接配置ip -br addr show eno3 应为空
8计划内的 VLAN ID(91)未被其他 NAD 占用kubectl get nad -A -o custom-columns=NS:.metadata.namespace,NAME:.metadata.name,VLAN:.metadata.labels.network\\.harvesterhci\\.io/vlan-id

⚠️ 关于 mgmt 上建 VLAN 网络的历史坑:在 v1.5.x 及更早版本,若把整个 VLAN 范围(2–4094)分配给 mgmt 接口,遇到网卡硬件 VLAN offloading 的设备会出现异常(官方 issue #7650)。本环境用独立 ClusterNetwork 承载 VM 流量,天然规避此问题;这也是官方最佳实践的推荐做法。

4.2.1 bond 模式与交换机聚合模式的对应关系(官方表)

Bond 模式交换机侧需要的链路聚合模式
balance-rr(0)manual(静态聚合)
active-backup(1)none(无需聚合配置)
balance-xor(2)manual
broadcast(3)manual
802.3ad(4)LACP
balance-tlb(5)none
balance-alb(6)none

本环境 cn-vm 采用 802.3ad(对应交换机 LACP);若交换机侧还没配好,先用 active-backup 打通,后续按 4.9 流程变更。


4.3 步骤一:创建 ClusterNetwork cn-vm

4.3.1 UI 方式

  1. Networks → Cluster Networks → Create
  2. 名称填 cn-vm(短、小写、只用字母数字和 -,它会成为 Linux 接口名的一部分)。
  3. 提交。此时 cn-vmReady 会是 False,因为还没有任何 VlanConfig 把网卡绑上来——这是正常的,不是错误

4.3.2 kubectl 方式

yaml
# appendix/network/01-clusternetwork-cn-vm.yaml
apiVersion: network.harvesterhci.io/v1beta1
kind: ClusterNetwork
metadata:
  name: cn-vm
bash
kubectl apply -f appendix/network/01-clusternetwork-cn-vm.yaml
kubectl get clusternetwork cn-vm -o yaml

ClusterNetwork 对象本身几乎没有 spec 字段,它只是一个「命名空间式的资源池标识」。真正决定用哪些网卡的是下一步的 VlanConfig


4.4 步骤二:创建 VlanConfig(把 12 节点的 eno3/eno4 接进 cn-vm)

UI 上这一步叫 Network Config(Networks → Network Configs → Create),底层对象是 VlanConfig

4.4.1 完整 YAML(生产推荐:按节点标签选)

yaml
# appendix/network/02-vlanconfig-vlan-cfg-vm.yaml
apiVersion: network.harvesterhci.io/v1beta1
kind: VlanConfig
metadata:
  name: vlan-cfg-vm
spec:
  clusterNetwork: cn-vm
  nodeSelector:                     # 注意:这是扁平的 label map,不是 matchLabels
    harvesterhci.io/managed: "true" # 选中所有 Harvester 节点(12 台)
  uplink:
    nics:
      - eno3
      - eno4
    bondOptions:
      mode: 802.3ad                 # 交换机必须配 LACP;未配好先用 active-backup
      miimon: 100
    linkAttributes:
      mtu: 1500
      txQLen: -1
bash
kubectl apply -f appendix/network/02-vlanconfig-vlan-cfg-vm.yaml

4.4.2 nodeSelector 的三种写法

写法适用示例
全部节点12 台都要跑 VM 网络(本书默认)harvesterhci.io/managed: "true"
指定若干主机名只有部分机器接了业务交换机见下
自定义标签按机架/区域/角色分组topology.kubernetes.io/zone: zone-a

指定多个主机名(nodeSelector 是 map,同一 key 只能有一个值,因此多主机名要用多个 VlanConfig 或用标签):

yaml
# 单节点写法
spec:
  clusterNetwork: cn-vm
  nodeSelector:
    kubernetes.io/hostname: harvester-04
  uplink:
    nics: [eno3, eno4]
    bondOptions: {mode: active-backup, miimon: 100}
    linkAttributes: {mtu: 1500, txQLen: -1}

⚠️ 一个节点同一时刻只能属于一个 ClusterNetwork 下的一个 VlanConfig(节点上会打 network.harvesterhci.io/vlanconfig 标签记录归属)。想把不同机架的机器分到不同 ClusterNetwork,就给不同节点打不同标签,再写多个 VlanConfig。

⚠️ 同一 ClusterNetwork 下所有 VlanConfig 的 MTU 必须一致,否则 webhook 直接拒绝创建/修改。这是最常见的「配置明明没错却 apply 失败」的原因之一。

  1. nics 里写的是宿主机上的物理网卡名(不是 MAC)。因此 12 台机器的网口命名必须一致,否则要按节点分组写多个 VlanConfig。装机前用 ip -br link 核对(2.4.3)。
  2. 写两张卡就会自动组成 bond(名为 <cn>-bo);只写一张卡则不聚合(依然会生成 bridge)。
  3. 这些网卡不能已经在 mgmt bond 里,也不能有 IP 配置。Harvester 会接管它们。

4.5 步骤三:验证宿主机是否真的落地

这一步是本章的灵魂。K8s 对象创建成功 ≠ 节点上的网络真的配好了。

4.5.1 三个 K8s 层面的检查

bash
# ① VlanConfig 是否 Ready
kubectl get vlanconfig vlan-cfg-vm -o wide

# ② 每个节点是否被打上归属标签
kubectl get nodes -L network.harvesterhci.io/vlanconfig

# ③ 每个节点的 VlanStatus(12 条,ready=True)
kubectl get vlanstatus -o custom-columns=\
'NAME:.metadata.name,NODE:.status.node,CN:.status.clusterNetwork,VC:.status.vlanConfig,READY:.status.conditions[0].status,LINKMON:.status.linkMonitor'

# 看某一台的完整状态
kubectl get vlanstatus <vlanstatus-name> -o yaml

VlanStatus 的关键字段(真实样例):

yaml
apiVersion: network.harvesterhci.io/v1beta1
kind: VlanStatus
metadata:
  name: harvester-04-vlan-cfg-vm
status:
  clusterNetwork: cn-vm
  conditions:
    - lastUpdateTime: "2026-09-08T02:11:41Z"
      status: "True"
      type: ready
  linkMonitor: cn-vm-bo          # 被监控的上联口
  localAreas:                    # 由 NAD 的 route 配置推导出来的可达网段
    - cidr: 10.181.91.0/24
      vlanID: 91
  node: harvester-04
  vlanConfig: vlan-cfg-vm

4.5.2 五个宿主机层面的检查(登到节点上执行)

bash
# ① 接口存在且 UP
ip -br link show cn-vm-bo; ip -br link show cn-vm-br
#   期望: cn-vm-bo UP ...   cn-vm-br UP ...

# ② bond 成员与状态(802.3ad 时看 LACP 是否协商成功)
cat /proc/net/bonding/cn-vm-bo

期望输出的关键行(802.3ad 示例,逐行对照):

text
Bonding Mode: IEEE 802.3ad Dynamic link aggregation   ← 与 VlanConfig 的 mode 一致
Transmit Hash Policy: layer3+4
MII Status: up                                        ← bond 整体存活
...
Slave Interface: eno3
MII Status: up                                        ← 成员 1 存活
Link Failure Count: 0
Aggregator ID: 1                                      ← 两个成员必须是同一个 ID
Partner Mac Address: 4c:d9:8f:xx:xx:xx                ← 非全 0 = LACP 协商成功
...
Slave Interface: eno4
MII Status: up                                        ← 成员 2 存活
Aggregator ID: 1                                      ← 与 eno3 相同
Partner Mac Address: 4c:d9:8f:xx:xx:xx                ← 同一个对端

三项硬判据:Partner Mac Address 非全 0(全 0 = LACP 没协商上,见 8.4 案例 1)、两个成员的 Aggregator ID 相同所有 MII Status 为 up。任何一条不满足,后面的验证都不用做,先回 8.2.2。

bash
# ③ bridge 上联口放行了 VLAN 91
bridge vlan show dev cn-vm-bo

期望输出(1 PVID Egress Untagged 是默认 VLAN,91 这一行才是关键):

text
cn-vm-bo        1 PVID Egress Untagged
                91                                    ← net-91 创建后出现;没有它,带 91 标签的包过不了 bridge
bash
# ④ 物理网卡确实被 bond 接管(不再有独立 IP)
ip -br addr show eno3; ip -br addr show eno4      # 期望无 IPv4

# ⑤ MTU 一致
ip -br link show cn-vm-bo | awk '{print $NF}'     # 或 cat /sys/class/net/cn-vm-bo/mtu

4.5.3 打通验证(最关键的一步)

还没有任何 VM 的情况下,就可以验证 VLAN 91 是否真的通到交换机:临时在宿主机上建一个 VLAN 子接口测试(测完删掉)。

bash
# 临时测试:在 cn-vm-bo 上建 VLAN 91 子接口并配一个测试 IP
sudo ip link add link cn-vm-bo name cn-vm-bo.91 type vlan id 91
sudo ip addr add 10.181.91.250/24 dev cn-vm-bo.91
sudo ip link set cn-vm-bo.91 up

ping -c3 10.181.91.1                 # 期望通网关
arping -c2 -I cn-vm-bo.91 10.181.91.1

# 测完清理
sudo ip link del cn-vm-bo.91
  • ping 通网关 = 物理链路 + VLAN 标签 + 交换机 trunk 全部正确,后面的问题只可能在 VM 内部配置。
  • ping 不通 = 问题一定在宿主机以下(bond/LACP/trunk/VLAN/SVI),跟 Harvester 无关,先找网络组。这一步能省掉几小时的错误方向排查。

⚠️ 如果你打算让宿主机长期持有 10.181.91.x 地址(比如给 Managed DHCP 当 serverIP,或做 Kube-OVN underlay),不要手工建子接口,请用 4.7 的 HostNetworkConfig,它会自动创建 cn-vm-br.91 并在重启后自动恢复。


4.6 步骤四:创建 VM Network net-91(NAD)

4.6.1 UI 方式(推荐,最不容易写错)

  1. Networks → VM Networks → Create
  2. 填写:
字段本环境取值说明
Namenet-91与 VLAN 号对应,见名知意
NamespacedefaultVM 默认命名空间;多租户可分开
TypeVLAN Network另有 Untagged / VLAN Trunk / Overlay(Kube-OVN, 实验)
VLAN ID91范围 2–4094;必须与交换机放行的 VLAN 一致
Cluster Networkcn-vm只有已被 VlanConfig 覆盖的 ClusterNetwork 才可选/才有效
Route 标签页见下决定 Harvester 如何验证「每个节点都能到达这个网络」
  1. Route 标签页的两种模式(这一步很多人跳过,结果 UI 上 Network connectivity 一直报错):
模式行为适用
Auto (DHCP)Harvester 网络控制器从 DHCP 服务器获取 CIDR 与网关;可指定 DHCP server 地址VLAN 91 上已有 DHCP 服务器
Manual手工指定 CIDR 与 gateway本环境采用:CIDR 10.181.91.0/24,Gateway 10.181.91.1

Harvester 用这些信息在每个节点上做可达性验证:全部节点都能访问该网段,UI 的 Network connectivity 列显示 Active;否则显示错误。对应的底层数据就是 VlanStatus.status.localAreas

  1. 提交后检查 Networks → VM Networks 列表:net-91 的 Ready = True、Network connectivity = Active(12/12)。

4.6.2 等价的 YAML(自动化/IaC 用)

建议先用 UI 建一个,再 kubectl get nad net-91 -o yaml 导出,作为你环境里的「标准形态」;下面的 YAML 与 v1.8 实际对象结构一致,可直接 apply

yaml
# appendix/network/03-nad-net-91.yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: net-91
  namespace: default
  annotations:
    # route 信息:Manual 模式,写清 CIDR 与网关
    network.harvesterhci.io/route: '{"mode":"manual","serverIPAddr":"","cidr":"10.181.91.0/24","gateway":"10.181.91.1"}'
  labels:
    network.harvesterhci.io/clusternetwork: cn-vm
    network.harvesterhci.io/type: L2VlanNetwork
    network.harvesterhci.io/vlan-id: "91"
spec:
  # config 是一段 JSON 字符串:bridge CNI 配置
  # bridge 必须是 <clusterNetwork>-br;ipam 为空表示 CNI 不分配 IP
  config: '{"cniVersion":"0.3.1","name":"net-91","type":"bridge","bridge":"cn-vm-br","promiscMode":true,"vlan":91,"ipam":{},"mtu":1500}'

如果 VLAN 91 上有 DHCP 服务器,改用 Auto 模式:

yaml
metadata:
  annotations:
    network.harvesterhci.io/route: '{"mode":"auto","serverIPAddr":"10.181.91.2","cidr":"","gateway":""}'

创建后由控制器补齐状态类标签(例如 network.harvesterhci.io/ready: "true"):

bash
kubectl apply -f appendix/network/03-nad-net-91.yaml
kubectl get nad -n default net-91 -o yaml | grep -A6 'labels:'
kubectl get nad -n default net-91 -o jsonpath='{.spec.config}{"\n"}' | python3 -m json.tool

4.6.3 三种 VM 网络类型怎么选

类型何时用注意
VLAN Network(本方案)单网段直通,最常见vlan 写在 CNI config 里
Untagged Network交换机端口是 access/native,不希望收到显式 VLAN 标签CNI config 里无 vlan 字段
VLAN Trunk Network让 VM 内部自己处理多个 VLAN(Guest 里做 VLAN 子接口)指定 min/max VLAN ID 范围,可多段且可重叠
Overlay Network(Kube-OVN,实验)需要 VPC/子网等 SDN 能力外部无法直接访问这些 VM;与 Managed DHCP 不兼容

⚠️ 官方明确:只有在所有挂载该网络的 VM 都关机后,才能修改网络类型;而且从 VLAN 改成 VLAN Trunk/Untagged 时,Route 标签页的配置会被清除,改回来必须重新配置。所以类型一次选对。

4.6.4 另外两种类型的配置示例(备查)

Untagged Network:交换机端口是 access(或 native VLAN)时用。NAD 的 config不写 vlan 字段,标签只有 clusternetworktype

yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: net-untagged
  namespace: default
  annotations:
    network.harvesterhci.io/route: '{"mode":"manual","serverIPAddr":"","cidr":"10.181.95.0/24","gateway":"10.181.95.1"}'
  labels:
    network.harvesterhci.io/clusternetwork: cn-vm
    network.harvesterhci.io/type: UntaggedNetwork
spec:
  config: '{"cniVersion":"0.3.1","name":"net-untagged","type":"bridge","bridge":"cn-vm-br","promiscMode":true,"ipam":{},"mtu":1500}'

VLAN Trunk Network:VM 内部要自己处理多个 VLAN(比如在 Guest 里建 eth1.100eth1.200 子接口)时用。允许一段 VLAN 范围通过,不在 NAD 里固定单个 VLAN:

yaml
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: net-trunk
  namespace: default
  labels:
    network.harvesterhci.io/clusternetwork: cn-vm
    network.harvesterhci.io/type: L2VlanTrunkNetwork
  annotations:
    # trunk 模式在 annotation 里声明放行的 VLAN 范围(字段以集群实际对象为准:kubectl get nad <已有trunk网络> -o yaml 反查)
    network.harvesterhci.io/vlan-trunk: '[{"id":100,"min":0,"max":0},{"min":110,"max":120}]'
spec:
  config: '{"cniVersion":"0.3.1","name":"net-trunk","type":"bridge","bridge":"cn-vm-br","promiscMode":true,"ipam":{},"mtu":1500}'

VM 内使用示例(在 Guest 里自建 VLAN 子接口,此时地址获取完全由 Guest 自己负责):

bash
sudo ip link add link eth1 name eth1.100 type vlan id 100
sudo ip addr add 10.181.100.10/24 dev eth1.100 && sudo ip link set eth1.100 up

⚠️ Trunk 网络的 VLAN 范围必须也被交换机 trunk 放行,且 VM 的地址管理(DHCP/静态/路由)全部下移给 Guest,运维复杂度明显上升——除非确有「一台 VM 跨多网段」的需求,否则优先用多个普通 VLAN 网络。

4.6.5 扩展:同一块网卡上再加一个网段(多 VLAN 并存)

VLAN 方案的最大红利:加一个网段不用动一根线。比如新增 10.181.92.0/24(VLAN 92):

  1. 交换机:创建 VLAN 92 + SVI 10.181.92.1,把 92 加进 12 台机器 trunk 端口的 allowed vlan(与 91 并列)。
  2. Harvester:不用动 ClusterNetwork 和 VlanConfig(同一个 cn-vm 上可并存多个 VLAN),只需再建一个 NAD default/net-92vlan: 92,route 指向 10.181.92.0/24 / 10.181.92.1
  3. 验证:bridge vlan show dev cn-vm-bo 现在应同时有 9192

VM 建第三块网卡选 net-92 即可;批量交付时 gen-vms.sh 用环境变量切换网段:NAD=default/net-92 GW=10.181.92.1 NET_PREFIX=10.181.92. ./gen-vms.sh vms-vlan92.csv render(见 6.5.6)。

4.6.6 容量扩展:一个 /24 不够用了怎么办

先算清楚:10.181.91.0/24 里真正能给 VM 用的只有约 150 个地址——.1 网关、.2–.10 基础设施、.11–.22 宿主机、.50–.99 LB 池都被占掉,剩下静态段 .101–.200(100 个)+ DHCP 段 .201–.250(50 个)。所以「超过 255 台」其实到 ~150 台就该触发扩容动作了。

四条路径对比:

方案做法代价适用
新增 VLAN 网段(首选)net-92 = 10.181.92.0/24,见 4.6.5交换机 trunk 加放 92 + 建一个 NAD,不动现有 VM任何时候;还能按业务/环境分段隔离
回收治理不需要固定 IP 的 VM 迁回 mgmt;能共享入口的改走 LB VIP只改 VM 配置地址紧张但总量不大
扩大网段(/24 → /23)SVI、DHCP、所有静态 IP 的掩码、防火墙策略全要改存量迁移极其痛苦只建议在规划期做
更大前缀重规划直接上 /22(1022 地址)或多个 /24一期设计决策预估总量 > 1000

推荐操作顺序:

  1. 先治理:审计现有 VM,纯内部负载(CI runner、批处理、集群内服务)只保留 mgmt 网卡,立刻释放固定 IP;多台 VM 对外提供同一服务的,改用 LB VIP 共享地址。
  2. 按业务/环境开新网段net-92 给第二业务线、net-93 给测试环境……每个网段原样复用同一套内部约定.1 网关、.11+ 宿主机、.50–.99 LB、.101–.200 静态、.201–.250 DHCP),台账按网段分表,gen-vms.shNET_PREFIX/GW/NAD 环境变量直接切换。
  3. 扩容动作(以 net-92 为例):
bash
# ① 交换机:vlan 92 + SVI 10.181.92.1/24;12 台 trunk 端口 allowed vlan 追加 92
# ② Harvester:ClusterNetwork / VlanConfig 都不动,只建新 NAD
kubectl apply -f - <<'EOF'
apiVersion: k8s.cni.cncf.io/v1
kind: NetworkAttachmentDefinition
metadata:
  name: net-92
  namespace: default
  annotations:
    network.harvesterhci.io/route: '{"mode":"manual","serverIPAddr":"","cidr":"10.181.92.0/24","gateway":"10.181.92.1"}'
  labels:
    network.harvesterhci.io/clusternetwork: cn-vm
    network.harvesterhci.io/type: L2VlanNetwork
    network.harvesterhci.io/vlan-id: "92"
spec:
  config: '{"cniVersion":"0.3.1","name":"net-92","type":"bridge","bridge":"cn-vm-br","promiscMode":true,"vlan":92,"ipam":{},"mtu":1500}'
EOF
# ③ 验证:91 与 92 同时放行
ssh rancher@10.181.0.11 'bridge vlan show dev cn-vm-bo'   # 期望同时看到 91 和 92
kubectl get nad -n default net-92                          # Ready=True
# ④ 批量交付到新网段
cd appendix/vm && NAD=default/net-92 GW=10.181.92.1 NET_PREFIX=10.181.92. \
  IMAGE_PVC=image-79hdq ./gen-vms.sh vms-vlan92.csv render
  1. 若用 Managed DHCP:为新网段单独建一个 IPPool(serverIP 取新网段的 .2,pool 取 .201–.250),不要跨网段共用一个池。

⚠️ MTU 是 ClusterNetwork 级共享的net-92net-91 同在 cn-vm 上,NAD 里的 "mtu" 必须与 VlanConfig 一致(本环境 1500)。想给新网段开 jumbo,得按 4.9.2 的流程整个 ClusterNetwork 一起改。

⚠️ 新网段的地址段纪律(保留段、LB 池、静态段、DHCP 池互不重叠)要原样复制,并在 CMDB 里为新网段单独建台账——地址冲突不会因为你换了个 VLAN 就放过你。


4.7 可选:给宿主机自身配 10.181.91.x(HostNetworkConfig)

VM 网络只解决 VM 的地址。但有些场景需要宿主机自己也在 10.181.91.0/24 里有地址:

  • Managed DHCP 需要节点上有可用的 serverIP
  • 想让节点通过业务网直连外部存储/备份服务器(独立 L3 路径);
  • Kube-OVN underlay 卸载(把跨节点 VM 流量从 mgmt 移到专用 VLAN);
  • 需要在宿主机上直接 tcpdump / ping 业务网段做排障。

v1.8 提供 HostNetworkConfig 来做这件事:它会创建 cn-vm-br.91 这样的 VLAN 子接口,并在节点重启后自动恢复

4.7.1 前置条件

  • 对应 ClusterNetwork 的 VlanConfig 已存在并覆盖目标节点(4.4 已完成);
  • static 模式:必须为 VlanConfig 选中的每一个节点都提供地址;后加入的节点要先更新 HostNetworkConfig 才会被配置;
  • underlay 模式:HostNetworkConfig 必须覆盖集群全部节点,否则 webhook 拒绝。

4.7.2 DHCP 模式(VLAN 91 上已有 DHCP 服务器时最省事)

yaml
# appendix/network/04-hostnetworkconfig-dhcp.yaml
apiVersion: network.harvesterhci.io/v1beta1
kind: HostNetworkConfig
metadata:
  name: cn-vm-vlan91-dhcp
spec:
  clusterNetwork: cn-vm
  vlanID: 91
  mode: dhcp

4.7.3 静态模式(本书推荐:地址可控)

yaml
# appendix/network/05-hostnetworkconfig-static.yaml
apiVersion: network.harvesterhci.io/v1beta1
kind: HostNetworkConfig
metadata:
  name: cn-vm-vlan91-static
spec:
  clusterNetwork: cn-vm
  vlanID: 91
  mode: static
  ips:
    harvester-01: 10.181.91.11/24
    harvester-02: 10.181.91.12/24
    harvester-03: 10.181.91.13/24
    harvester-04: 10.181.91.14/24
    harvester-05: 10.181.91.15/24
    harvester-06: 10.181.91.16/24
    harvester-07: 10.181.91.17/24
    harvester-08: 10.181.91.18/24
    harvester-09: 10.181.91.19/24
    harvester-10: 10.181.91.20/24
    harvester-11: 10.181.91.21/24
    harvester-12: 10.181.91.22/24

只作用于部分节点(用标签选):

yaml
spec:
  nodeSelector:
    matchLabels:
      network-role: l3
  clusterNetwork: cn-vm
  vlanID: 91
  mode: static
  ips:
    harvester-04: 10.181.91.14/24
    harvester-05: 10.181.91.15/24
bash
kubectl label node harvester-04 network-role=l3
kubectl label node harvester-05 network-role=l3
kubectl apply -f appendix/network/05-hostnetworkconfig-static.yaml

⚠️ HostNetworkConfig.spec.nodeSelectormatchLabels 结构,而 VlanConfig.spec.nodeSelector扁平 label map。两者写法不同,抄错就选不中节点,且不报错——只是「什么都没发生」。

4.7.4 验证

bash
kubectl get hostnetworkconfig cn-vm-vlan91-static -o yaml | sed -n '/status:/,$p'

# 节点上:
ip -br addr show cn-vm-br.91             # 期望 10.181.91.x/24 且 UP
bridge vlan show dev cn-vm-br            # 91 已加入 bridge
ping -c2 -I cn-vm-br.91 10.181.91.1      # 通网关

4.7.5 行为与限制(官方文档要点)

  • 切换 dhcpstatic、或修改静态 IP:会先移除子接口上的旧地址,再应用新地址(有短暂中断)。
  • 节点重启后:VLAN 子接口与地址自动恢复,DHCP 续租自动继续。
  • 对应 VlanConfig 被删除或 nodeSelector 变化:不再被覆盖的节点上的 VLAN 子接口自动移除。
  • 删除 HostNetworkConfig:VLAN ID 从 bridge/uplink 移除,地址一并删除。
  • 接口名 15 字符限制:生成名为 <ClusterNetwork>-br.<vlanID>cn-vm-br.91 = 11 字符,安全。

4.7.6 Kube-OVN underlay 卸载(本环境暂不需要,留档)

yaml
spec:
  underlay: true          # 该 VLAN 子接口承载 Kube-OVN 隧道流量
  clusterNetwork: cn-vm
  vlanID: 91
  mode: static
  ips: { ... }            # 必须覆盖全部 12 个节点

开启后控制器会把每个节点的 ovn.kubernetes.io/tunnel_interface 注解指向 cn-vm-br.91;设回 underlay: false 则恢复默认 mgmt 接口。

⚠️ 本环境默认 CNI 是 Canal(非 Kube-OVN),不要开 underlay。另外:在 underlay 生效期间,只要还有 VMI 存在,修改或删除对应 VlanConfig 会被 webhook 拒绝。


4.8 可选:Managed DHCP —— 让 Harvester 自己给 VM 发地址

如果 VLAN 91 上没有 DHCP 服务器,又不想给每台 VM 写静态 IP,可以启用 Harvester 的实验特性 Managed DHCP。

4.8.1 它是什么、不是什么

不是
由 addon harvester-vm-dhcp-controller 提供,不包含在 ISO 内,需单独安装不是默认功能,装完 ISO 就有
租约存在 etcd,全集群单一事实来源不是完整 DHCP 服务器(功能是子集)
Agent 按 IPPool 按需生成;控制面故障时仍能为已有实体继续服务不兼容 Kube-OVN overlay 网络
只作用于 VM CR 中声明的网卡VM 内部自行添加的网卡不支持
IPPool 变更后需手工重启 agent pod 才生效不支持 DHCP RELEASE

4.8.2 安装与启用

bash
# 1) 安装 addon(离线环境需先把该 YAML 及其镜像准备到内网仓库)
kubectl apply -f https://raw.githubusercontent.com/harvester/experimental-addons/v1.8/harvester-vm-dhcp-controller/harvester-vm-dhcp-controller.yaml

# 2) 启用:UI 的 Addons 页面打开开关,或 kubectl
kubectl -n harvester-system patch addon harvester-vm-dhcp-controller \
  --type=merge -p '{"spec":{"enabled":true}}'
kubectl -n harvester-system get addon harvester-vm-dhcp-controller \
  -o jsonpath='{.spec.enabled}{"\n"}'

⚠️ 该 addon 不会自动探测集群的 Service CIDR,默认按 10.53.0.0/16 处理。本环境正是默认值,无需额外配置。若你的集群改过 Service CIDR,必须在 Addon 的 valuesContent 里显式声明,否则创建 IPPool 会因网段重叠而失败:

yaml
apiVersion: harvesterhci.io/v1beta1
kind: Addon
metadata:
  name: harvester-vm-dhcp-controller
  namespace: harvester-system
spec:
  enabled: true
  valuesContent: |-
    serviceCIDR: 10.96.0.0/16

查当前 Service CIDR:kubectl -n kube-system get pods -l component=kube-apiserver -o yaml | grep "service-cluster-ip-range"

4.8.3 创建 IPPool(注意 API group)

yaml
# appendix/network/06-ippool-net-91-dhcp.yaml
apiVersion: network.harvesterhci.io/v1alpha1     # ← 不是 networking.harvesterhci.io
kind: IPPool
metadata:
  name: net-91
  namespace: default
spec:
  networkName: default/net-91                    # 指向 4.6 创建的 NAD
  ipv4Config:
    serverIP: 10.181.91.2                        # DHCP 服务使用的地址(VLAN 91 内可用)
    cidr: 10.181.91.0/24
    router: 10.181.91.1                          # 下发给 VM 的默认网关
    pool:
      start: 10.181.91.201
      end: 10.181.91.250
      exclude:
        - 10.181.91.210
        - 10.181.91.211
    dns:
      - 10.181.0.2
    domainName: example.local
    domainSearch:
      - example.local
    ntp:
      - ntp.aliyun.com
    leaseTime: 3600
bash
kubectl apply -f appendix/network/06-ippool-net-91-dhcp.yaml
kubectl get ippools.network.harvesterhci.io -n default net-91

期望:

text
NAME     NETWORK         AVAILABLE   USED   REGISTERED   CACHEREADY   AGENTREADY
net-91   default/net-91  48          0      True         True         True

4.8.4 工作机制与验证

  • 控制器为每个 IPPool 拉起 agent pod;创建 VM 时控制器自动生成 VirtualMachineNetworkConfig 对象(一般无需手工创建),把 IP↔MAC 映射写入该对象与 IPPool;VM 删除时自动清理。
  • VM 侧网卡使用 DHCP 即可拿到地址(cloud-init 写法见 5.3.4 变体 A)。
bash
kubectl get virtualmachinenetworkconfig -A
kubectl get virtualmachinenetworkconfig <name> -n default -o yaml    # IP-MAC 映射
kubectl get pods -n harvester-system | grep -i dhcp                  # controller + agent
kubectl get ippools.network.harvesterhci.io -n default net-91 -o yaml | sed -n '/status:/,$p'

⚠️ 修改 IPPool(例如扩大地址池)后不会自动生效,必须手工重启相关 agent pod。先用 kubectl get pods -n harvester-system --show-labels | grep -i dhcp 确认标签,再删除对应 pod 让其重建。

⚠️ 地址池务必与 LB 池(.50-.99)、VM 静态段(.101-.200)、宿主机段(.11-.22)互不重叠;重叠会导致偶发 IP 冲突,症状是「时通时断」,极难定位。


4.9 后续变更:改 MTU / 改 bond 模式 / 换网卡 / 加节点

4.9.1 三条硬约束(webhook 会直接拒绝)

约束触发场景正确做法
还有 VMI 在运行时不能改 VlanConfig 的 uplink/bond 选项、不能删 NAD直接 kubectl edit vlanconfig先把该网络上的 VM 迁移或关机(见 7.6)
同一 ClusterNetwork 下所有 VlanConfig 的 MTU 必须一致只改了一个 VlanConfig一次性把所有相关 VlanConfig 的 MTU 都改掉
underlay 模式生效期间,存在 VMI 时不能改/删对应 VlanConfig开了 Kube-OVN underlay先关 underlay 或迁走 VMI

4.9.2 变更 MTU(例如 1500 → 9000 启用巨帧)

顺序很重要,必须交换机先行

  1. 交换机上把对应端口/VLAN 的 MTU(或全局 jumbo frame)调到 ≥9000。
  2. 修改 VlanConfig.spec.uplink.linkAttributes.mtu: 9000(同 ClusterNetwork 下的所有 VlanConfig 一起改)。
  3. 修改对应 NAD 的 spec.config 里的 "mtu": 9000(UI:Networks → VM Networks → Edit → MTU)。
  4. 验证:
    bash
    kubectl get nad -n default net-91 -o jsonpath='{.spec.config}' | python3 -m json.tool | grep mtu
    ssh rancher@10.181.0.11 'cat /sys/class/net/cn-vm-bo/mtu'
    # VM 内实测巨帧(DF 置位、payload = MTU - 28)
    ping -M do -s 8972 -c3 10.181.91.1
  5. 已运行的 VM 需要重启(或重建)才能用上新 MTU。

4.9.3 更换/新增物理网卡(硬件维护)

  1. 前置检查:确认新网卡被当前 Harvester 版本与内核支持;在非生产环境先验证。
  2. 逐台操作(一次只动一台,保持其余节点可承载 VM):
    • 无法迁移的 VM 先手工关机;
    • UI 上对该节点启用维护模式,其余 VM 自动迁移走(见 7.6);
    • 等待该节点无 VMI、集群 Ready;
    • 停机换卡;开机后确认新网口名(ip -br link),必要时先改 VlanConfig.spec.uplink.nics
    • 关闭维护模式,观察 VlanStatus 重新 Ready、bond 成员正确。
  3. 12 台逐台滚动,不要并发。

4.9.4 新增节点时网络要做什么

新节点加入后,若其 VlanConfig 的 nodeSelector 覆盖到它(例如按 harvesterhci.io/managed=true),控制器会自动在其上创建 bond/bridge,无需手工干预。需要额外注意的只有两点:

  • 新网卡的命名必须与现有节点一致eno3/eno4),否则要为它单独写一个 VlanConfig;
  • 若使用了 HostNetworkConfig 的 static 模式,必须先把新节点名加进 ips,否则该节点不会被配置 VLAN 子接口。
bash
kubectl get vlanstatus | grep <新节点>        # 确认自动生成了对应 VlanStatus

4.10 本章验证清单

K8s 对象层

bash
kubectl get clusternetwork                                     # mgmt + cn-vm
kubectl get vlanconfig -o wide                                 # READY=True
kubectl get nodes -L network.harvesterhci.io/vlanconfig        # 12 节点都有归属
kubectl get vlanstatus -o custom-columns=NODE:.status.node,READY:.status.conditions[0].status,AREA:.status.localAreas
kubectl get nad -A                                             # net-91 READY=True
kubectl get nad -n default net-91 -o jsonpath='{.spec.config}{"\n"}'
kubectl get hostnetworkconfig -o wide                          # 若使用了 4.7
kubectl get ippools.network.harvesterhci.io -A                 # 若使用了 4.8

节点层(每台抽查,至少 3 台)

bash
ip -br link | grep cn-vm                    # cn-vm-bo / cn-vm-br 均 UP
cat /proc/net/bonding/cn-vm-bo | egrep 'Mode|MII Status|Slave Interface'
bridge vlan show dev cn-vm-bo | grep -w 91  # VLAN 91 已放行
ip -br addr show cn-vm-br.91                # 若配了 HostNetworkConfig

连通性层

bash
# 宿主机临时子接口 ping 网关(测完删除)
ping -c2 -I cn-vm-br.91 10.181.91.1
# 或用 4.5.3 的临时子接口方法
检查项期望
ClusterNetwork cn-vmReady=True
VlanConfig vlan-cfg-vmReady=True,覆盖 12 节点
VlanStatus12 条,全部 ready=True,localAreas 含 10.181.91.0/24 vlanID=91
NAD default/net-91Ready=True,config 里 bridge=cn-vm-brvlan=91ipam={}
UI VM NetworksNetwork connectivity = Active(12/12)
节点 bondmode 与交换机聚合模式匹配,MII up
bridge VLAN91 已加入 cn-vm-bo 的 vlan 列表
10.181.91.1从宿主机 ping 通
UI 建 VM网络下拉框可见 default/net-91

下一章:把 VM 的第二块网卡接进 net-91,用 cloud-init 让它拿到 10.181.91.x,并从外部 SSH 进去验证。