Files
order_site/docker-compose.yml
yml2213 56ca479c6b chore(deploy): 基础镜像源改为可配置 BASE_REGISTRY,并提前补齐 FROM 基础镜像
## 修的是什么

线上部署卡死过一次:daocloud 加速源返回 502 Bad Gateway,整轮构建在 load metadata
阶段就失败,而服务器 daemon.json 里另外两个 registry-mirrors 完全帮不上忙。

原因是**显式写死的 registry 会绕过 daemon.json 的 registry-mirrors**:
Dockerfile 里 `FROM docker.m.daocloud.io/library/node:...` 属于显式指定 registry,
镜像列表对它就失效了,于是这个源成了构建的单点故障。

## 修法

1. 四个 Dockerfile(backend / frontend / backend-dev / dev-caddy)的 `FROM` 改用
   `${BASE_REGISTRY}`(registry 前缀语义),默认值 docker.m.daocloud.io 保持不变;
2. docker-compose.yml / docker-compose.dev.yml 的 `build.args` 接上
   `${BASE_REGISTRY:-docker.m.daocloud.io}`,并把 postgres / minio / node / caddy
   这几处 `image:` 也改成同一变量,整套编排只有一个换源开关;
3. 部署脚本:新增 resolve_base_registry()(读 .env 的 BASE_REGISTRY,缺省同 compose),
   --dry-run 的基础镜像 digest 预检沿用同一变量;
   新增 ensure_dockerfile_base_images(),在 pull_base_images 阶段把 Dockerfile 里
   `FROM` 用到的基础镜像提前补齐 —— 这类镜像不归 `docker compose pull` 管,
   本地缺失要拖到构建阶段才报错,且报错信息晦涩,看不出来是"源挂了"还是"代码写错了";
   现在缺失时明确提示并给出换源/离线搬运两条排障路径;
4. .env.server.example 增加 BASE_REGISTRY 说明(推荐多源容灾时设 docker.io,
   走 daemon 的 registry-mirrors 列表自动故障转移)。

## 验证

- docker compose config:默认值下所有镜像引用与改动前逐字一致(不会触发重新下载);
  BASE_REGISTRY=docker.1panel.live / docker.io 时全部引用正确替换;
- docker buildx build --call=check -f deploy/docker/backend.Dockerfile:Check complete, no warnings;
- deploy/ubuntu-deploy.sh bash -n 通过;
- 生产实测:预拉三个基础镜像后,buildkit 解析基础镜像 1s 命中本地镜像、不再访问远端。
2026-10-12 16:33:45 +08:00

178 lines
7.9 KiB
YAML
Raw Permalink 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
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: