Docker 与容器 列表全部文章
Docker 与容器
Docker 与容器第 2 / 5 篇

Docker 笔记(四)生产篇:Compose 编排、日志轮转与上线检查清单

单个容器好管,一堆容器就靠 Compose。但 Compose 里最容易踩的不是语法,是「启动顺序」和「日志把磁盘吃光」。这篇给一份能直接改用的 Java + MySQL + Redis 编排示例,讲清 depends_on 为什么只等启动不等就绪、日志驱动怎么配才不会撑爆根分区,以及上线前必须过一遍的检查清单。

更多

单个容器好管,一堆容器就靠 Compose。但 Compose 里最容易踩的坑不是语法,是两件事:启动顺序和日志把磁盘吃光。这篇给一份能直接改用的编排示例,讲清 depends_on 的真实语义,最后附一份上线前的检查清单。

一、Compose 文件骨架

services:      # 每个服务 = 一组相同配置的容器
  app: ...
  mysql: ...
networks:      # 自定义网络,服务间按名字互访
  app-net:
volumes:      # 命名卷,数据持久化
  mysqldata:

现在的 Compose v2 不需要写 version: 键了,写了反而会告警。命令也从 docker-compose 变成了 docker compose(带空格)。

二、一份能直接改用的编排

services:
  app:
    image: registry.example.com/team/myapp:1.0.0     # 固定 tag,绝不用 latest
    container_name: app
    restart: unless-stopped
    depends_on:
      mysql:
        condition: service_healthy                   # 等 MySQL 真正就绪,不是等启动
      redis:
        condition: service_healthy
    environment:
      SPRING_PROFILES_ACTIVE: prod
      SPRING_DATASOURCE_URL: jdbc:mysql://mysql:3306/app?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
      SPRING_REDIS_HOST: redis
      JAVA_OPTS: -XX:MaxRAMPercentage=75.0
    ports:
      - "127.0.0.1:8080:8080"                        # 只监听本机,外网走 Nginx
    volumes:
      - applogs:/var/log/app
    networks: [app-net]
    deploy:
      resources:
        limits:
          cpus: "2.0"
          memory: 2g
    healthcheck:
      test: ["CMD", "wget", "-qO-", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 3s
      retries: 3
      start_period: 60s                              # 给慢启动留宽限
    logging: &default-logging                        # 锚点,下面复用
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  mysql:
    image: mysql:8.0.36
    restart: unless-stopped
    environment:
      MYSQL_ROOT_PASSWORD_FILE: /run/secrets/mysql_root_pw
      MYSQL_DATABASE: app
      TZ: Asia/Shanghai
    volumes:
      - mysqldata:/var/lib/mysql
      - ./conf/my.cnf:/etc/mysql/conf.d/my.cnf:ro    # 配置用 bind mount,只读
    command: --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
    networks: [app-net]
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1"]
      interval: 10s
      timeout: 5s
      retries: 10
      start_period: 60s
    logging: *default-logging

  redis:
    image: redis:7.2-alpine
    restart: unless-stopped
    command: redis-server --appendonly yes --maxmemory 512mb --maxmemory-policy allkeys-lru
    volumes:
      - redisdata:/data
    networks: [app-net]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5
    logging: *default-logging

networks:
  app-net:
    driver: bridge

volumes:
  mysqldata:
  redisdata:
  applogs:

三、depends_on 只等"启动",不等"就绪"

这是 Compose 最大的误会。默认写法:

depends_on:
  - mysql

只保证 mysql 容器被创建并启动,不等它初始化完、不等它能接受连接。于是 Spring Boot 启动时数据库还没好,报 Communications link failure,然后应用自己退出。

三种解法,建议同时用:

  1. condition: service_healthy:依赖方必须配 healthcheck,Compose 会等它变成 healthy 再启动上游。
  2. 应用侧重试:数据库连接池配置重连(HikariCP 本身会重试),或加 spring.datasource.hikari.initialization-fail-timeout=-1 让启动不因数据库未就绪而失败。
  3. 重启策略兜底:restart: unless-stopped,启动失败会再试,配合上面的健康检查形成闭环。

四、环境变量与密钥

environment:
  JAVA_OPTS: -XX:MaxRAMPercentage=75.0
env_file:
  - .env.app                 # 或 env_file 从文件读
# .env 文件(跟 docker-compose.yml 同级,自动加载)
TAG=1.0.0
MYSQL_ROOT_PASSWORD=xxxxxx
image: myapp:${TAG:-1.0.0}   # 变量替换,带默认值

关于密钥的三条纪律:

  • 不要把密码写进 environment——docker inspect 能直接看到;
  • 不要写进 Dockerfile 的 ENV 或用 --build-arg 传——会留在 docker history 里;
  • 用 secrets(Swarm)、*_FILE 环境变量(MySQL/Redis 官方镜像都支持,如 MYSQL_ROOT_PASSWORD_FILE),或者外部配置中心。

五、日志:别让 json.log 撑爆根分区

Docker 默认用 json-file 驱动,默认不做任何轮转。一个跑半年的服务,日志能把 200G 磁盘吃到 0 字节,然后全站写不进去。

logging:
  driver: json-file
  options:
    max-size: "10m"      # 单文件上限
    max-file: "3"        # 保留几个

全局兜底配置(对所有容器生效),改 /etc/docker/daemon.json:

{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
sudo systemctl restart docker     # 改完要重启 daemon(会影响在跑的容器)

注意:daemon.json 的改动只对新创建的容器生效,已有的容器要重建。

驱动适用说明
json-file单机、默认必须配 max-size,否则就是定时炸弹
local单机、要省空间压缩格式,体积更小,但 docker logs 读的是二进制
journald已用 systemd 的机器交给 journalctl 统一管理
fluentd / loki要集中收集直接推到日志平台
none完全不要日志排查靠应用自己写文件

真实事故复盘见:根分区 100%:一个没配 max-size 的 json.log,把 200G 磁盘"吃"成了 0 字节。

六、常用命令

docker compose up -d                       # 后台启动
docker compose up -d --build               # 改了 Dockerfile 后重新构建再启
docker compose ps                          # 看各服务状态与健康检查
docker compose logs -f --tail 100 app      # 跟日志
docker compose restart app                 # 重启单个服务
docker compose down                        # 停并删除容器(保留卷)
docker compose down -v                     # 连卷一起删(危险)
docker compose config                      # 校验并展开最终配置,排查变量没生效很好用
docker compose exec app sh                 # 进容器
docker compose pull && docker compose up -d   # 更新镜像

七、上线检查清单

按重要性排序,每一项都对应一类真实事故:

  1. 镜像 tag 固定:myapp:1.0.0,不用 latest。否则回滚时你根本不知道要回到哪。
  2. 非 root 运行:Dockerfile 里 USER 10001。容器逃逸时 root 是最大助攻。
  3. 资源限制:mem_limit / cpus / pids_limit。不限就是让一个容器能拖垮整台机。
  4. 日志轮转:max-size + max-file,外加 daemon.json 全局兜底。
  5. 健康检查 + 合理的重启策略:unless-stopped 或 on-failure:5,别裸用 always。
  6. JVM 容器感知:MaxRAMPercentage 而非写死 -Xmx,并留出堆外空间。
  7. 端口只监听本机:127.0.0.1:8080:8080,公网入口统一走 Nginx。
  8. 数据用命名卷,配置用只读 bind mount,密钥走 secrets 或 *_FILE。
  9. 只读根文件系统:能做到的服务加 read_only: true + tmpfs: [/tmp]。
  10. 镜像漏洞扫描:trivy image myapp:1.0.0,基础镜像也要定期更新。
  11. 时区统一:容器 TZ、JVM user.timezone、JDBC serverTimezone 三处都要对齐。
  12. 备份与回滚演练:docker compose down 改 tag 再 up 能不能起来,平时就要试过。

八、出问题先看这几条

docker compose ps                                   # 谁 unhealthy 了
docker inspect app --format '{{.State.ExitCode}} {{.State.OOMKilled}}'
dmesg | grep -i -E 'killed|oom' | tail              # 内核 OOM 的直接证据
docker stats --no-stream                            # 谁在吃资源
docker compose logs --since 30m mysql | tail -50
docker system df -v                                 # 磁盘是不是被日志/镜像吃满了
docker events --since 30m --filter container=app    # 这半小时它经历了什么

排查思路的更完整版本,以及 37 个真实案例的索引,在生产问题排查手记总目录里。

备忘

  • Compose v2 命令是 docker compose,不需要 version: 键。
  • depends_on 默认只等启动;要等就绪必须配 healthcheck + condition: service_healthy。
  • 日志不配 max-size 迟早撑爆磁盘,daemon.json 全局兜底也别忘了。
  • 密码不进 environment、不进 Dockerfile,用 secrets 或 *_FILE。
  • 端口写 127.0.0.1:宿主机:容器,公网入口统一收口。
  • 上线清单里最容易漏的是:时区三处对齐、回滚演练、镜像扫描。
— 感谢读到这里 —发现错误,或想补充?告诉我 →
TURN KNOWLEDGE INTO SOMETHING REAL

有想法,也想把它做出来?

从技术方案到上线交付,聊聊你的下一个项目。

聊聊需求

一起聊聊

搜索文章

输入关键词,按标题和摘要即时筛选。

浏览分类文章归档

阅读设置

字号
行宽
专注模式

专注模式会隐藏页头与侧栏,只留正文。设置保存在当前浏览器。

稍后阅读

保存在当前浏览器,留给下一次安静阅读。

总浏览 -- 次 · 总访客 -- 人 · 今日浏览 -- 次 · 今日访客 -- 人