Skip to content

控制面整栈部署

控制面是一个 docker compose 整栈,编排文件是 deploy/docker/docker-compose.yml单个文件。生产即此形态。

本文所有 /control/v1/admin/* 调用都需携带 -H "Authorization: Bearer <admin token>",示例中为简洁省略。


栈内服务

服务说明发布
nginx收口 80/443,TLS 终止(Cloudflare Origin CA),回源内网各服务0.0.0.0[::] 的 80/443
control-serverFastAPI,/control/v1/*默认仅 127.0.0.1:8000
auth-serverASN 归属验证与 OAuth2/OIDC Provider,/api/v1/* 与根上的协议端点默认仅 127.0.0.1:8100
registry-server官方 registry 副本,/registry/v1/*不发布,仅容器网内
postgres 16三服务各自独立 database(同实例),持久卷 pg-data见下
redis 7控制面缓存(maxmemory + LRU,不持久化)。旁路降级——不可用时自动回落 DB不发布
cap自托管人机验证服务默认仅 127.0.0.1:3000
valkeyCap 的 KV 后端,兼作本栈通用 KV,持久卷 valkey-data不发布

全部容器同处一个显式命名的网络,彼此用服务名直连。

PostgreSQL 在生产上对公网发布了 51888 端口供远程管理:pg_hba 已收紧到公网仅管理账号可认证(scram),超级用户只在容器网与回环可达,云侧防火墙需另行放行。不需要远程管理的部署应删掉这段 ports


起栈

bash
cd deploy/docker
docker compose -p docker-compose up -d

-p docker-compose 必须显式给。 项目名决定容器名(docker-compose-<service>-1),不给会按目录名派生成 docker-*,把既有容器名全改一遍。

build context 是仓库根,.env 自动从 deploy/docker/ 读取。

启动顺序由 depends_on: service_healthy 保证:postgres 与 redis 先 healthy,应用容器再起。registry-server 是软依赖service_started)——名称富化会回落占位符,不能让它的故障连坐拖住控制面重启。

关停:down 保留数据卷,down -v 连数据卷一起删。


配置载体

生产以 TOML 为主载体,compose 的 environment 里只留容器网内的连接串。

文件挂载到内容
deploy/docker/control-server.toml/etc/dn42-control/控制面全部配置
deploy/docker/auth-server.toml/etc/dn42-auth/认证服务全部配置
deploy/docker/registry-server.toml/etc/dn42-registry/副本服务全部配置
deploy/docker/.envcompose 变量(数据库口令、绑定地址、Cap 管理密钥)

⚠️ environment 里加一项,就等于把 TOML 同名项永久压掉(env > TOML)。这是「改了配置文件没生效」的第一嫌疑。

TOML 文件含 token 与口令,宿主侧务必 chmod 600。全部键位见 配置参考

首次初始化(空卷)时 pg-init/10-create-auth-db.sh 会顺带建出 auth-server 与 registry-server 的独立 database。既有卷不会重跑,手动补:

bash
docker exec <pg> psql -U dn42 -d dn42_control -c "CREATE DATABASE dn42_auth"
docker exec <pg> psql -U dn42 -d dn42_control -c "CREATE DATABASE dn42_registry"

nginx 前置

配置在 deploy/docker/nginx/conf.d/,三份 conf 加两份 include 片段跟踪在仓库里——这是生产 nginx 配置的单一真相源。

文件承载
control-server.conf控制面(含 api.natlan.io)与 SNI 分流
auth-natlan.confauth.natlan.io:授权页静态资产加 auth-server 反代
challenges-natlan.confCap 实例,widget 端点公开、管理面 IP 收窄
00-admin-allowlist.conf全站唯一的运维 IP 白名单
cloudflare-realip.inc / security-headers.inc共用片段

要点:

  • 证书按 SNI 分流*.ngworks.orgnatlan.io / *.natlan.io 各一套 Cloudflare Origin CA 证书。
  • 80 与 443 都回源,不强跳 HTTPS:CDN 无论 Full 还是 Flexible 模式都不会 301 死循环。
  • agent WebSocket 透传/control/v1/agent/ws/…),proxy_read_timeout 放到 3600s。
  • CF-Connecting-IPX-Forwarded-For 是覆写而非透传。realip 只修正 nginx 自己的 $remote_addr,客户端自带的这两个头仍会原样转给上游,而应用侧的 client_ip() 读的就是它们。不覆写的话,直连源站的请求可以任意伪造来源 IP,把按 IP 的限速与审计一并架空。因此这里不能$proxy_add_x_forwarded_for——那是追加语义,会把客户端伪造的那一跳留在首项。

⚠️ upstream control_backend 是静态解析:容器名在 nginx 配置加载时解析一次并缓存,之后不再重解析。单独重建 control-server 会让它指着已失效的旧 IP,对外一律 502。 重建后必须 nginx -s reload;整栈 up -d 会连 nginx 一起重建,故不受影响。

auth-natlan.conf 里的块用 resolver 加变量形式,属于每请求重解析,不吃这个亏。

授权页静态资产

前端站点都在独立的前端仓库里,唯独 OAuth 授权页与 auth-server 同源挂在 auth.natlan.io 下——免 CORS、免第三方 Cookie、地址栏始终显示用户正在授权的那个域名。它的构建产物因此手工投放并跟踪在本仓库deploy/docker/nginx/www/auth/,由本栈的 nginx 直接发。

静态白名单是显式列举的/authorize/assets//favicon.svg 走静态,其余一律回后端。反过来做(默认静态、列举后端路由)会在后端新增端点时被 404 悄悄遮住。

换产物前必须验两件事:

  1. 产物风味。授权页依赖 index.html 里的 __CSP_NONCE__ 占位符加 nginx 的 sub_filter 注入。若拿到的是为其它托管形态构建的产物(删了占位符、改由别处注入),内联脚本会拿不到 nonce 被 CSP 拦掉——表现为验证码不出现而页面无任何报错
  2. CSP 与人机验证的兼容/authorize 块的 CSP 里 'unsafe-eval' 不是可选项:Cap 的 instrumentation 挑战脚本(服务端逐次下发的反自动化代码)用 evalFunction。只给 'wasm-unsafe-eval' 的话它会静默死在沙箱 iframe 里、二十秒后超时——违规事件发生在那个 opaque origin 的子文档里,主文档控制台一条都看不到。

改 CSP 前先读 deploy/docker/nginx/www/README.md


数据库与缓存

  • DN42_CONTROL_DATABASE_URL=postgresql+asyncpg://… 直连;缓存经 DN42_CONTROL_REDIS_URL(未配置或不可用即全程 no-op 回落 DB)。
  • ASN 等列用 BigInteger——DN42 的 4242420000+ 超 int32 上限。
  • 建表:由 [database] auto_migrate 分流——truealembic upgrade head(fail-fast),falsecreate_all(只建缺失的表)。⚠️ 现役生产置 false(compose 的 DN42_CONTROL_DB_AUTO_MIGRATE=false):库由 create_all 建起、未被 alembic 纳管,直接 upgrade 会从 base 重跑撞已存在的表。接管步骤与已知的 ORM 偏差见 数据库迁移
  • SQLite 升级到 PostgreSQLdeploy/docker/migrate_sqlite_to_postgres.py 按外键拓扑序整库拷贝(类型安全)并重置自增序列,源库只读。

迁移操作见 数据库迁移


生产必做项

说明
设 admin 凭据[server] admin_token。不设则管理面整体 403(fail-closed)
配管理员账号[auth] bootstrap_admin 加三项 cap_*,浏览器登录用。见 认证与账号
改掉默认口令POSTGRES_PASSWORD 与 enrollment token
保护密钥表node_wireguard_keys.private_key明文的全 fleet 隧道私钥。收紧库的访问与备份;可选配 [server] recovery_public_key 启用离线托管(现役生产未配)。见 密钥托管与恢复
收紧 CORS[server] cors_origins 只列前端站点的真实来源
网络层保护管理面前置 TLS 反代加防火墙收窄入站源。生产由 Cloudflare 代理到源站 nginx,TLS 两侧各终止一次(回源用 Origin CA,CDN 侧 SSL 模式取 Full (Strict))

全部配置项见 Control Server 配置


升级重建

控制面走整栈重建。步骤与验收见 控制面升级