Files
MeshSate/docker-compose.yml
zhaolei 64b682319e fix: meshsafe-cron 定时任务自引入以来从未成功执行 — 三处缺陷
复现而非等日志:容器内直接跑 archive_old_tasks.py 即崩。

1. app/config.py 的 admin_password 是必填字段,cron 环境没有它 →
   get_settings() 在 import 阶段抛 ValidationError,归档与 VACUUM 全崩。
   证据:policy_versions 留 10 行(策略写的留 5 删 5)、scan_tasks_archive 0 行,
   而容器 Up 且 RestartCount=0,没有任何存活信号暴露。给占位 env,不把真实口令复制进 cron。
2. VACUUM 不能在事务块里跑(ActiveSqlTransaction),原代码用了 engine.begin(),
   改用 AUTOCOMMIT 连接;实测 6 张表 0.25s 完成。
3. ValidationError 把整个 settings dict 打进 stderr,被 run() 原样写进 docker 日志,
   含 DB 连接串与 master key 尾部 → 加 _redact() 落盘前抹掉,验证输出中不可见。

cron 不监听 8443,镜像层 HEALTHCHECK 恒为 unhealthy,调度器真挂掉时该信号失效,故关闭。
2026-11-02 20:26:08 +08:00

191 lines
7.6 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# =============================================================================
# MeshSafe 单机部署(内网光闸摆渡场景)
# =============================================================================
#
# 启动顺序:
# 1) 准备 .env(cp .env.example .env 并填 MESHSAFE_MASTER_KEY)
# 2) 准备数据目录(mkdir -p data/{postgres,redis,clamav,logs,temp})
# 3) docker compose up -d postgres redis # 先起基础设施
# 4) docker compose up -d clamav # ClamAV(如首次启动尝试 freshclam 拉签名库)
# 5) bash scripts/import_clamav_db.sh clamav-db-*.tar.gz # 光闸摆渡导入签名库(可选)
# 6) docker compose up -d --build api # 起主服务
# 7) docker compose exec api python scripts/init_db.py # 建表 + admin + 初始策略
# 8) docker compose up -d nginx # 最后起 Nginx
#
# 外部访问:http://<内网服务器IP>:44380/console/login
# =============================================================================
services:
# ---------- PostgreSQL ----------
postgres:
image: postgres:16-alpine
container_name: meshsafe-postgres
restart: unless-stopped
networks: [meshsafe_net]
environment:
- TZ=Asia/Shanghai
- POSTGRES_DB=meshsafe
- POSTGRES_USER=meshsafe
- POSTGRES_PASSWORD=${MESHSAFE_DB_PASSWORD:-meshsafe}
volumes:
- ./data/postgres:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U meshsafe -d meshsafe"]
interval: 10s
timeout: 3s
retries: 5
# ---------- Redis ----------
redis:
image: redis:7-alpine
container_name: meshsafe-redis
restart: unless-stopped
networks: [meshsafe_net]
environment:
- TZ=Asia/Shanghai
command: ["redis-server", "--maxmemory", "128mb", "--maxmemory-policy", "allkeys-lru"]
volumes:
- ./data/redis:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 3s
retries: 5
# ---------- ClamAV ----------
# 生产:签名库通过光闸离线同步
# 外网 → bash scripts/sync_clamav_db.sh 生成 clamav-db-*.tar.gz
# 光闸摆渡 → bash scripts/import_clamav_db.sh clamav-db-*.tar.gz(放 ./data/clamav 下)
# 首次启动脚本(scripts/init_clamav.sh):如 ./data/clamav 为空则尝试 freshclam
clamav:
image: clamav/clamav:latest
container_name: meshsafe-clamav
restart: unless-stopped
networks: [meshsafe_net]
environment:
- TZ=Asia/Shanghai
volumes:
- ./data/clamav:/var/lib/clamav
- ./scripts/init_clamav.sh:/init_clamav.sh:ro
- ./data/clamav/clamd.conf:/etc/clamav/clamd.conf:ro
entrypoint: ["/init_clamav.sh"]
healthcheck:
test: ["CMD-SHELL", "echo PING | nc -w 2 localhost 3310 || exit 1"]
interval: 60s
timeout: 5s
start_period: 180s
retries: 5
# ---------- API 主服务 ----------
api:
build:
context: .
dockerfile: Dockerfile
image: meshsafe:latest
container_name: meshsafe-api
restart: unless-stopped
networks: [meshsafe_net]
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
clamav:
condition: service_started
environment:
- TZ=Asia/Shanghai
- MESHSAFE_ENV=prod
- MESHSAFE_HOST=0.0.0.0
- MESHSAFE_PORT=8443
- MESHSAFE_LOG_LEVEL=INFO
- MESHSAFE_DB_URL=postgresql+psycopg2://meshsafe:${MESHSAFE_DB_PASSWORD:-meshsafe}@postgres:5432/meshsafe
- MESHSAFE_MASTER_KEY=${MESHSAFE_MASTER_KEY:?MASTER_KEY_REQUIRED}
- MESHSAFE_ADMIN_PASSWORD=${MESHSAFE_ADMIN_PASSWORD:-admin123}
- MESHSAFE_POLICY_FILE=/app/config/policy.yaml
- MESHSAFE_CLAMAV_HOST=clamav
# API 调用方白名单:client_id:client_secret,client_id2:client_secret2
# ⚠️ 生产环境必须替换为真实密钥,切勿把生产密钥提交到仓库
- MESHSAFE_API_CLIENTS=${MESHSAFE_API_CLIENTS:-test-client:test-secret}
- MESHSAFE_CLAMAV_PORT=3310
- MESHSAFE_REDIS_URL=redis://redis:6379/0
- MESHSAFE_WORKERS=2
- MESHSAFE_RULES_DIR=/app/rules
# uvicorn / DB 连接池(并发优化)
- UVICORN_WORKERS=${UVICORN_WORKERS:-8}
- DB_POOL_SIZE=${DB_POOL_SIZE:-30}
- DB_MAX_OVERFLOW=${DB_MAX_OVERFLOW:-50}
volumes:
- ./data/logs:/var/log/meshsafe
- ./data/temp:/tmp/meshsafe
- ./rules:/app/rules:ro
- ./config/policy.yaml:/app/config/policy.yaml:ro
- ./config/diagnostics.yaml:/app/config/diagnostics.yaml:ro
- ./data/clamav:/var/lib/clamav # 共享签名库目录(api 可写入上传的 .cvd 文件,clamd 自动热加载)
- /var/run/docker.sock:/var/run/docker.sock:ro # 诊断套件只读访问 Docker(可选;没有也能降级)
- ./data/diagnostics:/app/data/diagnostics # 诊断套件持久化(规则历史 + 日志快照)
- ./tests/samples:/app/tests/samples:ro # 测试靶场样本库(CSV 对齐,可读扫描)
group_add:
- "999" # docker.sock 的 GID,让 api 容器能读
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8443/api/v1/health', timeout=3).read()"]
interval: 30s
timeout: 5s
start_period: 15s
retries: 3
# ---------- Cron (定时任务: DB 归档 + VACUUM) ----------
# 纯 Python 实现, 零额外依赖, 和 api 共享 meshsafe:latest 镜像
# 任务:
# 每天 03:00 archive_old_tasks.py --apply (清 90 天 detail / 365 天归档 / 幂等 TTL)
# 每周一 03:10 VACUUM ANALYZE (维护表统计信息)
cron:
image: meshsafe:latest
container_name: meshsafe-cron
restart: unless-stopped
networks: [meshsafe_net]
depends_on:
postgres:
condition: service_healthy
environment:
- TZ=Asia/Shanghai
- MESHSAFE_ENV=prod
- MESHSAFE_DB_URL=postgresql+psycopg2://meshsafe:${MESHSAFE_DB_PASSWORD:-meshsafe}@postgres:5432/meshsafe
- MESHSAFE_MASTER_KEY=${MESHSAFE_MASTER_KEY:?MASTER_KEY_REQUIRED}
# cron 不做控制台认证,但 Settings.admin_password 是必填字段,缺它 get_settings()
# 就抛 ValidationError,归档与 VACUUM 全崩。给占位值,不把真实口令复制进 cron
# 容器环境(docker inspect 可见)。
- MESHSAFE_ADMIN_PASSWORD=cron-unused-placeholder
volumes:
- ./data/logs:/var/log/meshsafe # 归档日志共享目录
- ./scripts:/app/scripts:ro # archive_old_tasks.py + cron_entrypoint.py
entrypoint: ["python3", "/app/scripts/cron_entrypoint.py"]
# 镜像层的 HEALTHCHECK 探测 localhost:8443(API 服务端口),cron 容器不监听该端口,
# 因此状态恒为 unhealthy —— 真正调度器挂掉时这个信号完全失效。关掉它,
# 存活与否由 restart: unless-stopped + 日志判断。
healthcheck:
disable: true
# ---------- Nginx(反向代理,内网 HTTP 明文)----------
# 外部端口 44380 → 内部 80 → api:8443
# 无 TLS,外层防护靠光闸
nginx:
image: nginx:1.27-alpine
container_name: meshsafe-nginx
restart: unless-stopped
networks: [meshsafe_net]
environment:
- TZ=Asia/Shanghai
depends_on:
api:
condition: service_healthy
ports:
- "0.0.0.0:44380:80"
volumes:
- ./deploy/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
# ---------- 独立 bridge 网络 ----------
# 容器间走服务名通信,IP 由 Docker 自动分配,无需固定网段
networks:
meshsafe_net:
driver: bridge