主题
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 前置条件(不满足就别开始)
| # | 前置条件 | 检查命令 |
|---|---|---|
| 1 | 12 节点全部 Ready | kubectl get nodes |
| 2 | eno3/eno4 未被 Harvester 占用(不在 mgmt bond 里) | cat /proc/net/bonding/mgmt-bo | grep 'Slave Interface' |
| 3 | eno3/eno4 物理链路 up | ethtool eno3 | grep -i 'link detected' |
| 4 | 交换机对应端口为 trunk 且放行 VLAN 91 | 交换机侧 show interfaces trunk |
| 5 | 若用 802.3ad,交换机已配 LACP 聚合组 | 交换机侧 show etherchannel summary |
| 6 | VLAN 91 的 SVI/网关 10.181.91.1 已存在 | 从同 VLAN 机器 ping 10.181.91.1 |
| 7 | eno3/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 方式
- Networks → Cluster Networks → Create。
- 名称填
cn-vm(短、小写、只用字母数字和-,它会成为 Linux 接口名的一部分)。 - 提交。此时
cn-vm的 Ready 会是 False,因为还没有任何 VlanConfig 把网卡绑上来——这是正常的,不是错误。
4.3.2 kubectl 方式
yaml
# appendix/network/01-clusternetwork-cn-vm.yaml
apiVersion: network.harvesterhci.io/v1beta1
kind: ClusterNetwork
metadata:
name: cn-vmbash
kubectl apply -f appendix/network/01-clusternetwork-cn-vm.yaml
kubectl get clusternetwork cn-vm -o yamlClusterNetwork 对象本身几乎没有 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: -1bash
kubectl apply -f appendix/network/02-vlanconfig-vlan-cfg-vm.yaml4.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 失败」的原因之一。
4.4.3 关于 uplink.nics 的三个事实
nics里写的是宿主机上的物理网卡名(不是 MAC)。因此 12 台机器的网口命名必须一致,否则要按节点分组写多个 VlanConfig。装机前用ip -br link核对(2.4.3)。- 写两张卡就会自动组成 bond(名为
<cn>-bo);只写一张卡则不聚合(依然会生成 bridge)。 - 这些网卡不能已经在 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 yamlVlanStatus 的关键字段(真实样例):
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-vm4.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 标签的包过不了 bridgebash
# ④ 物理网卡确实被 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/mtu4.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 方式(推荐,最不容易写错)
- Networks → VM Networks → Create。
- 填写:
| 字段 | 本环境取值 | 说明 |
|---|---|---|
| Name | net-91 | 与 VLAN 号对应,见名知意 |
| Namespace | default | VM 默认命名空间;多租户可分开 |
| Type | VLAN Network | 另有 Untagged / VLAN Trunk / Overlay(Kube-OVN, 实验) |
| VLAN ID | 91 | 范围 2–4094;必须与交换机放行的 VLAN 一致 |
| Cluster Network | cn-vm | 只有已被 VlanConfig 覆盖的 ClusterNetwork 才可选/才有效 |
| Route 标签页 | 见下 | 决定 Harvester 如何验证「每个节点都能到达这个网络」 |
- 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。
- 提交后检查 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.tool4.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 字段,标签只有 clusternetwork 和 type:
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.100、eth1.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):
- 交换机:创建 VLAN 92 + SVI
10.181.92.1,把 92 加进 12 台机器 trunk 端口的 allowed vlan(与 91 并列)。 - Harvester:不用动 ClusterNetwork 和 VlanConfig(同一个
cn-vm上可并存多个 VLAN),只需再建一个 NADdefault/net-92,vlan: 92,route 指向10.181.92.0/24/10.181.92.1。 - 验证:
bridge vlan show dev cn-vm-bo现在应同时有91和92。
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 |
推荐操作顺序:
- 先治理:审计现有 VM,纯内部负载(CI runner、批处理、集群内服务)只保留 mgmt 网卡,立刻释放固定 IP;多台 VM 对外提供同一服务的,改用 LB VIP 共享地址。
- 按业务/环境开新网段:
net-92给第二业务线、net-93给测试环境……每个网段原样复用同一套内部约定(.1网关、.11+宿主机、.50–.99LB、.101–.200静态、.201–.250DHCP),台账按网段分表,gen-vms.sh的NET_PREFIX/GW/NAD环境变量直接切换。 - 扩容动作(以 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- 若用 Managed DHCP:为新网段单独建一个 IPPool(
serverIP取新网段的.2,pool 取.201–.250),不要跨网段共用一个池。
⚠️ MTU 是 ClusterNetwork 级共享的:
net-92与net-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: dhcp4.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/24bash
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.nodeSelector用matchLabels结构,而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 行为与限制(官方文档要点)
- 切换
dhcp↔static、或修改静态 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 会因网段重叠而失败:yamlapiVersion: 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: 3600bash
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 True4.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 启用巨帧)
顺序很重要,必须交换机先行:
- 交换机上把对应端口/VLAN 的 MTU(或全局 jumbo frame)调到 ≥9000。
- 修改
VlanConfig.spec.uplink.linkAttributes.mtu: 9000(同 ClusterNetwork 下的所有 VlanConfig 一起改)。 - 修改对应 NAD 的
spec.config里的"mtu": 9000(UI:Networks → VM Networks → Edit → MTU)。 - 验证: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 - 已运行的 VM 需要重启(或重建)才能用上新 MTU。
4.9.3 更换/新增物理网卡(硬件维护)
- 前置检查:确认新网卡被当前 Harvester 版本与内核支持;在非生产环境先验证。
- 逐台操作(一次只动一台,保持其余节点可承载 VM):
- 无法迁移的 VM 先手工关机;
- UI 上对该节点启用维护模式,其余 VM 自动迁移走(见 7.6);
- 等待该节点无 VMI、集群 Ready;
- 停机换卡;开机后确认新网口名(
ip -br link),必要时先改VlanConfig.spec.uplink.nics; - 关闭维护模式,观察
VlanStatus重新 Ready、bond 成员正确。
- 12 台逐台滚动,不要并发。
4.9.4 新增节点时网络要做什么
新节点加入后,若其 VlanConfig 的 nodeSelector 覆盖到它(例如按 harvesterhci.io/managed=true),控制器会自动在其上创建 bond/bridge,无需手工干预。需要额外注意的只有两点:
- 新网卡的命名必须与现有节点一致(
eno3/eno4),否则要为它单独写一个 VlanConfig; - 若使用了
HostNetworkConfig的 static 模式,必须先把新节点名加进ips,否则该节点不会被配置 VLAN 子接口。
bash
kubectl get vlanstatus | grep <新节点名> # 确认自动生成了对应 VlanStatus4.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-vm | Ready=True |
VlanConfig vlan-cfg-vm | Ready=True,覆盖 12 节点 |
| VlanStatus | 12 条,全部 ready=True,localAreas 含 10.181.91.0/24 vlanID=91 |
NAD default/net-91 | Ready=True,config 里 bridge=cn-vm-br、vlan=91、ipam={} |
| UI VM Networks | Network connectivity = Active(12/12) |
| 节点 bond | mode 与交换机聚合模式匹配,MII up |
| bridge VLAN | 91 已加入 cn-vm-bo 的 vlan 列表 |
到 10.181.91.1 | 从宿主机 ping 通 |
| UI 建 VM | 网络下拉框可见 default/net-91 |
下一章:把 VM 的第二块网卡接进 net-91,用 cloud-init 让它拿到 10.181.91.x,并从外部 SSH 进去验证。