在 VPS 上用 Docker Compose 跑生产环境,不留后患

重启策略、日志轮转、资源限制、带自动 TLS 的反向代理,以及只需两行命令的数据卷备份。

C工CheapServ 工程团队作者 8 分钟阅读
悬浮平台上层层堆叠、发着光的容器
本页目录10
  1. 经得起时间考验的目录布局
  2. 重启后自动恢复
  3. 别让日志写满磁盘
  4. 别让一个失控进程拖垮整台机器
  5. 带自动 TLS 的反向代理
  6. 机密信息
  7. 更新不出意外
  8. 备份卷,而不是容器
  9. 第一时间发现故障
  10. 检查清单

在我们的 VPS 套餐上,大多数小型生产应用栈都是用 Docker Compose 运行的,这是一种很不错的做法。真正让人后悔的,往往是默认配置:重启后起不来的容器、到第三个月就把磁盘写满的日志、一个失控的进程拖垮整台机器,以及只备份了容器、却没备份数据的备份。每一个问题,都只需两行配置就能修好。下面把它们全部列出,按您会用到的先后顺序排列。

经得起时间考验的目录布局

每个应用栈在 /srv 下占一个目录,里面放 Compose 文件、存放机密信息的 .env,以及一个 data/ 目录,用来存放您想直接查看的 bind mount 数据。其余一切都用命名卷(named volume)。别搞花样:

/srv/app/
  compose.yaml
  .env               # chmod 600, never in git
  data/caddy/        # bind mounts you want to inspect
/var/lib/docker/volumes/   # named volumes, managed by Docker

Docker 一键应用会安装 Engine 27 和 Compose v2,并把守护进程的数据放在 NVMe 根盘上。如果您以后加挂了一块单独的存储卷,请把 /var/lib/docker 迁移过去,并通过 data-root(写在 daemon.json 里)指定新位置,而不要使用符号链接。

重启后自动恢复

内核更新会在夜间重启服务器。每个服务都必须声明,重启之后自己希望发生什么:

services:
  app:
    image: ghcr.io/example/app:1.8.2
    restart: unless-stopped
    env_file: .env
    depends_on:
      db:
        condition: service_healthy
  db:
    image: postgres:16.4
    restart: unless-stopped
    volumes: [pgdata:/var/lib/postgresql/data]
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      retries: 5
volumes:
  pgdata:

这段配置里有两个习惯,比看上去重要得多。把镜像标签固定到某个具体版本,绝不要用 latest:凌晨 4 点的一次重启拉取了 PostgreSQL 的新主版本,是最让人无从排查的那种故障。另外,带有 depends_on 条件的健康检查,可以避免数据库还在启动时,应用不断崩溃重启。

别让日志写满磁盘

默认情况下,Docker 会把每个容器写过的每一行日志,以 JSON 格式永久保存。一个日志量很大的应用,几个月就能写满 40 GB。只需对整个守护进程做一次配置,就能解决:

cat > /etc/docker/daemon.json <<'EOF'
{
  "log-driver": "json-file",
  "log-opts": { "max-size": "20m", "max-file": "5" },
  "default-address-pools": [{ "base": "172.30.0.0/16", "size": 24 }]
}
EOF
systemctl restart docker

已有的容器在重新创建之前仍沿用旧设置;执行 docker compose up -d --force-recreate 即可使其生效。其中地址池(address-pool)那一行是额外的好处,能避免日后 Compose 网络与 WireGuard 或办公网络的网段冲突。

别让一个失控进程拖垮整台机器

如果不设限制,某个容器的内存泄漏会让内核的 OOM killer 去挑一个“受害者”,而它挑中的往往是数据库。请给所有容器都设置内存上限,宽松一点,但必须有上限:

    deploy:
      resources:
        limits:
          memory: 1g
          cpus: "2.0"

Compose v2 在不使用 Swarm 的情况下也会遵循 deploy.resources.limits。设置各项限制时,要让它们的总和低于 VPS 内存减去留给宿主机的约 1 GB;在 8 GB 的 VPS-4 上,大约有 6.5 GB 可以分配。当某个容器触及上限时,只有它自己会重启,事件日志会告诉您是哪一个。

带自动 TLS 的反向代理

应用端口只绑定到 localhost,把 80 和 443 端口交给唯一的一个代理。容器中的 Caddy 会自行获取证书,并在配置变更时自动重新加载:

services:
  caddy:
    image: caddy:2.8
    restart: unless-stopped
    ports: ["80:80", "443:443", "443:443/udp"]
    volumes:
      - ./data/caddy/Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
  app:
    expose: ["3000"]      # reachable by caddy on the compose network, not published
volumes:
  caddy_data:

Caddyfile 每个站点只需一行:app.example.com { reverse_proxy app:3000 }。只发布代理的端口,还能绕开 Docker 在 ufw 上“打洞”这个众所周知的问题:除了 80 和 443,不会发布任何其他端口。

机密信息

把它们放进 .env 并执行 chmod 600,在 Compose 文件中用 ${DB_PASSWORD} 引用,并且不要把 .env 提交到 git,旁边放一份 .env.example。如果机密不止寥寥几项,那么以文件形式挂载的 Compose secrets: 要比环境变量更干净,因为环境变量会泄露到 docker inspect 的输出和崩溃报告中。

更新不出意外

cd /srv/app
docker compose pull            # fetches the new pinned tags you edited
docker compose up -d           # recreates only what changed
docker image prune -f          # frees the old layers

改标签,pull,up。在单台 VPS 上,类似 Watchtower 的自动更新工具很诱人,但一次破坏性变更也正是这样在午夜到来的;如果您非要用,请把版本固定到次版本号,这样它就只能应用补丁版本。

备份卷,而不是容器

容器是可丢弃的;卷才是数据。对于数据库,请用 dump 导出,而不是直接复制其底层的文件:

docker compose exec -T db pg_dump -U postgres -Fc app > /srv/backups/app-$(date +%F).dump

对于其他所有数据,可以用一个一次性容器给卷打 tar 包:docker run --rm -v app_uploads:/v -v /srv/backups:/b alpine tar czf /b/uploads-$(date +%F).tgz -C /v .。用 restic 把 /srv/backups 传出服务器,具体做法见《真正能恢复的备份》。大型更新之前先在控制面板里做一个快照,几秒钟就能满足“撤销”的需求。

第一时间发现故障

docker compose ps 可以查看健康状态;docker stats --no-stream 可以看到谁在吃内存。如果想要一个常驻的视图,可以使用 netdata 附加服务,它会自动发现容器,并为每个容器绘制 CPU、内存和重启次数的曲线。请针对重启设置告警:一个小时内重启了三次的容器,会赶在用户之前告诉您出了问题。

检查清单

  • 固定的镜像标签、restart: unless-stopped、健康检查,以及 depends_on 条件。
  • 作用于整个守护进程的日志轮转。
  • 每个服务都设置内存上限,且总和低于 VPS 的内存。
  • 由一个反向代理发布 80 和 443 端口;其余一切都留在内部网络中。
  • 机密信息放在权限为 600 的 .env 中,不进 git。
  • 数据库导出和卷归档每晚传送到异地,并且每月在别处做一次实际恢复。

按这种方式配置的 VPS-4,可以运行一套由五到八个服务组成的典型应用栈,一连数年都不需要任何人登录,这是对一台服务器的最高赞美。

C工
CheapServ 工程团队

负责构建开通流水线、控制面板和存储层的工程师。

一分钟内部署您的第一台服务器。

以 BTC、ETH、XMR 或 USDT 充值,$25 起。余额永不过期,未使用的部分可退款。

立即注册