自托管 zrok2 后客户端还在连 api-v1.zrok.io:默认值与 3022/1280 端口边界排障
项目:https://github.com/openziti/zrok | 文档:https://netfoundry.io/docs/zrok/
自托管 zrok2 出问题,多数不在 compose 起没起来,而在两处默认值:客户端仍然指向公共 api-v1.zrok.io,以及把 OpenZiti 的 3022/1280 当成普通 Web 端口塞进 Caddy/Nginx。下面按默认值、端口语义、客户端核对、边界、bootstrap 顺序、硬前提六段整理。
一、默认值清单:先对准这几项
| 变量 | 默认值或说明 |
|---|---|
ZROK2_API_ENDPOINT | 不设置时指向 api-v1.zrok.io(公共 SaaS) |
ZROK2_INSECURE_INTERFACE | 127.0.0.1,非 TLS 端口只绑本机 |
ZROK2_STORE_TYPE | postgres,单 controller 可改 sqlite3 |
ZROK2_DNS_ZONE | 必填,如 share.example.com,需通配 A 记录 |
ZROK2_ADMIN_TOKEN | 必填,≥32 字符 |
ZITI_PWD | 必填,OpenZiti controller admin 密码 |
ZROK2_CTRL_PORT / ZROK2_FRONTEND_PORT | 18080 / 8080 |
ZITI_CTRL_PORT / ZITI_ROUTER_PORT | 1280 / 3022 |
ZROK2_DB_PASSWORD / ZITI_USER | zrok2defaultpw / admin |
ZROK2_IMAGE / ZROK2_TAG | docker.io/openziti/zrok2 / latest,生产应固定版本 |
两个默认值最容易被忽略:ZROK2_API_ENDPOINT 为空即按公共 SaaS 走;ZROK2_INSECURE_INTERFACE 为 127.0.0.1,意味着 18080/8080 默认只监听本机。
二、端口语义与接线
SDK 客户端 ──TLS 直连:3022──> OpenZiti router(数据面,not proxied)
│
OpenZiti controller :1280(mTLS,不可代理)
│
zrok2 controller :18080 ─────┼──── Caddy :443(TLS 终止)
public frontend :8080 ──────┘ ├─ zrok2.share.example.com -> controller API
├─ *.share.example.com -> public frontend
└─ ziti.share.example.com -> OpenZiti 管理 API
依据官方 docker 部署文档的端口说明:
- 3022:OpenZiti router 数据面,always required,direct TLS,不能被代理。SDK 客户端必须从 Docker 网络外部直连。
- 1280:OpenZiti controller 控制面,router 与 controller 不同机时必需,走 mTLS,必须直接暴露,Caddy 无法代理该端口。
- 18080 / 8080:zrok2 controller 与 public frontend,仅本地测试,无 TLS。
- 443:Caddy 入口,终止 TLS 后分发到上面几个内部端点。
组件对应关系:OpenZiti controller + router 构成安全回传网络,zrok2 controller 管账号、token、share、Web console 与 REST API,public frontend 按 subdomain 把公网流量路由到具体 share;存储用 PostgreSQL 或 SQLite3,metrics/limits 可选 RabbitMQ + InfluxDB。Linux 包手工部署时这套语义不变,只是换成 ziti-controller、ziti-router、zrok2-controller、zrok2-frontend 等 systemd unit,etc/ctrl.yml 的 admin 凭据仍由 ZROK2_ADMIN_TOKEN 提供。
三、客户端仍连公共端点:怎么确认
zrok2 二进制默认面向 zrok.io 公共服务,通信 endpoint 是 api-v1.zrok.io。对接自托管实例必须显式改:
export ZROK2_API_ENDPOINT=http://your-server:18080
# 或写到配置里
zrok2 config set apiEndpoint https://zrok.example.com
也可以用 zrok2 enable ,token 由 controller 创建账号时给出:
docker compose exec zrok2-controller zrok2 admin create account you@example.com yourpassword
核对用:
zrok2 status --secrets
看输出里的两段:
Config: apiEndpoint https://zrok.quigley.com
Environment: Secret Token SuGzRPjVDIcF / Ziti Identity 2AS1WZ3Sz
Config 段的 apiEndpoint 就是当前生效的地址;Environment 段的 Secret Token 与 Ziti Identity 来自 controller 的 enable 流程,两者缺一说明客户端根本没接上自托管实例,此时 share 行为会退回公共 SaaS。
版本差异是这一节最常见的次生问题:zrok v0.x 的命令行前缀是 zrok,环境变量是 ZROK_*;实际 zrok2 用 zrok2 与 ZROK2_*。照 v0.x 教程抄命令会直接找不到命令,变量也不会生效。
自托管栈起来后可以先验 controller:
docker compose ps
curl -sf -H 'Accept: application/zrok.v1+json' http://127.0.0.1:18080/api/v2/versions
四、反代边界:3022/1280 不能套 Caddy
3022 和 1280 是 OpenZiti 自己的传输层,不是 HTTP 站点:3022 走 direct TLS,1280 走 mTLS,代理层无法完成它们要求的握手与身份校验,官方文档对 1280 的直接表述是 "Caddy cannot proxy this port"。因此:
- 3022 保持直连暴露,SDK 客户端从 Docker 网络外连进来,安全性由 OpenZiti 自身的 TLS 保证;默认它已绑
0.0.0.0。 - 1280 只在 router 与 controller 不同机时必须直接暴露,同样不进 Caddy。
- 对外由 Caddy 承担的只有 443:
zrok2.share.example.com到 controller API、*.share.example.com到 public frontend、ziti.share.example.com到 OpenZiti 管理 API。启用 Caddy overlay 需在.env设CADDY_DNS_PLUGIN=cloudflare、CADDY_DNS_PLUGIN_TOKEN=...,然后COMPOSE_FILE=compose.yml:compose.caddy.yml docker compose up -d;Caddy 首次启动会自编译 DNS 插件,耗时几分钟。启用后客户端地址同步改成export ZROK2_API_ENDPOINT=https://zrok2.share.example.com。
frontend 自身不提供 server TLS,放在反向代理后面时,代理必须转发浏览器原始的 Host 头。原因是 etc/http-frontend.yml 用 host_match 匹配 DNS zone:
v: 3
host_match: zrok.quigley.com
address: 0.0.0.0:8080
只有 Host 头落在该 zone 内的请求才由这个 frontend 处理,Host 被代理改写后就路由不到具体 share。创建 frontend 的命令形如 zrok2 admin create frontend sqJRAINSiB public http://{token}.zrok.quigley.com:8080,参数里的 zone 与 host_match 要对齐。
另有一个暴露面的取舍:要让 8080 从公网可达又不配 TLS,需把 ZROK2_INSECURE_INTERFACE 改成 0.0.0.0,官方原文明确提示这会把 18080 和 8080 明文发布到所有网卡,属于不安全做法。替代做法是在 compose.override.yml 里只给 frontend 单独加 ports: "0.0.0.0:8080:8080",避免 controller API 一起暴露。
五、bootstrap 失败的观察顺序
首次 docker compose up -d 由 zrok2-init 容器完成 OpenZiti overlay 与 zrok2 stack 的 bootstrap,约 1–2 分钟:
docker compose logs -f zrok2-init
zrok2-init 成功退出之前,controller 与 frontend 不会起来,这时看 docker compose ps 里后两者缺席是正常现象,不要去重启它们。init 退出后再依次确认:controller 监听 18080、frontend 监听 8080、3022/1280 在宿主机上可达、Caddy 拿到证书。
栈本身从 curl -sSfL https://get.openziti.io/zrok2-instance/fetch.bash | bash,或 git clone https://github.com/openziti/zrok 后进 docker/compose/zrok2-instance 获取,.env 从 .env.example 复制。存储按场景选:默认 postgres,单 controller 部署可以把 ZROK2_STORE_TYPE 设为 sqlite3,省掉一个数据库容器。
六、硬前提与扩容
自托管绕不开的前提是公网 IPv4 加一条通配 A 记录(如 *.share.example.com 指向 VPS IP),通配证书只能走 DNS challenge 签发。官方论坛对「ISP 不提供公网 IPv4、实例在 CGNAT 后面」的答复是没有静态已知公网 IP 就无法解决,只能把 OpenZiti overlay 与 zrok 放到公网 VPS,或直接用 SaaS。
compose 栈默认是单 frontend 实例。需要更高吞吐或可用性时,跑多个 frontend 实例放到 Caddy/Traefik 后面,详见 Scaling frontends 一节;同一套 controller 代码也支撑 zrok.io 公共服务本身。
七、项目信息
- 仓库:https://github.com/openziti/zrok
- 文档:https://netfoundry.io/docs/zrok/
- 许可证:Apache-2.0,Go 编写,单二进制
- 形态:SaaS(zrok.io / myzrok.io)与自托管共用同一套代码,当前版本线为 zrok2(v2.0.x),命令前缀
zrok2,环境变量前缀ZROK2_