Skip to content

Harvester Dashboard 公网访问(frp 隧道 + Traefik TLS 终止)

状态:已落地并实测通过(2026-09-07 07:33,30-verify-public.sh 14/14 全绿) 公网入口:https://ai-ear.cn:9443/ | 账号 admin | 口令见 ../../README.md §6


0. 30 秒速览

公网访问地址https://ai-ear.cn:9443/ → 自动 302 到 /dashboard/
内网真实地址https://192.168.150.200/(Harvester VIP,kube-vip 漂移,自签证书)
浏览器证书CN=ai-ear.cn 正式证书,无告警(TLS 在公网 Traefik 终止)
frp 隧道端口公网 127.0.0.1:19048 ⇄ 本机 192.168.150.200:443,代理名 harvester-vip-9443
落地脚本10-frpc-tunnel.sh(本机)→ 20-traefik-route.sh(公网)→ 30-verify-public.sh(验证)
回滚每个写操作脚本都有 rollback 子命令;备份文件见 §6

链路(4 跳)

浏览器
  │  https://ai-ear.cn:9443/            ← TLS 在这里终止(正式证书 CN=ai-ear.cn)

公网机 8.153.84.140 · Traefik 3.7.10
  │  entryPoint ep9443 → router harvester-9443(Host(`ai-ear.cn`) + tls:{})
  │  → service svc-19048 → https://127.0.0.1:19048
  │    serversTransport: harvester-insecure(insecureSkipVerify: true,因为后端是自签)

公网机 · frps:19048  ⇄⇄⇄  frp 隧道(tcpMux + token)  ⇄⇄⇄  本机 frpc
  │  代理 harvester-vip-9443:type=tcp, localIP=192.168.150.200, localPort=443

本机 10.20.24.225 · Harvester VIP 192.168.150.200:443(kube-vip → 当前主节点)
     Rancher Dashboard 框架的 SPA + /v3 API + /v1/subscribe WebSocket

1. 三个关键决策(以及被否掉的方案)

#决策为什么被否掉的方案
1frpc 的 localIP 指向 VIP 192.168.150.200,不是某个节点 IPHarvester 三节点 HA,API/Dashboard 由 kube-vip 提供,主节点会漂移;绑节点 IP 会在切换后 502localIP = 192.168.150.11(node01)——主节点切换即失效
2TLS 在 Traefik 终止(HTTP 反代),不是 TCP passthroughpassthrough 会把 Harvester 的自签证书直接递给浏览器 → 每次访问都要点"继续前往",且证书 CN=dynamic 与域名完全不匹配tcp routers + HostSNI 直通 443(本仓库 dynamic.yaml 里的 svc-19047 那种玩法)
3后端必须配 serversTransport.insecureSkipVerify: true反代目标是 HTTPS 且证书自签,Traefik 默认校验会直接失败:x509: certificate signed by unknown authority → 前端 502让 Traefik 用普通 transport(实测 502)

端口选择:9443 未被占用,且与既有 ep8443(GitLab)、ep30443(Rancher) 命名风格一致; 19048 沿用公网机 190xx 隧道段(已有 19036/19047/…),只用作 Traefik 的上游。


2. 三个脚本怎么用

bash
cd worker/harvester-iso/deploy/public-access

# ① 本机 frpc 隧道(需要 root 写 /etc/frp/frpc.toml)
./10-frpc-tunnel.sh check      # 只读体检:代理是否在、frpc 是否 active、VIP /ping、公网 19048 是否监听
./10-frpc-tunnel.sh apply      # 备份 → 追加 [[proxies]] → frpc verify → systemctl restart frpc → 核验
./10-frpc-tunnel.sh rollback   # 用备份还原 frpc.toml 并重启

# ② 公网 Traefik 路由(SSH 到 root@8.153.84.140 执行)
./20-traefik-route.sh check    # 只读体检:版本、entryPoint/router/service 是否存在、端口是否监听
./20-traefik-route.sh apply    # 备份 → awk 插入 → PyYAML 语法+结构双校验 → 落盘 → restart traefik → 本地探测
./20-traefik-route.sh rollback # 还原两个 yaml 并重启

# ③ 端到端验证(10 层,见 §4)
./30-verify-public.sh              # 全量(含登录 + WebSocket)
./30-verify-public.sh --no-auth    # 跳过 L8/L9/L10
HV_PASS='新口令' ./30-verify-public.sh   # 口令已改时传入,不落盘

三个脚本都是幂等的:apply 重复执行只会打印 [skip] ... 已存在,不会重复插入、不会无谓重启服务。

2.1 没有免密 sudo 时怎么执行 root 段

本机 xxxdocker 组但没有免密 sudo,而 /etc/frp/frpc.toml 属 root。 用特权容器 + nsenter 进入宿主机命名空间即可(等效 root,且脚本本身在宿主文件系统上跑):

bash
docker run --rm --privileged --pid=host docker.m.daocloud.io/library/ubuntu:22.04 \
  nsenter -t 1 -m -u -i -n -- \
  bash /home/xxx/docs/docs/ai-ear-website/worker/harvester-iso/deploy/public-access/10-frpc-tunnel.sh apply

⚠️ 以 root 身份跑时,脚本里"SSH 到公网机核验 19048 是否监听"这一步会因为没有 root 免密密钥而失败。 脚本对此返回 nossh跳过核验(不算失败),改用 xxx 身份跑 30-verify-public.sh 做端到端确认即可。


3. 线上实际落地的配置(原文,2026-09-07 复核)

3.1 本机 /etc/frp/frpc.toml 追加块

toml
# >>> # harvester-public-access >>>
# Harvester HCI Dashboard(kube-vip VIP 192.168.150.200:443,ISO 一体机形态)
# → frps 8.153.84.140:19048 → Traefik ep9443 → https://ai-ear.cn:9443/
[[proxies]]
name = "harvester-vip-9443"
type = "tcp"
localIP = "192.168.150.200"      # Harvester VIP,随 kube-vip 漂移;不要写死节点 IP
localPort = 443
remotePort = 19048
# <<< # harvester-public-access <<<

首尾两行标记是 10-frpc-tunnel.sh 写入的,rollback 靠它精准删块(# >>># <<<)。

frpc 日志确认注册成功:[I] [client/control.go:165] [harvester-vip-9443] start proxy success

3.2 公网 /etc/traefik/traefik.yaml(静态配置,改后必须 restart

yaml
entryPoints:
  # ……原有 32 个 entryPoint 保持不变……
  ep36000:
    address: ":36000"

  # Harvester HCI Dashboard(本机 VIP 192.168.150.200:443 ← frp 隧道,2026-09-06)
  ep9443:
    address: ":9443"

providers:
  file:
    filename: /etc/traefik/dynamic.yaml
    watch: true          # 只热加载 dynamic.yaml;entryPoint 属静态配置,热加载不生效

3.3 公网 /etc/traefik/dynamic.yaml(动态配置,watch: true 自动热加载)

yaml
http:
  # Harvester 后端走 frp 隧道且为自签证书 → 跳过证书校验(2026-09-06)
  serversTransports:
    harvester-insecure:
      insecureSkipVerify: true
      maxIdleConnsPerHost: 200
  routers:
    # Harvester HCI Dashboard(https://ai-ear.cn:9443/ → frp https://127.0.0.1:19048 → 本机 VIP 192.168.150.200:443)
    harvester-9443:
      entryPoints: [ep9443]
      rule: "Host(`ai-ear.cn`)"
      service: svc-19048
      tls: {}                     # 用 tls.stores.default 的 CN=ai-ear.cn 正式证书
  services:
    svc-19048:
      loadBalancer:
        servers:
          - url: "https://127.0.0.1:19048"    # 注意是 https:Harvester 后端只说 TLS
        serversTransport: harvester-insecure

3.4 落盘后核验结果

text
static entryPoints: ['web','websecure','ep9000',…,'ep36000','ep9443']  ← 32 原有 + 1 新增,无损坏
harvester router  : {'entryPoints': ['ep9443'], 'rule': 'Host(`ai-ear.cn`)', 'service': 'svc-19048', 'tls': {}}
harvester service : {'loadBalancer': {'servers': [{'url': 'https://127.0.0.1:19048'}],
                                      'serversTransport': 'harvester-insecure'}}
serversTransport  : {'insecureSkipVerify': True, 'maxIdleConnsPerHost': 200}
routers count: 33   tcp routers: 3      ← main-website / web-to-websecure / tcp 直通路由均未受影响
traefik 日志      : 无 error / warn

4. 验证清单(30-verify-public.sh,10 层 14 项,实测全绿)

检查项实测结果(2026-09-07 07:33)
L1VIP 直连 https://192.168.150.200/ping200 ✅ 集群本身活着
L2frpc.tomlharvester-vip-9443frpc.service已配置 ✅ / active
L3公网 :19048 监听(frps 已注册隧道)[::]:19048 frps(pid 870)
L4公网 :9443 监听(entryPoint 生效)[::]:9443 traefik(pid 25826)
L5证书链校验(不加 -k)+ /pingCN=ai-ear.cnnotAfter=Oct 15 2026Verify return code: 0 (ok) ✅;/ping200
L6GET / 跳转;响应头不泄露内网地址302 Location: /dashboard/(相对路径)✅;头里无 192.168.150.x / :19048
L7SPA 骨架 + 前端资源可加载(决定会不会白屏)/dashboard/200(2534 字节,含 id="app")✅;/dashboard/js/index.b827b2fd.js200(1557036 字节)✅
L8POST /v3-public/localProviders/local?action=login201 + token-wq6x4:…
L9GET /v3/users?me=true(带 Bearer token)200,`mustChangePassword=xxx ✅
L10WebSocket GET /v1/subscribe 协议升级HTTP/1.1 101 Switching Protocols ✅(UI 实时刷新、VM VNC 控制台依赖)

手工快速三连(不想跑脚本时):

bash
curl -s  -o /dev/null -w '%{http_code}\n' https://ai-ear.cn:9443/ping        # 200;不加 -k 能过 = 证书没问题
curl -sk -D - -o /dev/null https://ai-ear.cn:9443/ | grep -i '^location'     # Location: /dashboard/
curl -sk -o /dev/null -w '%{http_code}\n' https://ai-ear.cn:9443/dashboard/  # 200

5. 回滚(备份文件都在,可直接还原)

对象备份文件(本次落地生成)一条命令回滚
本机 /etc/frp/frpc.toml/etc/frp/frpc.toml.bak.20260906223048./10-frpc-tunnel.sh rollback(需 root,见 §2.1)
公网 /etc/traefik/traefik.yaml/etc/traefik/traefik.yaml.bak.20260906223344./20-traefik-route.sh rollback
公网 /etc/traefik/dynamic.yaml/etc/traefik/dynamic.yaml.bak.20260906223344同上(一次还原两个文件 + restart traefik)

手工回滚(等价操作):

bash
# 本机:删掉标记块并重启 frpc(或直接还原备份)
sudo cp -a /etc/frp/frpc.toml.bak.20260906223048 /etc/frp/frpc.toml && sudo systemctl restart frpc

# 公网:还原两个 yaml 并重启(entryPoint 是静态配置,必须重启才摘掉 :9443)
ssh root@8.153.84.140 'cd /etc/traefik && cp -a traefik.yaml.bak.20260906223344 traefik.yaml \
  && cp -a dynamic.yaml.bak.20260906223344 dynamic.yaml && systemctl restart traefik \
  && ss -tln | grep -c ":9443\b"'      # 期望输出 0

回滚后 https://ai-ear.cn:9443/ 立即失效,但 Harvester 集群本身与内网 https://192.168.150.200/ 完全不受影响—— 本方案是纯旁路接入,没有在集群里装任何东西。


6. 踩坑记录(每条都是这次实际撞到的)

#现象根因处置
110-frpc-tunnel.sh apply 末尾告警"公网 19048 未监听",但 frpc 日志明明 start proxy success脚本以 root(docker+nsenter)身份跑,而 root 没有到 8.153.84.140 的免密密钥,SSH 探测直接失败被当成"未监听"探测函数区分 yes/no/nossh 三态,nossh 时打印"跳过核验"而非告警;端到端确认交给 xxx 身份跑的 30-verify-public.sh
2/dashboard/ 返回 200 却被判失败(grep -qi harvester 不命中)index.html 的 <title>Rancher——Harvester 复用 Rancher Dashboard 框架,品牌名由前端运行时从 settings 注入,HTML 里根本没有 "harvester" 字样L7 改判 SPA 骨架特征(id="app" + /dashboard/js/)并追加静态资源探测index.*.js 200 且 >10KB)——这才真正决定会不会白屏
3改了 dynamic.yaml 秒生效,改了 traefik.yaml 加 entryPoint 却毫无反应providers.file.watch: true 只热加载动态配置;entryPoints静态配置,必须重启进程20-traefik-route.sh apply 固定执行 systemctl restart traefik(重启前已做 PyYAML 双校验,避免坏配置把服务干挂)
4反代到 Harvester 报 502,Traefik 日志 x509: certificate signed by unknown authority后端 https://127.0.0.1:19048 递过来的是 Harvester 自签证书(CN=dynamic,签发者 dynamiclistener-caserversTransports.harvester-insecure.insecureSkipVerify: true 并在 service 里显式引用
5想直接 awk 往 dynamic.yaml 里塞 router,怕把 33 个路由搞坏该文件是全站入口,坏一行 = 所有站点 502先写 .new → PyYAML 校验语法 + 断言结构(新 router/service/transport 齐全、main-website 与 tcp 路由仍在)→ 通过才 mv 落盘;失败则原文件分毫未动
6WebSocket 探测总是"失败"(curl 退出码 28)101 Switching Protocols 之后连接会一直挂着,curl 只能等到 --max-time 超时判定不看退出码,只看响应头首行是否 101;超时属预期行为

7. 安全加固建议(可选,尚未执行)

实测发现:frps 的隧道端口是 [::]:19048 全网卡监听,且公网机没有 iptables/firewalld/ufw 过滤,因此

bash
curl -sk https://8.153.84.140:19048/ping   # → 200,证书 CN=dynamic(Harvester 自签)

可以绕过 Traefik 直连到 Harvester 登录页。这与该机既有做法一致(19047/19036 等隧道端口同样直连可达), 且 Harvester 自身要求登录认证,风险等级不变;但确实多了一个"证书告警 + 无域名"的入口。 如果希望只允许 https://ai-ear.cn:9443/ 一条路,在公网机上把隧道端口限成本地回环即可(Traefik 走 127.0.0.1:19048,不受影响):

bash
# 公网机 root:只放行 lo 接口访问 19048
iptables -I INPUT -p tcp --dport 19048 ! -i lo -j DROP
# 持久化(CentOS/RHEL 系)
service iptables save   # 或 iptables-save > /etc/sysconfig/iptables
# 验证:本机 curl -sk https://127.0.0.1:19048/ping → 200;外网 curl → 超时

⚠️ 这条规则只针对 19048。不要图省事对 19000-19100 整段 DROP——其它隧道(Rancher/Jenkins/Gitea 等) 目前有直接按 IP:端口 访问的用法,会一起被打断。 同理,frps 的全局 proxyBindAddr = "127.0.0.1" 虽然一行搞定,但会影响所有代理,不可用。

另外两条常规建议:① admin 口令属实验环境弱口令,公网可达后建议改强口令(改完用 HV_PASS='新口令' ./30-verify-public.sh 复验); ② 证书 notAfter=Oct 15 2026,到期前记得续(30-verify-public.sh 的 L5 会打印有效期)。


8. 相关文档与入口同步

本次一并更新了导航与文档索引(发布用仓库根目录的 ./publish.sh):

位置改动
../../README.md §2 文档导航、§6 访问与凭据新增本目录入口;补"公网访问地址"与 14/14 实测结论
worker/utilities/devops-tools/frp-port-forwarding/doc-index.html运维导航页 https://ai-ear.cn/doc/ 新增 Harvester HCI 卡片(徽章"超融合",指向 :9443
site/docs/.vitepress/config.mts顶部导航"工具"下拉新增 Harvester HCIhttps://ai-ear.cn:9443
bash
cd /home/xxx/docs/docs/ai-ear-website && ./publish.sh    # 构建 + 备份旧站 + 增量上传 + 校验 https://ai-ear.cn/

相关文档../README.md(一键部署总览)| ../../05-接入Rancher与内外网分离解析.md(另一种接入形态:被 Rancher 纳管)| worker/utilities/traefik/README.md(公网 Traefik 网关全貌)| worker/utilities/devops-tools/frp-port-forwarding/README.md(frp 端口转发总表)