Files
order_site/docker-compose.yml
T
yml2213 08318ef8d9 feat(deploy): 财务上线数据阶段编排与运维加固
- 新增 deploy/post-deploy.sh:回填 → 业务配置 → 同步 → 验收四阶段编排,支持 --only/--dry-run/窗口参数
- 新增 tools 镜像与 scripts/verify-finance-launch.sql(15 项上线验收)
- 验收口径:成本闸门二选一、要求已过账凭证、备用金支出关联、成本覆盖 WARN
- 加固:参数校验、工具镜像每次刷新、docker run 兜底超时、迁移前缀探测、验收失败退出码 2
- 工具镜像预建 /app/data,消除日志 EACCES
- 回填后与 restore-db 恢复后自动 VACUUM (ANALYZE),修复订单列表分页 COUNT 全表扫描(41 万单实测 27s → 0.17s)
- 文档:41 万单同步用 7 天窗 + 180s 超时;回填/恢复后刷新统计
2026-10-28 11:52:42 +08:00

212 lines
9.2 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.
x-json-log-rotation: &json-log-rotation
driver: json-file
options:
max-size: "20m"
max-file: "5"
services:
postgres:
image: ${BASE_REGISTRY:-docker.m.daocloud.io}/library/postgres:16-bookworm
# 开启 pg_stat_statements,便于定位生产环境累计最耗时的 SQL。
# 内存类参数默认按 4 核 8G 且与其他服务共用的服务器设置(见 .env.server.example)。
command:
[
"postgres",
"-c",
"shared_preload_libraries=pg_stat_statements",
"-c",
"pg_stat_statements.track=all",
"-c",
"log_min_duration_statement=${POSTGRES_LOG_MIN_DURATION_STATEMENT_MS:-500}",
"-c",
"shared_buffers=${POSTGRES_SHARED_BUFFERS:-384MB}",
"-c",
"effective_cache_size=${POSTGRES_EFFECTIVE_CACHE_SIZE:-4GB}",
"-c",
"work_mem=${POSTGRES_WORK_MEM:-16MB}",
"-c",
"maintenance_work_mem=${POSTGRES_MAINTENANCE_WORK_MEM:-256MB}",
# 并行度 1:生产实测单条大查询一旦开 2 个 parallel worker,就需要
# 「leader + 2 worker = 3 核」,而容器配额只有 2 核 → 必然撞配额被节流
# (累计 nr_throttled 94,197 次 / throttled_usec 9,337s)。重查询已优化到
# 10ms 量级,用不到并行。
"-c",
"max_parallel_workers_per_gather=${POSTGRES_MAX_PARALLEL_WORKERS_PER_GATHER:-1}",
# 检查点:默认 5 分钟一次 + max_wal_size 1GB,生产 43 天累计 21,778 次检查点,
# sar 里就是每 20–40 分钟一轮写突发(wtps 130、8MB/s)。放宽到 15 分钟 / 4GB。
"-c",
"checkpoint_timeout=${POSTGRES_CHECKPOINT_TIMEOUT:-900}",
"-c",
"max_wal_size=${POSTGRES_MAX_WAL_SIZE:-4GB}",
# 自动清理观测:默认 600000(10 分钟)等于什么都看不到。
# 这是 sighup 级参数,不能 ALTER DATABASE SET,只能走启动参数。
"-c",
"log_autovacuum_min_duration=${POSTGRES_LOG_AUTOVACUUM_MIN_DURATION_MS:-5000}",
]
restart: unless-stopped
# 让 PostgreSQL 优雅关闭有足够时间完成检查点;无限制时 OOM 可能拖垮整机。
mem_limit: ${POSTGRES_MEM_LIMIT:-1.5g}
# 3 核:这台机器 4 核,postgres 是最大单一消耗者(22.6h 烧 12.2 CPU·h、均 0.54 核),
# 却被 2 核配额卡住,累计 2.6 小时处于被节流状态。同机其它容器实测只用 0.2–0.4 核,
# 且几乎不节流,所以这里让出 1 核给数据库的突发。
cpus: ${POSTGRES_CPUS:-3}
shm_size: 512mb
stop_grace_period: 60s
logging: *json-log-rotation
environment:
TZ: ${TZ:-Asia/Shanghai}
POSTGRES_DB: ${POSTGRES_DB:-order_site}
POSTGRES_USER: ${POSTGRES_USER:-postgres}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres}
healthcheck:
test:
[
"CMD-SHELL",
"pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-order_site}",
]
interval: 5s
timeout: 5s
retries: 20
volumes:
- postgres_data:/var/lib/postgresql/data
backend:
build:
context: .
dockerfile: deploy/docker/backend.Dockerfile
args:
# 基础镜像源(registry 前缀,默认 docker.m.daocloud.io)。
# 显式指定的 registry 会绕过 daemon.json 的 registry-mirrors —— 加速源一抽风
# 整轮构建就在 load metadata 阶段失败,其它可用镜像源帮不上忙。
# 想要多源容灾就改成 docker.io:走 daemon 的 registry-mirrors 列表自动故障转移。
BASE_REGISTRY: ${BASE_REGISTRY:-docker.m.daocloud.io}
# 默认阿里云公共源;阿里云 ECS 内网可设 DEBIAN_MIRROR=mirrors.cloud.aliyuncs.com
DEBIAN_MIRROR: ${DEBIAN_MIRROR:-mirrors.aliyun.com}
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
restart: unless-stopped
# 30s 让后端在 SIGTERM 后完成优雅关闭(HTTP 连接收尾 + 数据库连接池释放)。
# 内存限额 768m → 1g:生产实测十天从重启后的 ~194MB 涨到 754MB(98%),
# 贴着 cgroup 上限时 V8 会持续 GC 抖动、接口整体变慢(且未必 OOM,所以日志里看不到崩溃)。
# 服务器 .env 里若仍写着 BACKEND_MEM_LIMIT=768m,会覆盖这里的默认值,需一并调整。
mem_limit: ${BACKEND_MEM_LIMIT:-1g}
cpus: ${BACKEND_CPUS:-1.5}
stop_grace_period: 30s
env_file:
- .env
depends_on:
postgres:
condition: service_healthy
healthcheck:
test:
["CMD-SHELL", "curl -fsS http://127.0.0.1:3000/health/ready || exit 1"]
interval: 5s
timeout: 5s
retries: 20
start_period: 20s
environment:
TZ: ${TZ:-Asia/Shanghai}
PORT: ${BACKEND_PORT:-3000}
# V8 老生代上限留出余量(限额 1g → 堆 768m):让 GC 在撞上 cgroup 墙之前主动回收,
# 避免"贴顶后才疯狂 GC"的抖动。native/external 内存与堆外缓冲还需要剩余那部分。
NODE_OPTIONS: ${BACKEND_NODE_OPTIONS:---max-old-space-size=768}
HTTP_TRUST_PROXY: ${HTTP_TRUST_PROXY:-1}
DATABASE_URL: ${DATABASE_URL:-postgres://postgres:postgres@postgres:5432/order_site}
DATABASE_SSL: ${DATABASE_SSL:-false}
DATABASE_MAX_CONNECTIONS: ${DATABASE_MAX_CONNECTIONS:-8}
DATABASE_IDLE_TIMEOUT_MS: ${DATABASE_IDLE_TIMEOUT_MS:-30000}
DATABASE_CONNECTION_TIMEOUT_MS: ${DATABASE_CONNECTION_TIMEOUT_MS:-5000}
DATABASE_STATEMENT_TIMEOUT_MS: ${DATABASE_STATEMENT_TIMEOUT_MS:-15000}
DATA_ROOT: /app/data
LOG_RETENTION_DAYS: ${LOG_RETENTION_DAYS:-30}
LOG_MAX_FILE_SIZE_MB: ${LOG_MAX_FILE_SIZE_MB:-20}
LOG_MAX_FILES: ${LOG_MAX_FILES:-5}
CLAIM_BASE_URL: ${CLAIM_BASE_URL:-http://localhost/#/claim}
# 生产图片存储只走阿里云 OSS;MinIO 仅保留在本地开发编排(docker-compose.dev.yml)。
STORAGE_MODE: ${STORAGE_MODE:-oss}
STORAGE_ENDPOINT: ${STORAGE_ENDPOINT:-http://minio:9000}
STORAGE_BUCKET: ${STORAGE_BUCKET:-order-site}
STORAGE_ACCESS_KEY_ID: ${STORAGE_ACCESS_KEY_ID:-minioadmin}
STORAGE_SECRET_ACCESS_KEY: ${STORAGE_SECRET_ACCESS_KEY:-minioadmin}
STORAGE_REGION: ${STORAGE_REGION:-us-east-1}
STORAGE_MAX_UPLOAD_SIZE_MB: ${STORAGE_MAX_UPLOAD_SIZE_MB:-10}
STORAGE_OSS_ENDPOINT: ${STORAGE_OSS_ENDPOINT:-}
STORAGE_OSS_BUCKET: ${STORAGE_OSS_BUCKET:-}
STORAGE_OSS_ACCESS_KEY_ID: ${STORAGE_OSS_ACCESS_KEY_ID:-}
STORAGE_OSS_SECRET_ACCESS_KEY: ${STORAGE_OSS_SECRET_ACCESS_KEY:-}
STORAGE_OSS_REGION: ${STORAGE_OSS_REGION:-oss-cn-hangzhou}
# ADMIN_SESSION_SECRET / ADMIN_DEFAULT_USERS_JSON 仅走 env_file。
# 勿在此处用 ${ADMIN_DEFAULT_USERS_JSON} 再注入:JSON 会被 YAML 解析破坏,导致生产校验失败。
KUASHOU_INDUSTRY_RATE_LIMIT_MAX: ${KUASHOU_INDUSTRY_RATE_LIMIT_MAX:-120}
logging: *json-log-rotation
volumes:
- ./apps/backend/data:/app/data
# 财务运维工具容器:跑 scripts/ 下的回填/同步脚本。
#
# 生产 backend 镜像 `npm prune --omit=dev` 且只 COPY dist,没有 tsx 与 scripts/,
# 所以回填脚本必须用这个镜像执行。默认不随 `up` 启动(profiles: tools)。
#
# 用法:
# docker compose --profile tools run --rm --no-deps tools npm run revenue:backfill -- --dryRun
# 或者直接用编排脚本:
# bash deploy/post-deploy.sh --dry-run
tools:
profiles: ["tools"]
image: ${TOOLS_IMAGE:-order-site/tools:latest}
build:
context: .
dockerfile: deploy/docker/tools.Dockerfile
args:
BASE_REGISTRY: ${BASE_REGISTRY:-docker.m.daocloud.io}
DEBIAN_MIRROR: ${DEBIAN_MIRROR:-mirrors.aliyun.com}
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
restart: "no"
env_file:
- .env
depends_on:
postgres:
condition: service_healthy
environment:
TZ: ${TZ:-Asia/Shanghai}
DATABASE_URL: ${DATABASE_URL:-postgres://postgres:postgres@postgres:5432/order_site}
DATABASE_SSL: ${DATABASE_SSL:-false}
# 回填按批提交,单条语句可能比 Web 请求慢;这里比后端默认值放宽。
DATABASE_STATEMENT_TIMEOUT_MS: ${DATABASE_STATEMENT_TIMEOUT_MS:-60000}
DATA_ROOT: /app/data
logging: *json-log-rotation
web:
build:
context: .
dockerfile: deploy/docker/frontend.Dockerfile
args:
APP_DOMAIN: ${APP_DOMAIN:-localhost}
BASE_REGISTRY: ${BASE_REGISTRY:-docker.m.daocloud.io}
VITE_WORKER_SITE_URL: ${WORKER_SITE_ADDR:-https://worker.khhao.com}
ALPINE_MIRROR: ${ALPINE_MIRROR:-mirrors.aliyun.com}
NPM_REGISTRY: ${NPM_REGISTRY:-https://registry.npmmirror.com}
restart: unless-stopped
mem_limit: ${WEB_MEM_LIMIT:-256m}
cpus: ${WEB_CPUS:-0.5}
stop_grace_period: 20s
logging: *json-log-rotation
depends_on:
backend:
condition: service_healthy
ports:
- "80:80"
- "443:443"
environment:
TZ: ${TZ:-Asia/Shanghai}
APP_DOMAIN: ${APP_DOMAIN:-localhost}
CADDY_SITE_ADDR: ${CADDY_SITE_ADDR:-http://localhost}
WORKER_SITE_ADDR: ${WORKER_SITE_ADDR:-https://worker.khhao.com}
volumes:
- caddy_data:/data
- caddy_config:/config
volumes:
postgres_data:
caddy_data:
caddy_config: