KubeBYON 控制台 / API 共用 443 HTTPS 部署记录

最后更新:2026-04-29

本文记录当前为了让:

  • 前端控制台走 https://<DOMAIN>:443
  • 后端 API 也走 https://<DOMAIN>:443/api/...
  • 同时不破坏 HAProxy 已经承接的 vCluster 控制面 443

而采用的一套实现方式。

适用前提:

  • KubeBYON 后端通过 deployments/single-node/docker-compose 部署
  • vCluster 稳定公网入口已经使用 HAProxy + TLS SNI
  • Web Console 通过仓库内的 web/Dockerfile 构建为静态站点镜像

补充说明:这份文档描述的是 HAProxy + Caddy 的平台站点方案
如果你当前做的是 Helm in-cluster + Gateway API Phase 3(平台 hostname 下 /api/healthz 到 API,/ 到 in-cluster web),请优先参考:

  • docs/runbooks/deploy-gateway-api-public-access.md

1. 为什么不能直接让站点自己监听 443

当前单机部署里:

  • HAProxy 已经监听宿主机 443
  • 它负责把不同 control_plane_host 的 TLS 流量按 SNI 转发到对应 vCluster NodePort

如果再让前端站点或普通反向代理直接占用宿主机 443,会立刻和 HAProxy 冲突。

因此最终做法不是“替换掉 HAProxy”,而是:

  1. 保留 HAProxy 继续占用外部 443
  2. 给 HAProxy 增加一条“静态 TLS SNI 直通规则”
  3. 当 SNI 命中平台主站域名时,转发给本机另一个 TLS 终结器(这里用 Caddy)
  4. Caddy 再把:
    • /api/*/healthz 转给 127.0.0.1:8080
    • 其余页面请求转给 127.0.0.1:3000

2. 最终拓扑

浏览器
  -> https://kubebyon.example.com:443
  -> HAProxy:443
     -> 若 SNI = kubebyon.example.com
        -> 127.0.0.1:8443 (Caddy)
           -> /api/*, /healthz -> 127.0.0.1:8080 (kubebyon-api)
           -> 其他路径          -> 127.0.0.1:3000 (Next.js web)
     -> 否则
        -> 继续按既有规则转发到各 vCluster NodePort backend

这里的关键点是:

  • 对外仍然只有一个 443
  • HAProxy 仍是最外层 TCP 入口
  • 平台站点只是其中一个特定 SNI host 的后端
  • vCluster 控制面公网入口逻辑不受影响

3. 为此做过的代码改动

3.1 给 HAProxy 同步器增加“静态 TLS SNI backend”能力

新增环境变量:

  • KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS
  • KUBEBYON_HAPROXY_EXTRA_TLS_BACKEND_ADDR

语义:

  • KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS:额外静态匹配的一组 SNI host
  • KUBEBYON_HAPROXY_EXTRA_TLS_BACKEND_ADDR:这些 host 命中后要转发到的固定后端地址,例如 127.0.0.1:8443

涉及文件:

  • internal/infra/config/config.go
  • cmd/kubebyon-api/main.go
  • internal/infra/vcluster/haproxy_sync.go
  • internal/infra/vcluster/haproxy_sync_test.go
  • deployments/single-node/docker-compose/docker-compose.yml

当前行为:

  • 正常的 cluster loadbalancer SNI 规则继续自动生成
  • 额外静态 host 会先生成一组固定 use_backend 规则
  • 如果配置了固定后端地址,则渲染一个静态 backend,例如:
use_backend kubebyon_static_tls_backend if { req.ssl_sni -i kubebyon.example.com }
server static_tls_1 127.0.0.1:8443 check

3.2 给 Compose 增加环境变量透传

deployments/single-node/docker-compose/docker-compose.yml 里补充了:

  • KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS
  • KUBEBYON_HAPROXY_EXTRA_TLS_BACKEND_ADDR

否则即使后端支持了,这两个变量也不会真正进入 kubebyon-api 容器。

3.3 为静态 TLS backend 增加测试

新增测试覆盖:

  • TestHAProxyConfigSyncerRenderConfigIncludesStaticTLSBackend

验证点:

  • SNI host 会被规范化
  • 渲染结果里会出现静态 req.ssl_sni 规则
  • backend 会正确指向 127.0.0.1:8443

4. 远程机器上的实际部署步骤

以下示例使用域名:

  • kubebyon.youguess.me

4.1 后端 .env 需要增加 / 修改

在:

  • /opt/kubebyon/deployments/single-node/docker-compose/.env

写入:

KUBEBYON_SESSION_COOKIE_SECURE=true
KUBEBYON_CORS_ALLOWED_ORIGINS=https://kubebyon.youguess.me
KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS=kubebyon.youguess.me
KUBEBYON_HAPROXY_EXTRA_TLS_BACKEND_ADDR=127.0.0.1:8443

说明:

  • KUBEBYON_SESSION_COOKIE_SECURE=true:站点改走 HTTPS 后,登录 cookie 必须标记为 Secure
  • KUBEBYON_CORS_ALLOWED_ORIGINS:如果浏览器中的 Web Console 与 API 存在同站跨 origin 访问,必须显式写入 Console origin;不要用 * 承载 cookie session。任意 cross-site 前端仍会被 Sec-Fetch-Site: cross-siteSameSite=Lax cookie 策略阻止,需改用 Bearer/API token 或重新设计 SameSite=None
  • KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS:告诉 HAProxy 这个主站域名也要在 443 上接入
  • KUBEBYON_HAPROXY_EXTRA_TLS_BACKEND_ADDR:命中后把 TLS 直通给本地 Caddy

完成后重建后端:

cd /opt/kubebyon/deployments/single-node/docker-compose
docker compose --env-file .env up -d --build

4.2 Web Console 运行在 3000

当前做法是让 Web Console 自己运行在宿主机 3000,再交给 Caddy 反代。

如果需要先在宿主机本地验证静态导出,可执行:

cd /opt/kubebyon/web
npm ci
npm run build

正式运行建议直接复用仓库内的 Dockerfile 构建静态站点镜像:

docker build -f web/Dockerfile -t kubebyon-web:local .
docker run --rm -p 3000:3000 \
  -e API_UPSTREAM=http://127.0.0.1:8080 \
  kubebyon-web:local

当前 web/Dockerfile 会:

  • npm ci
  • npm run build
  • 最后用 Nginx 提供静态站点,并暴露 3000

如果你使用 systemd 管理容器,可参考:

[Unit]
Description=KubeBYON Web Console
After=network.target docker.service
Requires=docker.service

[Service]
Type=simple
ExecStartPre=-/usr/bin/docker rm -f kubebyon-web
ExecStart=/usr/bin/docker run --name kubebyon-web --rm -p 3000:3000 -e API_UPSTREAM=http://127.0.0.1:8080 kubebyon-web:local
ExecStop=/usr/bin/docker stop kubebyon-web
Restart=always
RestartSec=5
User=root

[Install]
WantedBy=multi-user.target

说明:

  • 当前 Docker 镜像使用 Nginx 提供静态站点,并通过 API_UPSTREAM 配置 /api 上游
  • 浏览器侧通常不需要额外设置 NEXT_PUBLIC_KUBEBYON_API_BASE_URL
  • 因为站点最终通过同域 /api/... 访问后端即可
  • 只有当浏览器必须直连不同 origin 的 API 时,才建议显式设置 NEXT_PUBLIC_KUBEBYON_API_BASE_URL

4.3 安装 Caddy 作为站点 TLS 终结器

当前做法使用 Caddy:

  • 80:处理明文跳转与 ACME HTTP challenge
  • 8443:真正提供站点 TLS

注意:这里 Caddy 监听的是 8443,不是外部 443。
外部 443 仍然是 HAProxy;HAProxy 只是把特定 SNI 的 TLS 直通到 127.0.0.1:8443

可参考配置:

{
  auto_https disable_redirects
}

http://kubebyon.youguess.me {
  redir https://kubebyon.youguess.me{uri} 308
}

https://kubebyon.youguess.me:8443 {
  encode zstd gzip

  @api path /api/* /healthz
  reverse_proxy @api 127.0.0.1:8080

  reverse_proxy 127.0.0.1:3000
}

启用后检查:

systemctl enable --now caddy
systemctl status caddy --no-pager
ss -lnt | grep -E ':(80|8443)\s'

5. 管理后台里还要补的设置

站点已经切到 HTTPS 后,还需要登录管理员后台更新:

  • public_base_url = https://kubebyon.youguess.me
  • join_base_url = https://kubebyon.youguess.me

这样影响到:

  • 控制台内展示 / 生成的站点链接
  • Join 命令中的 base URL
  • 一些需要绝对 URL 的跳转 / 邮件 / 回调场景

6. 验证方式

6.1 检查 HTTP 是否跳转到 HTTPS

curl -I http://kubebyon.youguess.me

预期:

  • 308 跳转到 https://kubebyon.youguess.me/...

6.2 检查 HTTPS 站点是否可用

curl -I https://kubebyon.youguess.me/login
curl -I https://kubebyon.youguess.me/healthz
curl -I https://kubebyon.youguess.me/api/auth/providers
curl -i \
  -H 'Content-Type: application/json' \
  -d '{"email":"admin@kubebyon.local","password":"YOUR_PASSWORD"}' \
  https://kubebyon.youguess.me/api/auth/login

预期响应头中应出现:

  • Set-Cookie: ...; Secure; HttpOnly; SameSite=Lax

6.4 检查 HAProxy 最终渲染结果

sed -n '1,240p' deployments/single-node/docker-compose/runtime/haproxy/haproxy.cfg

预期至少能看到:

  • use_backend kubebyon_static_tls_backend if { req.ssl_sni -i kubebyon.youguess.me }
  • server static_tls_1 127.0.0.1:8443 check

7. 常见问题

7.1 为什么 Caddy 不是直接监听 443

因为宿主机外部 443 已经被 HAProxy 占用,用于承接:

  • 平台主站
  • 各个 vCluster 控制面域名

所以站点 TLS 必须挂在 HAProxy 后面,而不是和 HAProxy 抢端口。

7.2 这样会不会影响 vCluster 控制面流量

不会。

因为平台站点只占用:

  • KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS 指定的少数固定域名

其他 SNI host 仍按原来的 cluster 动态 backend 规则处理。

7.3 为什么后端 API 不是直接开放 https://IP:8080

因为目标是:

  • 前后端统一域名
  • 统一 443
  • 浏览器 Cookie / SameSite / Secure 语义更稳定
  • 避免用户记多个端口

7.4 如果以后主站域名不止一个怎么办

可以继续扩展:

  • KUBEBYON_HAPROXY_EXTRA_TLS_SNI_HOSTS=console.example.com,api.example.com,...

前提是:

  • 这些域名都要能由同一个本地 TLS 终结器处理
  • Caddy / Nginx 也要同步配置这些站点

8. 相关文档

  • deployments/single-node/docker-compose/README.md
  • docs/runbooks/deploy-kubebyon-backend-docker-compose.md
  • docs/runbooks/deploy-haproxy-public-access.md