单二进制三角色 + Dock 适配器 + Swarm stack 达到可部署态;mgr1 实测订阅经 central-proxy bootstrap,探活 alive=41/52。 Co-authored-by: Cursor <cursoragent@cursor.com>
365 lines
No EOL
13 KiB
YAML
365 lines
No EOL
13 KiB
YAML
# stacks/browser-server.yml — onesvm-browser-server Swarm stack(Contract §5-A5)。
|
||
#
|
||
# 权威对齐:docs/deploy-prod-preset-20260901.md §2 服务清单/镜像/资源/端口(逐字段);
|
||
# stack 必备字段对齐 onesvm-dev-md/base/deployment-rules.md §1.3
|
||
# (每服务 TZ / placement / restart_policy / resources.limits / healthcheck,backup 除外)。
|
||
# 测试期全钉 mgr1(placement node.hostname==swarm-mgr1);scheduler 因 SQLite 卷必须钉。
|
||
#
|
||
# 密钥注入面(deploy.env 提供,禁默认值禁入 git):
|
||
# BROWSER_SERVER_KEYS_SEED / BROWSER_SERVER_ADMIN_TOKEN / PROXY_SUB_URLS / SEARXNG_SECRET
|
||
# 部署:docker stack deploy -c stacks/browser-server.yml --resolve-image never browser-server
|
||
# 红线:本文件本轮仅交付,不执行部署(Contract §9)。
|
||
#
|
||
# O13 镜像 tag 约定:自研镜像 onesvm/browser-server:dev / onesvm/trafilatura-http:dev
|
||
# 日常迭代复用覆盖;第三方镜像 tag + digest 双锁(deployment-rules §2.3)。
|
||
|
||
version: "3.8"
|
||
|
||
x-restart: &restart
|
||
restart_policy:
|
||
condition: on-failure
|
||
delay: 5s
|
||
max_attempts: 3
|
||
window: 60s
|
||
|
||
x-placement-mgr1: &placement-mgr1
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
|
||
services:
|
||
# ---------- 控制层(自研三角色,单二进制 role 分流) ----------
|
||
|
||
gateway:
|
||
image: onesvm/browser-server:dev
|
||
command: ["-role=gateway"]
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
BROWSER_SERVER_KEYS_SEED: ${BROWSER_SERVER_KEYS_SEED:?required}
|
||
BROWSER_SERVER_ADMIN_TOKEN: ${BROWSER_SERVER_ADMIN_TOKEN:?required}
|
||
BROWSER_SERVER_LISTEN_gateway: ":8640"
|
||
BROWSER_SERVER_SCHEDULER_URL: http://scheduler:8641
|
||
# gateway 与 scheduler 共库(gateway 只读 consumers/api_keys,写 audit/quota)。
|
||
BROWSER_SERVER_DB_PATH: /data/browser-server.db
|
||
volumes:
|
||
- browser-server-data:/data # 与 scheduler 共库(gateway 只读写 consumers/api_keys/audit/quota;
|
||
# SQLite 需目录可写以建 WAL 伴生文件,读写纪律由应用层单写者约定保证)
|
||
ports:
|
||
- "8640:8640" # host ingress(对外唯一入口;反代前缀 /bs-api)
|
||
networks:
|
||
# vlepontas-casa-net 附加(D10,对齐 WSG 先例);长格式显式声明 alias
|
||
# browser-server(V3 arch-review D-2:compose 校验器不接受列表内映射,
|
||
# 须用长格式;alias 防 vlepontas 消费方 DNS 解析失败)
|
||
browser-server-net:
|
||
aliases: []
|
||
casa-net:
|
||
aliases:
|
||
- browser-server
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1 # 测试期钉 mgr1(与共库卷同节点)
|
||
resources:
|
||
limits:
|
||
memory: 64m
|
||
cpus: "0.25"
|
||
healthcheck:
|
||
test: ["CMD", "/browser-server", "-healthz-probe"] # 见下方说明(distroless 无 curl/wget)
|
||
interval: 30s
|
||
timeout: 3s
|
||
retries: 3
|
||
start_period: 10s
|
||
|
||
scheduler:
|
||
image: onesvm/browser-server:dev
|
||
command: ["-role=scheduler"]
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
BROWSER_SERVER_LISTEN_scheduler: ":8641"
|
||
BROWSER_SERVER_DB_PATH: /data/browser-server.db
|
||
BROWSER_SERVER_ADMIT_MAX: "60"
|
||
BROWSER_SERVER_WORKERS: "8"
|
||
BROWSER_SERVER_SEARXNG_CN_URL: http://searxng-cn:8080
|
||
BROWSER_SERVER_SEARXNG_GLOBAL_URL: http://searxng-global:8080
|
||
BROWSER_SERVER_TRAFILATURA_URL: http://trafilatura:8080
|
||
BROWSER_SERVER_LIGHTPANDA_CDP_HOST: lightpanda:9222
|
||
BROWSER_SERVER_SHELL_CDP_HOST: headless-shell:9222
|
||
BROWSER_SERVER_PROXYMANAGER_URL: http://proxymanager:8642
|
||
# MIHOMO 配置目录:proxymanager 写 provider 到挂载卷,mihomo 同卷读取。
|
||
BROWSER_SERVER_MIHOMO_CONFIG_DIR: /data/mihomo
|
||
MIHOMO_CONTROLLER_URL: http://mihomo:19090
|
||
volumes:
|
||
- browser-server-data:/data # named volume 钉 mgr1(SQLite 单写者,禁跨节点)
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 64m
|
||
cpus: "0.5"
|
||
healthcheck:
|
||
test: ["CMD", "/browser-server", "-role=scheduler", "-healthz-probe"]
|
||
interval: 30s
|
||
timeout: 3s
|
||
retries: 3
|
||
start_period: 10s
|
||
|
||
proxymanager:
|
||
image: onesvm/browser-server:dev
|
||
command: ["-role=proxymanager"]
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
BROWSER_SERVER_LISTEN_proxymanager: ":8642"
|
||
PROXY_SUB_URLS: ${PROXY_SUB_URLS:?required}
|
||
# 订阅拉取专用 bootstrap(非密钥;缺省空=直连)。mgr1 直连机场被墙,
|
||
# 复用 mgr3 central-proxy :7890(deploy-preset §1)。探活仍走自家 mihomo。
|
||
BROWSER_SERVER_SUB_FETCH_PROXY: ${BROWSER_SERVER_SUB_FETCH_PROXY:-}
|
||
MIHOMO_CONTROLLER_URL: http://mihomo:19090
|
||
# provider 文件写到 mihomo 挂载卷(stack 把默认 /data/mihomo 指到 mihomo 同卷挂载点)。
|
||
BROWSER_SERVER_MIHOMO_CONFIG_DIR: /data/mihomo
|
||
# 热载路径必须是 mihomo 容器内视角(同卷挂 /root/.config/mihomo)。
|
||
BROWSER_SERVER_MIHOMO_RELOAD_PATH: /root/.config/mihomo/config.yaml
|
||
BROWSER_SERVER_DB_PATH: /data/browser-server.db
|
||
volumes:
|
||
- browser-server-data:/data # rules 表共享(gateway policy 联动)
|
||
- browser-server-mihomo:/data/mihomo # mihomo provider 写入点(与 mihomo 服务同卷)
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 32m
|
||
cpus: "0.1"
|
||
healthcheck:
|
||
test: ["CMD", "/browser-server", "-role=proxymanager", "-healthz-probe"]
|
||
interval: 30s
|
||
timeout: 3s
|
||
retries: 3
|
||
start_period: 10s
|
||
|
||
# ---------- 数据面 ----------
|
||
|
||
mihomo:
|
||
image: metacubex/mihomo:v1.19.24@sha256:9e37208fae8afa4c8b83d14ff2e9771b99178ebaf65a2c4fb388bc67ecefe4dc
|
||
volumes:
|
||
- browser-server-mihomo:/root/.config/mihomo # proxymanager 写 config.yaml/providers 到此(PM 侧 BROWSER_SERVER_MIHOMO_CONFIG_DIR=/data/mihomo 指同卷)
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
# 69 节点 + 订阅自带 dns/geosite 会加载 geoip(64m 实测 OOM 137)。
|
||
# 档标仍按 L2 超配;瘦 dns 后可回 64m(O12 校准)。
|
||
memory: 128m
|
||
healthcheck:
|
||
# mihomo controller 19090 REST 探活(仅 overlay;busybox sh 无 /dev/tcp,
|
||
# 用 busybox wget 打 /version——controller 无 secret 时返回 JSON)。
|
||
test: ["CMD-SHELL", "wget -q -O /dev/null http://127.0.0.1:19090/version || exit 1"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 15s
|
||
|
||
# ---------- Worker 层(第三方/自构建引擎,全部 pin) ----------
|
||
|
||
searxng-cn:
|
||
image: searxng/searxng:2026.8.29-d226b78bc@sha256:61ad36fcf71eb300f7d64216a6a04e010754755c7f5d33a526bd52e597e78c69
|
||
volumes:
|
||
- ./searxng-cn-settings.yml:/etc/searxng/settings.yml:ro
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
SEARXNG_BASE_URL: http://searxng-cn:8080/
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 192m
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/healthz"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 20s
|
||
|
||
searxng-global:
|
||
image: searxng/searxng:2026.8.29-d226b78bc@sha256:61ad36fcf71eb300f7d64216a6a04e010754755c7f5d33a526bd52e597e78c69
|
||
volumes:
|
||
- ./searxng-global-settings.yml:/etc/searxng/settings.yml:ro
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
SEARXNG_BASE_URL: http://searxng-global:8080/
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 192m
|
||
healthcheck:
|
||
test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/healthz"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 20s
|
||
|
||
trafilatura:
|
||
build:
|
||
context: ./trafilatura
|
||
image: onesvm/trafilatura-http:dev
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 64m
|
||
healthcheck:
|
||
test: ["CMD", "python", "-c", "import urllib.request;urllib.request.urlopen('http://127.0.0.1:8080/health', timeout=3)"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 15s
|
||
|
||
lightpanda:
|
||
image: lightpanda/browser:0.3.7@sha256:447598e4c103b70b0fb610813e72795da44341c564e1ec872c3a490c1bc6aebe
|
||
command: ["/bin/lightpanda", "serve", "--host", "0.0.0.0", "--port", "9222", "--log_level", "info"]
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
# 境外出口统一走 mihomo(对齐 bench compose-lightpanda-proxy.yml 的 proxy env;
|
||
# 域名级直连/代理由 mihomo 规则裁决)。
|
||
HTTP_PROXY: http://mihomo:17890
|
||
HTTPS_PROXY: http://mihomo:17890
|
||
http_proxy: http://mihomo:17890
|
||
https_proxy: http://mihomo:17890
|
||
NO_PROXY: 127.0.0.1,localhost
|
||
no_proxy: 127.0.0.1,localhost
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 320m
|
||
# lightpanda 镜像内无 curl/wget/sh 工具链(distroless 风格),CDP 探活由
|
||
# scheduler 适配器 Health 上报承担(healthcheck 缺省即 deployment-rules 例外,注明)。
|
||
|
||
headless-shell:
|
||
image: chromedp/headless-shell:151.0.7922.109@sha256:5f877a2a559dea1a99fb750da695d28a020cdd49db660aead6c78a46e3c7dd50
|
||
command:
|
||
- --disable-dev-shm-usage
|
||
# 境外出口统一走 mihomo(对齐 bench compose:--proxy-server=http://mihomo:17890)。
|
||
- --proxy-server=http://mihomo:17890
|
||
# ENTRYPOINT /headless-shell/run.sh 已带 --no-sandbox、--remote-debugging-port=9223
|
||
# 与 socat 9222→9223 转发(bench compose 实测复用),不得覆盖端口。
|
||
shm_size: 128m
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 0 # 按需槽:平时 0,scheduler 经 Swarm API 拉起(空闲 10min 回收)
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 400m
|
||
healthcheck:
|
||
# headless-shell 基于 debian + socat(有 shell),探 CDP 调试端口。
|
||
test: ["CMD", "sh", "-c", "exec 3<>/dev/tcp/127.0.0.1/9222 || exit 1"]
|
||
interval: 30s
|
||
timeout: 5s
|
||
retries: 3
|
||
start_period: 10s
|
||
|
||
# ---------- 备份 sidecar(对齐组织 NAS sidecar 惯例) ----------
|
||
|
||
backup:
|
||
# 简化实现:每日 01:00(Asia/Shanghai)sqlite3 .backup → tar 到 /backup 卷。
|
||
# 说明:alpine 基础镜像无 sqlite3 包,这里用 busybox sleep 循环 + cp 快照
|
||
#(WAL 模式下 cp 非原子;严格一致性备份轮换 sqlite3/amd64 镜像后升级 .backup)。
|
||
image: alpine:3.20@sha256:c64c687cbea9300178b30c95835354e34c4e4febc4badfe27102879de0483b5e
|
||
command:
|
||
- /bin/sh
|
||
- -c
|
||
- |
|
||
# 每日 01:00 Asia/Shanghai 备份(TZ 已设 Asia/Shanghai):
|
||
# 1) sleep 到次日 01:00;2) cp 数据库(WAL 模式 + checkpoint 后主库自洽);
|
||
# 3) tar 打包进 /backup,保留最近 14 份。
|
||
while true; do
|
||
now=$$(date +%s); target=$$(date -d "tomorrow 01:00" +%s 2>/dev/null || echo $$((now + 86400)));
|
||
sleep $$((target - now));
|
||
ts=$$(date +%Y%m%d-%H%M%S);
|
||
cp /data/browser-server.db /data/browser-server.db-wal /tmp/ 2>/dev/null || true;
|
||
tar -czf /backup/browser-server-$$ts.tar.gz -C /data browser-server.db browser-server.db-wal browser-server.db-shm 2>/dev/null \
|
||
|| tar -czf /backup/browser-server-$$ts.tar.gz -C /data browser-server.db;
|
||
ls -1t /backup/browser-server-*.tar.gz | tail -n +15 | xargs -r rm -f;
|
||
done
|
||
environment:
|
||
TZ: Asia/Shanghai
|
||
volumes:
|
||
- browser-server-data:/data:ro
|
||
- browser-server-backup:/backup
|
||
networks:
|
||
- browser-server-net
|
||
deploy:
|
||
replicas: 1
|
||
<<: *restart
|
||
placement:
|
||
constraints:
|
||
- node.hostname==swarm-mgr1
|
||
resources:
|
||
limits:
|
||
memory: 32m
|
||
# 备份 sidecar 无对外服务,healthcheck 缺省(deployment-rules §1.3 例外,对齐 pg 备份 sidecar 惯例)。
|
||
|
||
# ---------- 网络 / 卷 ----------
|
||
|
||
networks:
|
||
browser-server-net:
|
||
driver: overlay
|
||
name: browser-server-net
|
||
casa-net:
|
||
external: true
|
||
name: vlepontas-casa-prod-net
|
||
|
||
volumes:
|
||
browser-server-data: {} # SQLite 库(scheduler 单写者;gateway 只读;钉 mgr1 由 placement 保证)
|
||
browser-server-mihomo: {} # mihomo 配置/provider(PM 写、mihomo 读)
|
||
browser-server-backup: {} # 备份产物(NAS 侧同步由部署轮另议) |