外观
Control Server 配置
配置类:apps/control-server/app/core/config.py(ControlServerConfig / load())。加载纪律见 分层构造。
环境变量统一以 DN42_CONTROL_ 为前缀,唯一例外是 CAP_SECRET_KEY(变量名与部署侧约定同名,不带前缀)。
核心
| 环境变量 | TOML 键 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
DN42_CONTROL_DATABASE_URL | database.url | str | sqlite+aiosqlite:///<仓库根>/control.db | SQLAlchemy 异步 DSN。默认锚定在仓库根目录(避免随 cwd 漂移),适合本地与 CI。生产用 postgresql+asyncpg://user:pass@host:5432/db。Alembic 迁移读同一变量 |
DN42_CONTROL_DB_AUTO_MIGRATE | database.auto_migrate | bool | False | 启动时是否跑 alembic upgrade head(迁移失败即启动中止,fail-fast)。False 走 Base.metadata.create_all——只建缺失的表、绝不应用 ALTER。⚠️ 生产当前置 false:库由 create_all 建起、未被 alembic 纳管,切迁移链前必须先 alembic stamp,见 数据库迁移 |
DN42_CONTROL_ADMIN_TOKEN † | server.admin_token | str | None | None | 管理面 Bearer token(机器与自动化凭据,与账号会话令牌并行接受)。fail-closed:为 None 时管理面整体 403,账号登录同样锁定。生产必填 |
DN42_CONTROL_ENROLLMENT_TOKEN † | server.enrollment_token | str | None | None | 全局 bootstrap 注册 token,与 enrollment_tokens 表中按节点签发的 token 并行接受。fail-closed:为 None 时全局 token 关闭,只允许表内一次性 token 注册。本地照教程练手须显式设置 |
DN42_CONTROL_CORS_ORIGINS | server.cors_origins | 列表 | ("http://localhost:5173", "http://127.0.0.1:5173") | 浏览器管理面跨源直连的白名单。未设置返回默认(放行本地 dev server);显式设为空字符串表示关闭跨源。生产填控制台与首页的真实源 |
DN42_CONTROL_REDIS_URL † | redis.url | str | None | None | L5 派生缓存的 DSN(如 redis://redis:6379/1,db 1 = control-server)。缓存 desired-state(按 generation 键)、节点健康、路由聚合、ASN 名与 token 解析,写时主动失效。None 即不启用,全部读直接走 DB;不可用时同样自动回落——缓存是旁路,不影响正确性 |
DN42_CONTROL_KV_URL † | kv.url | str | None | None | L4 持久小状态的 DSN(如 redis://valkey:6379/1)。存 flap 热态基准、agent 心跳、登录限速、WS 订阅注册表、门户登录事务——都是唯一副本,故指向 noeviction + 落盘的 Valkey 而非会淘汰键的 Redis。None 时回落 redis.url(行为同旧版,但热态会随缓存淘汰或容器重建丢失)。角色与判据见 数据层参考 |
DN42_CONTROL_PARTITIONED_TIMESERIES | database.partitioned_timeseries | bool | false | 时序 rollup 表是否已转成按周分区。置真后按行裁剪的 DELETE 全部停掉,保留期改由分区维护循环 DROP 整个分区实现。⚠️ 必须在真的转换完之后才置真——未分区却置真等于旧行永不回收。见 时序表分区 |
DN42_CONTROL_AGENT_RELEASES_DIR | server.agent_releases_dir | Path | /data/agent-releases | 首方 wheel 发布库目录(agent 自更新分发源)。管理员上传构建产物到这里,agent 经鉴权通道拉取;默认落持久卷 |
DN42_CONTROL_RECOVERY_PUBLIC_KEY | server.recovery_public_key | str | None | None | 离线托管恢复公钥。取值既可是内联 PEM(以 -----BEGIN 开头),也可是 PEM 文件路径。节点用它封装 WG 私钥后上报,控制面只存密文、永不持有恢复私钥。None 表示未启用托管——节点仍上报公钥做一致性校验,但不产生密文。给的是文件路径且文件不存在时启动期抛 FileNotFoundError |
† 标记表示 token 类语义:显式设空 = 关闭。
健康判定
| 环境变量 | TOML 键 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
DN42_CONTROL_HEALTH_STALE_AFTER | health.stale_after_seconds | float 秒 | 900.0 | 在此时长内未上报的 ok 节点降为 stale |
DN42_CONTROL_HEALTH_DOWN_AFTER | health.down_after_seconds | float 秒 | 3600.0 | 超过此时长完全无上报判为 down,覆盖任何已知状态 |
两个阈值在读取时叠加,改阈值无需改库。判定顺序见 Control Server 内部。
管理员登录
| 环境变量 | TOML 键 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
DN42_CONTROL_CAP_API_ENDPOINT | auth.cap_api_endpoint | str | None | None | Cap 实例地址。生产走容器网内地址(http://cap:3000)避免公网回环 |
DN42_CONTROL_CAP_SITE_KEY | auth.cap_site_key | str | None | None | Cap site key(公开值) |
CAP_SECRET_KEY † | auth.cap_secret_key | str | None | None | Cap secret key(服务端专用) |
DN42_CONTROL_SESSION_TTL_SECONDS | auth.session_ttl_seconds | float 秒 | 86400.0 | 账号会话令牌 TTL(24h)。过期后任意接口 401,前端全局登出;改密时该用户全部存活会话立即吊销 |
DN42_CONTROL_BOOTSTRAP_ADMIN † | auth.bootstrap_admin | str | None | None | user:password——仅当 admin_users 表为空时创建首个管理员(幂等:已有账号则跳过,变量残留不会覆盖改过的密码)。密码以 argon2id 散列入库 |
人机验证 = 自托管 Cap,与 auth-server 共用同一实例同一 widget。三项 Cap 配置不齐时 CapVerifier 自身 fail-closed:账号登录一律 400,静态 admin token 不受影响。不配 bootstrap_admin 则不自动建账号(也可手动入库)。
registry 副本服务
| 环境变量 | TOML 键 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
DN42_CONTROL_REGISTRY_SERVICE_URL | registry.service_url | str | None | None | registry-server 地址(容器网内,如 http://registry-server:8200) |
DN42_CONTROL_REGISTRY_SERVICE_TOKEN † | registry.service_token | str | None | None | registry-server API 的 Bearer token |
两项任一不配 = 客户端 fail-closed:榜单名称富化回落占位符 AS<asn>,/ui/registry/* 显式查询 503。同步源 token 配在 registry-server 侧,控制面不再直连官方 registry。
认证服务接入
控制面作为 auth-server 会话 JWT 的消费方。
| 环境变量 | TOML 键 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
DN42_CONTROL_AUTH_SERVICE_BASE_URL | auth_service.base_url | str | None | None | auth-server 公开基址(如 https://auth.natlan.io,容器网内可用 http://auth-server:8100)。JWKS 端点由它推导为 <base_url>/.well-known/jwks.json,公钥缓存并按 TTL 刷新,未知 kid 强制刷新一次。fail-closed:未配置时 /autopeering/session 一律 503 |
DN42_CONTROL_AUTH_SERVICE_AUDIENCE | auth_service.audience | str | dn42-peering | 会话 JWT 的 aud 校验值,须与 auth-server 的 token.audience 一致 |
DN42_CONTROL_AUTH_SERVICE_ISSUER | auth_service.issuer | str | None | None | 会话 JWT 的 iss 校验值。不配则只锚 aud 不校验 iss;要锚死时配成 auth-server 的 token.issuer |
自动对等门户(OIDC 依赖方)
控制面自己对接 auth-server 的授权码流程,门户会话落在控制库。
| 环境变量 | TOML 键 | 类型 | 默认 | 说明 |
|---|---|---|---|---|
DN42_CONTROL_PORTAL_OIDC_ISSUER | portal.oidc_issuer | str | https://auth.natlan.io | 上游 issuer。discovery / JWKS / token 端点全由 <issuer>/.well-known/openid-configuration 推导,不单独配 |
DN42_CONTROL_PORTAL_OIDC_CLIENT_ID | portal.oidc_client_id | str | None | None | 门户在 auth-server 侧注册的机密客户端 id |
DN42_CONTROL_PORTAL_OIDC_CLIENT_SECRET † | portal.oidc_client_secret | str | None | None | 机密客户端凭据,只在服务端使用,永不下发浏览器 |
DN42_CONTROL_PORTAL_OIDC_REDIRECT_URI | portal.oidc_redirect_uri | str | http://127.0.0.1:8000/control/v1/autopeering/auth/callback | 回调地址,必须与 auth-server 侧登记值逐字节一致(精确匹配) |
DN42_CONTROL_PORTAL_OIDC_SCOPES | portal.oidc_scopes | 列表 | ("openid","profile","email","dn42") | 授权请求的 scope 集 |
DN42_CONTROL_PORTAL_FRONTEND_BASE_URL | portal.frontend_base_url | str | http://localhost:5173 | 门户前端基址:回调换完令牌后 302 到 {base}/auth/complete?handoff=…。生产指 https://peering.natlan.io |
DN42_CONTROL_PORTAL_SESSION_TTL_SECONDS | portal.session_ttl_seconds | float 秒 | 10368000.0(120 天) | 门户自有会话 TTL,与上游 1h access token 解耦(上游令牌由服务端静默 refresh)。对等是低频慢流程,频繁重登伤体验;令牌只解锁门户自身,长时效风险可接受。上游 refresh token 为 120 天滑动窗口,与本 TTL 对齐 |
client_id / client_secret 未配齐时门户登录端点 fail-closed 503,控制面其余功能不受影响。
flap 打分参数
[flap] 段仅文件可调,无对应环境变量。默认值即代码内置值;三个评估方(会话打分、前缀榜单、告警循环)装配时从同一份配置取值,自动同参——不同参会让判定口径分叉。
| TOML 键 | 默认 | 说明 |
|---|---|---|
flap.session_half_life_s | 1800 | 会话级(P0)衰减半衰期(秒)。约 5min 的快照节奏下,30min 半衰期意味着连续 4 份抖动快照才过线 |
flap.session_threshold | 3.0 | 会话级「抖动中」判定线,同时是告警开单线;消警线自动取一半做迟滞 |
flap.prefix_list_threshold | 20 | 前缀级(P1)榜单「抖动中」线,仅作可观测标记 |
flap.prefix_alert_threshold | 100 | 前缀级告警开单线(P2),与榜单线分层——低烈度背景抖动是 DN42 常态,按榜单线开单会告警疲劳;消警线自动取一半 |
flap.alert_loop_enabled | true | 告警评估循环开关。与主面共库的只读副本必须关掉:同一份分数两个评估者做边沿判定会竞态开出重复告警 |
刻意不开放的两个结构性常量:前缀级衰减半衰期(600s)必须与全 fleet agent 的
flapfeed_scoring.HALF_LIFE_S同参——控制面单方面改就是打分口径分叉;速率存档桶宽(60s)改了会毁历史 rollup 的语义。
算法见 flap 打分。
开发与已废弃键位
| 环境变量 | TOML 键 | 默认 | 说明 |
|---|---|---|---|
DN42_CONTROL_SEED_BOOTSTRAP_NODE | dev.seed_bootstrap_node | False | DB 为空时是否播种内置 demo 节点。默认关闭——生产启动即空库,节点数据由导入或 provision 流程写入 |
DN42_CONTROL_BOOTSTRAP_NODE_ID | dev.bootstrap_node_id | edge1 | demo 节点的 node_id,仅在上项开启时有意义 |
DN42_CONTROL_BOOTSTRAP_AGENT_TOKEN | dev.bootstrap_agent_token | mvp-agent-token | 与 demo 节点关联的初始 Bearer token,便于本地联调 |
DN42_CONTROL_MAIL_CF_ACCOUNT_ID / _MAIL_CF_API_TOKEN / _MAIL_FROM_ADDRESS / _MAIL_FROM_NAME | mail.* | — | 已废弃。挑战验证与邮件发送整体由 auth-server 承载,控制面不再装配任何邮件发送器。键位保留只为让既有部署不致启动报错;新部署应把这些配置挪到 auth-server.toml 的 [mail] 段 |
DN42_CONTROL_DEV_LOG_MAIL_SENDER | dev.log_mail_sender | False | 已废弃,同上 |
fail-closed 语义速查
| 变量 | 未设置 | 设为空字符串 |
|---|---|---|
DN42_CONTROL_ADMIN_TOKEN | None → 管理面全 403(含账号登录) | 同左 |
DN42_CONTROL_ENROLLMENT_TOKEN | None → 全局 token 关闭,仅表内 token 可注册 | 同左 |
CAP_SECRET_KEY | None → 账号登录全 400 | 同左 |
DN42_CONTROL_BOOTSTRAP_ADMIN | None → 不自动建账号 | 同左 |
DN42_CONTROL_REGISTRY_SERVICE_TOKEN | None → registry 查询 503、名称回落占位符 | 同左 |
DN42_CONTROL_RECOVERY_PUBLIC_KEY | None → 不托管 | 同左 |
DN42_CONTROL_PORTAL_OIDC_CLIENT_SECRET | None → 门户登录 503 | 同左 |
DN42_CONTROL_REDIS_URL | None → 不启用缓存 | 同左 |
DN42_CONTROL_CORS_ORIGINS | 默认本地白名单 | 空元组 → 关闭跨源 |
数据库 URL 与迁移
Alembic 迁移与运行时共享同一变量 DN42_CONTROL_DATABASE_URL:
migrations/env.py的_resolve_url()优先读该环境变量,缺失时退回alembic.ini的sqlalchemy.url(默认 SQLite,仅为alembic --help的无害占位)。- 迁移期把异步驱动替换为同步驱动:
+aiosqlite→ 去除、+asyncpg→+psycopg2、+asyncmy→+pymysql。 - 从仓库根运行
alembic upgrade head。
完整步骤见 数据库迁移。
最小配置示例
toml
# /etc/dn42-control/control-server.toml(chmod 600)
[server]
admin_token = "change-me-admin"
enrollment_token = "change-me"
cors_origins = ["https://console.example.dn42"]
[database]
url = "postgresql+asyncpg://dn42:secret@postgres:5432/dn42_control"
# 全新部署可置 true 走迁移链;从 create_all 库升级来的实例需先 alembic stamp
auto_migrate = false
[redis]
url = "redis://redis:6379/0"等价的纯环境变量形态:
dotenv
DN42_CONTROL_DATABASE_URL=postgresql+asyncpg://dn42:secret@postgres:5432/dn42_control
DN42_CONTROL_DB_AUTO_MIGRATE=0
DN42_CONTROL_ADMIN_TOKEN=change-me-admin
DN42_CONTROL_ENROLLMENT_TOKEN=change-me
DN42_CONTROL_CORS_ORIGINS=https://console.example.dn42
DN42_CONTROL_REDIS_URL=redis://redis:6379/0生产实配见 deploy/docker/control-server.toml,部署步骤见 控制面部署。