Compose 进阶:networks、depends_on、healthcheck 玩转编排

Compose 入门之后,下一步是把编排写扎实。小够讲透自定义网络、服务依赖的正确姿势、健康检查、env_file、profiles、多文件覆盖和 secrets,附一份生产可用的 compose 模板。

我是小够,只推够用的 VPS。

入门篇的 compose 文件能跑,但离"生产可用"还差几口气:服务隔离、数据库就绪后再起应用、密码别硬编码、多环境切换。今天把这几块补齐,最后给一份小够自己用的模板。

自定义网络:给服务分组

默认情况下,一个 compose 项目里的所有服务都在同一个网络,能互相 ping 通。应用一多,你可能想隔离:比如网站 A 的数据库,不想让网站 B 碰到。

services:
  web:
    image: nginx:alpine
    networks:
      - frontend
  db:
    image: mysql:8.0
    networks:
      - backend
  api:
    image: myapi:latest
    networks:
      - frontend
      - backend

networks:
  frontend:
  backend:

api 同时在两个网络,能跟 web 和 db 说话;web 和 db 互相隔离。应用不多时可以不用,但迟早用得上。

网络还有个实用选项 internal: true,表示这个网络不通外网。数据库放 internal 网络,连外网都出不去——零成本的安全提升。

自定义网络还能让跨项目的服务互联:两个 compose 项目默认网络隔离,但可以加入同一个外部网络:

networks:
  shared:
    external: true

先 docker network create shared 建好,两个项目的服务都加入,就能互相访问。适合"监控容器看到所有项目"这种场景。

depends_on 的正确姿势

depends_on 只保证启动顺序,不等服务就绪。“等数据库 ready 再起应用"靠 healthcheck:

services:
  db:
    image: mysql:8.0
    environment:
      MYSQL_ROOT_PASSWORD: 强密码
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-p强密码"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s

  wordpress:
    image: wordpress:6-php8.2-apache
    depends_on:
      db:
        condition: service_healthy

参数解释:检查命令 / 每 10 秒查一次 / 单次 5 秒超时 / 连失败 5 次判不健康 / 30 秒宽限期(1 核小机器把这个设大点,初始化慢)。

Redis 的健康检查一行就行:test: ["CMD", "redis-cli", "ping"]。

docker ps 的 STATUS 列会显示 (healthy),一眼看出谁有问题;配合 restart: unless-stopped,不健康会自动重启恢复。

敏感信息:env_file

密码别硬编码在 compose 文件里。建个 .env 文件(和 compose 文件同目录,加进 .gitignore):

MYSQL_ROOT_PASSWORD=强密码1
MYSQL_PASSWORD=强密码2

compose 文件里引用:

services:
  db:
    image: mysql:8.0
    env_file:
      - .env
    environment:
      MYSQL_DATABASE: wordpress

env_file 整文件导入,environment 单个写,混用时后者优先级更高。换机器部署,compose 文件直接拷,.env 单独传,权限设 600:

chmod 600 .env

profiles:一个文件,多种组合

services:
  web:
    image: nginx:alpine
    profiles: ["prod"]
  debug-tools:
    image: nicolaka/netshoot
    profiles: ["debug"]

默认只启动没有 profiles 的服务;docker compose --profile debug up -d 才把调试工具拉起来。测试、生产共用一份文件。netshoot 是网络调试瑞士军刀,排错时临时用,用完就停。

多文件覆盖:开发和生产分开

# docker-compose.yml(公共部分)
services:
  web:
    image: nginx:alpine

# docker-compose.override.yml(本地开发专用)
services:
  web:
    ports: ["8080:80"]

Compose 会自动合并 docker-compose.yml 和 docker-compose.override.yml。生产环境用 -f 指定文件,避开 override:

docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

多个 -f 按顺序合并,后面的覆盖前面的。开发、测试、生产各一个覆盖文件,公共部分只维护一份。

configs:只读配置的优雅挂法

services:
  web:
    image: nginx:alpine
    configs:
      - source: nginx-conf
        target: /etc/nginx/nginx.conf

configs:
  nginx-conf:
    file: ./nginx.conf

和绑定挂载效果类似,但语义更清晰:“这是配置,不是数据”。单机用绑定挂载也行,个人用户不用纠结。

资源限制:别让一个服务吃光机器

services:
  app:
    image: myapp
    deploy:
      resources:
        limits:
          cpus: "1.0"
          memory: 512M
        reservations:
          memory: 256M

limits 是上限,reservations 是预留。记住给数据库和应用都设上——MySQL 内存泄漏起来比应用还凶。

一份生产可用的模板

把上面全串起来。注意模板里每个服务都带了日志配置:

logging:
  driver: json-file
  options:
    max-size: "10m"
    max-file: "3"

长期跑的服务一定要配,不然磁盘迟早被日志撑满。

services:
  db:
    image: mysql:8.0
    restart: unless-stopped
    env_file: [.env]
    volumes:
      - db-data:/var/lib/mysql
    networks: [backend]
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      resources:
        limits:
          memory: 1G

  web:
    image: nginx:alpine
    restart: unless-stopped
    depends_on:
      db:
        condition: service_healthy
    ports: ["8080:80"]
    volumes:
      - ./html:/usr/share/nginx/html:ro
    networks: [frontend, backend]
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"
    deploy:
      resources:
        limits:
          memory: 256M

networks:
  frontend:
  backend:

volumes:
  db-data:

./html:/usr/share/nginx/html:ro 后面的 ro 是只读挂载——Nginx 只读网站文件不写,安全加分。

实战:五服务全家桶

看一个完整例子:反代 + 网站 + API + 数据库 + 缓存。

services:
  proxy:
    image: nginx:alpine
    restart: unless-stopped
    ports: ["80:80", "443:443"]
    volumes:
      - ./proxy.conf:/etc/nginx/conf.d/default.conf:ro
      - ./certs:/etc/nginx/certs:ro
    networks: [frontend]
    depends_on: [web, api]

  web:
    image: nginx:alpine
    restart: unless-stopped
    volumes:
      - ./html:/usr/share/nginx/html:ro
    networks: [frontend]
    logging:
      driver: json-file
      options: { max-size: "10m", max-file: "3" }

  api:
    image: myapi:1.2
    restart: unless-stopped
    env_file: [.env]
    depends_on:
      db: { condition: service_healthy }
      cache: { condition: service_healthy }
    networks: [frontend, backend]
    deploy:
      resources:
        limits: { memory: 512M }

  db:
    image: mysql:8.0
    restart: unless-stopped
    env_file: [.env]
    volumes: [db-data:/var/lib/mysql]
    networks: [backend]
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 30s
    deploy:
      resources:
        limits: { memory: 1G }

  cache:
    image: redis:7-alpine
    restart: unless-stopped
    networks: [backend]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    deploy:
      resources:
        limits: { memory: 256M }

networks:
  frontend:
  backend:
    internal: true

volumes:
  db-data:

这个拓扑里:proxy 是唯一对外入口;web 和 api 挂 frontend;db 和 cache 藏在 internal 的 backend 里,连外网都出不去;api 横跨两个网络当桥梁。网络分层 + 最小暴露,这就是生产编排的核心思想。

常见问题

Q:depends_on 加 condition 的写法,老版本 Compose 支持吗? A:需要 Compose v2.1+。认准用 docker compose(空格)就行,docker-compose(短横线那个 v1)不支持。

Q:healthcheck 一直 unhealthy,怎么查? A:docker inspect 容器名 | grep -A 10 Health 看最后一次检查的输出。常见原因是 test 命令写错,或 start_period 给太短、服务还没初始化完就被判失败。注意 MySQL 的 -p 后面直接跟密码,中间别加空格。

Q:networks 有必要每个项目都自定义吗? A:没必要。单个应用的默认网络就够了,等一台机器跑三四个项目、想隔离数据库时再用。

Q:这份模板能直接上生产吗? A:个人博客、小应用可以。真要上生产,再加备份、监控告警、HTTPS——模板只解决"跑得稳”,不是"万无一失"。.env 异地再存一份。

Q:profiles 和多文件覆盖用哪个? A:不冲突,可以一起用。profiles 管"同一文件里的可选服务",多文件管"不同环境的差异配置"。小项目两个都不用也行——YAGNI。