主题
05 · 接入 Rancher(Virtualization Management)与内外网分离解析
目标:让 Harvester 集群(
192.168.150.0/24,virbr10)用内网地址访问 Rancher (RKE2 三节点192.168.122.20/21/22,virbr0),从而被 Rancher 导入并在 Virtualization Management 页面统一管理;宿主机/公网侧仍走https://ai-ear.cn:30443(frp 隧道)。本文全部为实测结果(2026-09-06)。相关故障根因见
02-故障排查与修复记录.md案例 24~27, 一键脚本为deploy/scripts/99-rancher-internal-net.sh。
1. 最终结果(实测)
| 项目 | 值 |
|---|---|
| Rancher 集群名 | harvester-hci,id c-vd78l |
| provider | harvester(label provider.cattle.io: harvester,因此归入 Virtualization Management) |
| 状态 | state=active,nodeCount=3,Provisioned/Connected/Ready 均为 True |
| agent | cattle-system/cattle-cluster-agent ×2 Running;cattle-fleet-system/fleet-agent Running |
| agent 入口 | CATTLE_SERVER=https://ai-ear.cn(443,不带端口)、CATTLE_CA_CHECKSUM="" |
| 证书 | DigiCert CN=ai-ear.cn,节点系统信任(curl 不加 -k 即返回 pong) |
| agent 镜像 | 192.168.122.156:30000/rancher/rancher-agent:v2.14.3(Rancher 的 system-default-registry) |
| UI 数据面 | GET /k8s/clusters/c-vd78l/v1/harvester/nodes → count=3 |
2. ★ 为什么"只配 DNS"不够:三件事缺一不可
| # | 障碍 | 实测现象 |
|---|---|---|
| 1 | 跨网桥转发被拒 | libvirt 给每个 NAT 网络插入 FORWARD REJECT,virbr10 ↔ virbr0 双向都不通:harvester-01 → 192.168.122.20:443/22 全超时,而 → 192.168.150.1(宿主机)正常 |
| 2 | 没有内网解析 | harvester 网络的 dnsmasq 无 ai-ear.cn 记录,节点解析到公网 8.153.84.140;而公网 443 是文档站,Rancher 只在公网 30443 |
| 3 | CoreDNS 随机上游(最隐蔽) | 节点 resolv.conf 并列 192.168.150.1 与 223.5.5.5 时,Harvester 的 CoreDNS 是 forward . /etc/resolv.conf 且默认 policy=random:直查 CoreDNS 20 次,10 次返回公网 IP |
第 3 条的后果:cattle-cluster-agent 连 wss://ai-ear.cn/v3/connect/register 时 拿到的是 VitePress 首页 HTML,报 websocket: bad handshake,集群一度 state=error (CoreDNS cache 30 + 上游 TTL 过期后会自愈,但反复抖动)。 收敛为单一内网 DNS 后,20/20 全部内网。
详见
02案例 24。这也是为什么装机配置里的dns_nameservers也要收敛(§4)。
3. 一键修复:99-rancher-internal-net.sh 的 A~F 六步
bash
sudo bash scripts/99-rancher-internal-net.sh check # 只看不动(含节点侧巡检)
sudo bash scripts/99-rancher-internal-net.sh apply # 应用,幂等,可反复跑
sudo bash scripts/99-rancher-internal-net.sh rollback # 回滚 A/C/D(E/F 不回滚,见下)| 步骤 | 位置 | 内容 | 关键点 |
|---|---|---|---|
| A | 宿主机 | iptables -I FORWARD 1 -i virbr10 -o virbr0 -j ACCEPT(及反向) | 必须插在 libvirt 的 REJECT 之前;先 iptables -C 探测避免重复 |
| B | 宿主机 | 把放行块追加到 /etc/libvirt/hooks/network | 先备份原文件、只追加不覆盖;带 MARKER 幂等;网络/宿主重启后自动重插 |
| C | 宿主机 | virsh net-update harvester add dns-host:ai-ear.cn → 192.168.122.20/21/22 | --live --config 持久化到网络 XML |
| D | 节点 ×3 | nmcli con mod bridge-mgmt ipv4.dns 192.168.150.1 + nmcli device reapply mgmt-br | ★ 用 device reapply 不断链;仅在真改动时才 rollout restart CoreDNS |
| E | 节点 ×3 | 写 certs.d/192.168.122.156:30000/hosts.toml | containerd config_path 已启用 → 动态生效免重启 |
| F | Harvester API | 设置 containerd-registry | ★ 官方持久机制:控制器据此在各节点渲染 /etc/rancher/rke2/registries.yaml,rke2 再重写 certs.d |
⚠️
rollback不动 E/F:删掉私仓配置后cattle-cluster-agent将再也拉不到镜像。
内外域名分离:VM 内网直连 ingress;宿主机/公网仍走 frp:30443,互不影响。
4. 装机配置同步收敛(否则重装后又引入随机上游)
50-gen-configs.sh 与 configs/、http/configs/ 下的 config-node0*.yaml 的 dns_nameservers 只保留宿主 dnsmasq:
yaml
os:
# 只写宿主 dnsmasq:它既提供内网静态解析(ai-ear.cn -> RKE2 ingress 192.168.122.20/21/22),
# 也会把其它域名转发到公网。不要并列公网 DNS:Harvester 的 CoreDNS 用
# 「forward . /etc/resolv.conf」且默认随机选上游,实测约 50% 概率把 ai-ear.cn
# 解析到公网(公网 443 是文档站),导致 cattle-cluster-agent 间歇连不上 Rancher。
dns_nameservers:
- 192.168.150.1原文件已备份为 *.bak-dns(configs/ 与 http/configs/ 各 3 份)。
去掉公网 DNS 无副作用:宿主 dnsmasq 自身会转发公网域名(实测 www.baidu.com 正常解析)。
5. 导入 Rancher 的实际步骤(API 方式,本次已执行)
bash
TOK=$(cat /tmp/rancher.token) # Rancher API Token(admin)
# 1) 建「导入集群」;provider 会由 Rancher 事后从下游节点标签识别为 harvester
curl -sk -X POST -H "Authorization: Bearer $TOK" -H 'Content-Type: application/json' \
-d '{"name":"harvester-hci"}' https://ai-ear.cn:30443/v3/clusters # → id: c-vd78l
# 2) 取注册 manifest URL(server-url 是 https://ai-ear.cn,所以 URL **不带端口**)
curl -sk -H "Authorization: Bearer $TOK" \
https://ai-ear.cn:30443/v3/clusters/c-vd78l/clusterRegistrationTokens
# → https://ai-ear.cn/v3/import/<token>_c-vd78l.yaml
# 3) 在 Harvester 上设置 cluster-registration-url(注意 value 本身是一段 JSON 字符串)
ssh rancher@192.168.150.11 'sudo /var/lib/rancher/rke2/bin/kubectl \
--kubeconfig /etc/rancher/rke2/rke2.yaml patch settings.harvesterhci.io \
cluster-registration-url --type merge -p \
"{\"value\":\"{\\\"url\\\":\\\"https://ai-ear.cn/v3/import/<token>_c-vd78l.yaml\\\",\\\"insecureSkipTLSVerify\\\":false}\"}"'Harvester 收到设置后自动完成:3 个节点各跑一个 apply-system-agent-upgrader-on-harvester-0N-with-* Job → 部署 cattle-cluster-agent(2 副本) → Rancher 侧 Provisioned/Connected/Ready 依次转 True,state 由 initializing → active, 并自动装上 fleet-agent 与 cattle-impersonation-system。
💡 不要用
harvesterconfig/harvestercredentialconfig:那是"用 Harvester 当基础设施 provider 去创建下游 K8s 集群"的路径。导入已有 Harvester 走标准POST /v3/clusters, provider 由 Rancher 自动识别(02案例 27)。
6. 验证命令
6.1 节点侧(任一 Harvester 节点)
bash
getent hosts ai-ear.cn # 期望 192.168.122.20/21/22
curl -s https://ai-ear.cn/ping # 期望 pong(★ 故意不加 -k,验证证书被系统信任)
# ★ DNS 收敛是否真的生效:只应出现内网 IP;出现 8.153.84.140 说明步骤 D 未生效
for i in $(seq 1 20); do dig +short ai-ear.cn @10.53.0.10 | tr '\n' ' '; echo; done \
| sort | uniq -c
# ★ 私仓是否可用(绝对路径 + 显式 runtime-endpoint,见 02 案例 27)
sudo /var/lib/rancher/rke2/bin/crictl \
--runtime-endpoint unix:///run/k3s/containerd/containerd.sock \
pull 192.168.122.156:30000/rancher/rancher-agent:v2.14.3 # 期望 Image is up to date6.2 Rancher 侧
bash
curl -sk -H "Authorization: Bearer $TOK" https://ai-ear.cn:30443/v3/clusters/c-vd78l \
| jq '{state,provider,nodeCount}' # 期望 active / harvester / 3
curl -sk -H "Authorization: Bearer $TOK" \
https://ai-ear.cn:30443/k8s/clusters/c-vd78l/v1/harvester/nodes | jq '.count,[.data[].id]'
# 期望 3 / ["harvester-01","harvester-02","harvester-03"]7. Rancher UI 数据面的代理路径(复用价值高)
/k8s/clusters/<clusterId>/v1/harvester/...这就是 Rancher Virtualization Management 页面所用的代理路径 (dashboard 插件里拼作 "/k8s/clusters/" + clusterId + "/v1/harvester", local 集群则省略前缀)。
经该隧道同样能取到 KubeVirt 资源,例如:
/k8s/clusters/c-vd78l/v1/harvester/apis/kubevirt.io/v1/namespaces/default/virtualmachines用途:在 Rancher 侧做统一巡检/自动化时,不需要再单独维护一套 Harvester kubeconfig。
8. 本节踩坑索引(详见 02)
| 案例 | 一句话根因 |
|---|---|
| 24 | CoreDNS forward . /etc/resolv.conf + policy=random → 域名约 50% 概率解析到公网 → agent websocket: bad handshake |
| 25 | 私仓是 HTTP 且节点上不了 docker.io → crictl pull 报 http: server gave HTTP response to HTTPS client |
| 26 | 手写 registries.yaml 会被控制器回收(167B → 0B)→ 必须改 containerd-registry 设置;JSON 字段名从 rancherd 二进制 struct tag 反查 |
| 27 | 三个操作细节:CoreDNS forward 只在启动时读 resolv.conf;kubectl/crictl/ctr 必须绝对路径 + --runtime-endpoint;节点 SSH host key 变过要 UserKnownHostsFile=/dev/null |