4. Docker Compose:多容器编排的艺术

说实话,刚开始用 Docker 的时候,我总觉得一个容器一个容器地启动也没什么。直到有一次,我在复现一个需要 MySQL + Redis + Nginx + 应用服务四个容器的漏洞环境……手动敲了二十多行 docker run 命令,还搞错了启动顺序,折腾了半小时。嗯,从那以后,Docker Compose 就成了我的标配。

4.1 为什么需要 Docker Compose?

你想想看,一个典型的漏洞环境,往往需要多个服务配合。比如一个 Web 漏洞靶场,可能需要:

  • 一个 PHP 容器跑应用
  • 一个 MySQL 容器存数据
  • 一个 Redis 容器做缓存
  • 一个 Nginx 容器做反向代理

手动一个个启动,不仅麻烦,还容易出错。Docker Compose 就是来解决这个问题的——用一个 YAML 文件,把所有的容器定义、网络配置、依赖关系一次性搞定。

核心思想:Docker Compose 通过 docker-compose.yml 文件,将多容器应用的定义、配置、启动、停止、网络连接等操作统一管理。说白了,就是「一键部署」。

4.2 docker-compose.yml 编写技巧

我个人习惯把 docker-compose.yml 分成三个核心部分来看:services(服务)、networks(网络)、volumes(数据卷)。下面我拿一个实际漏洞环境来举例。

4.2.1 基础结构

version: '3.8'

services:
  web:
    image: vulnweb:latest
    ports:
      - "8080:80"
    depends_on:
      - db
      - redis
    networks:
      - vuln-net

  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: vulndb
    volumes:
      - db-data:/var/lib/mysql
    networks:
      - vuln-net

  redis:
    image: redis:6-alpine
    networks:
      - vuln-net

volumes:
  db-data:

networks:
  vuln-net:
    driver: bridge

这里有几个关键点,我踩过坑,得跟你说说:

  • version 字段:建议用 3.8 或更高版本。我之前用 2.x 版本,结果有些新特性不支持,排查了半天。
  • services 命名:尽量用有意义的名称,比如 web、db、cache,别用 s1、s2 这种。你想想看,三个月后你自己都看不懂。
  • ports 映射:格式是 "宿主机端口:容器端口"。注意别跟本地其他服务冲突,我习惯用 8080、3307 这类非常用端口。

4.2.2 环境变量管理

在实际项目中,我从来不会把敏感信息直接写在 docker-compose.yml 里。为什么呢?因为代码可能会上传到 Git,密码就泄露了。

services:
  db:
    image: mysql:5.7
    env_file:
      - ./db.env
    # 或者用环境变量文件
    environment:
      - MYSQL_ROOT_PASSWORD=${DB_PASSWORD}

我的习惯:创建一个 .env 文件放在项目根目录,然后在 docker-compose.yml 里用 ${变量名} 引用。.env 文件记得加到 .gitignore 里。

4.3 服务依赖管理

这是最容易出问题的地方。我曾经在复现一个 CMS 漏洞时,Web 容器启动比 MySQL 快,结果应用连不上数据库,报了一堆连接错误。

4.3.1 depends_on 的正确用法

services:
  web:
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started

  db:
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

注意了,depends_on 只控制启动顺序,不等待服务就绪。什么意思呢?就是 Docker 会先启动 db 容器,但不等 MySQL 服务完全启动好,就直接启动 web 容器了。所以需要配合 healthcheck 来真正等待服务就绪。

避坑指南:我曾经在写漏洞环境时,只用了 depends_on 没加 healthcheck,结果每次都要手动重启 web 容器才能连上数据库。后来加了 healthcheck,问题就解决了。

4.3.2 依赖关系的实际案例

来看一个更复杂的例子,一个包含初始化数据导入的场景:

services:
  init-db:
    image: mysql:5.7
    volumes:
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    depends_on:
      db:
        condition: service_healthy
    command: >
      sh -c "echo 'Waiting for db...' && sleep 5 && exit 0"

  db:
    image: mysql:5.7
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10
    volumes:
      - db-data:/var/lib/mysql

  web:
    image: vulnweb:latest
    ports:
      - "8080:80"
    depends_on:
      init-db:
        condition: service_completed_successfully

这个结构里,init-db 服务会在 db 健康后执行初始化 SQL,然后退出。web 服务要等 init-db 成功完成后才启动。这样就能保证数据库里有数据了,应用再启动。

4.4 网络配置技巧

我个人习惯为每个漏洞环境创建独立的网络,避免容器间端口冲突。

networks:
  vuln-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16
          gateway: 172.20.0.1

为什么要指定子网?因为有时候多个环境同时运行,默认的 172.17.0.0/16 可能会冲突。指定一个不常用的网段,比如 172.20.0.0/16,就能避免这个问题。

4.5 常用命令速查

命令 用途 我的使用场景
docker-compose up -d 后台启动所有服务 日常启动漏洞环境
docker-compose down 停止并删除容器和网络 清理环境,释放资源
docker-compose logs -f 实时查看日志 调试启动失败的问题
docker-compose exec web bash 进入容器内部 手动执行漏洞利用代码
docker-compose restart web 重启单个服务 修改配置后快速生效

4.6 实战:一个完整的漏洞环境

最后,给你看一个我常用的漏洞环境模板。这是一个包含 SQL 注入靶场的完整配置:

version: '3.8'

services:
  web:
    build: ./web
    ports:
      - "8080:80"
    volumes:
      - ./web/src:/var/www/html
    depends_on:
      db:
        condition: service_healthy
    networks:
      - sqli-lab
    environment:
      - DB_HOST=db
      - DB_USER=root
      - DB_PASS=root123

  db:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: sqli_lab
    volumes:
      - ./db/init.sql:/docker-entrypoint-initdb.d/init.sql
      - db-data:/var/lib/mysql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 5s
      timeout: 3s
      retries: 10
    networks:
      - sqli-lab

  adminer:
    image: adminer:latest
    ports:
      - "8081:8080"
    networks:
      - sqli-lab
    depends_on:
      - db

volumes:
  db-data:

networks:
  sqli-lab:
    driver: bridge

这个配置里,我加了 adminer 这个数据库管理工具,方便在复现漏洞时查看数据库内容。你想想看,如果每次都要进容器查数据库,多麻烦?

小技巧:在开发调试阶段,可以加一个 adminer 或 phpMyAdmin 容器,方便查看数据。上线前再删掉就行。

嗯,Docker Compose 的内容差不多就这些了。记住一个原则:能用 Compose 解决的问题,就别手动敲命令。我在项目中见过太多因为手动操作导致的环境不一致问题,用 Compose 统一管理,省心又省力。