Docker Compose多容器编排与Nginx集群实战

作者:没有四次元口袋的蓝胖

日期:2026-09-16

标签:Docker, Docker Compose, Nginx, 容器化


写在前面

这是一篇 Docker Compose 多容器编排 + Nginx 集群的实战记录。从 Dockerfile 编写、数据持久化,到 Nginx 反向代理负载均衡,再到 docker-compose.yml 一键编排,完整记录了微服务容器化部署的全过程。


一、项目背景

痛点:团队开发、测试、生产三套环境,Java 应用在不同机器上运行结果不一致(JDK 版本不同、依赖缺失、配置混乱),排查问题效率极低。

目标

  1. 统一环境 ------ "一次构建,到处运行"
  2. 简化部署 ------ 多服务(Web + MySQL + Redis + Nginx)一键编排
  3. 高可用 ------ Nginx 负载均衡 + 多应用实例

二、Dockerfile 编写与镜像构建

2.1 Spring Boot 应用镜像

dockerfile 复制代码
# 基础镜像:轻量级 JRE 环境
FROM openjdk:8-jre-alpine

# 设置工作目录
WORKDIR /app

# 指定非 root 用户运行(安全最佳实践)
RUN addgroup -S appgroup && adduser -S appuser -G appgroup

# 复制 jar 包
COPY target/app.jar /app/app.jar

# 通过环境变量实现多环境隔离(一次构建,多环境运行)
ENV SPRING_PROFILES_ACTIVE=prod

# 暴露端口
EXPOSE 8080

# 切换用户
USER appuser

# 启动命令
ENTRYPOINT ["java", "-jar", "/app/app.jar"]

关键点解析

指令 说明
FROM openjdk:8-jre-alpine 选择 alpine 版本,镜像体积更小(约 100MB vs 完整 JDK 的 500MB+)
SPRING_PROFILES_ACTIVE 通过环境变量切换配置,避免为不同环境打多个镜像
COPY --chown 指定文件属主为非 root 用户
USER appuser 容器不以 root 运行,遵循最小权限原则

💡 启动时指定环境

bash 复制代码
docker run -e SPRING_PROFILES_ACTIVE=dev myapp:latest    # 开发环境
docker run -e SPRING_PROFILES_ACTIVE=prod myapp:latest   # 生产环境

2.2 MySQL 自定义镜像

dockerfile 复制代码
FROM mysql:5.7

# 复制自定义配置文件
COPY my.cnf /etc/mysql/conf.d/custom.cnf

# 复制初始化 SQL(首次启动时自动执行)
COPY init.sql /docker-entrypoint-initdb.d/

# 设置字符集环境变量
ENV MYSQL_CHARSET=utf8mb4

自定义 my.cnf 关键配置

ini 复制代码
[mysqld]
# 字符集
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci

# 连接数
max_connections=500

# 慢查询日志
slow_query_log=1
slow_query_log_file=/var/log/mysql/slow.log
long_query_time=2

# InnoDB 缓冲池(根据服务器内存调整)
innodb_buffer_pool_size=512M

💡 通过自定义 my.cnf,把字符集、连接数、慢查询日志等配置固化到镜像中,避免每次部署后手动修改。

2.3 🎯 面试高频问题

Q:Dockerfile 中 COPY 和 ADD 有什么区别?

特性 COPY ADD
复制本地文件
自动解压 tar
支持远程 URL
推荐程度 推荐 非必要不用

最佳实践:优先用 COPY,语义更明确。ADD 的自动解压和远程下载功能可能带来不可预期的行为。

Q:为什么不用 root 用户运行容器?

安全。如果容器被攻破,root 用户意味着攻击者拥有容器内的最高权限,可能通过挂载卷等机制影响宿主机。非 root 用户限制了攻击面。


三、数据持久化方案

3.1 为什么需要数据持久化?

容器是"临时"的 ------ 容器删除后,内部数据全部丢失。数据库数据、应用日志、静态资源这些都不能丢。

3.2 Docker Volume 数据卷

bash 复制代码
# 创建命名数据卷
docker volume create mysql-data
docker volume create app-logs
docker volume create nginx-static

# 查看数据卷信息
docker volume inspect mysql-data

3.3 挂载方案

数据 容器内路径 宿主机挂载路径 说明
MySQL 数据 /var/lib/mysql /data/mysql/data 数据库文件,最重要
应用日志 /app/logs /data/app/logs 方便排查问题,不需要进容器看日志
Nginx 静态资源 /usr/share/nginx/html /data/nginx/static 前端文件,宿主机直接更新
Nginx 配置 /etc/nginx/conf.d /data/nginx/conf 方便修改配置,不用重建容器
bash 复制代码
docker run -d \
  -v mysql-data:/var/lib/mysql \
  -v /data/app/logs:/app/logs \
  myapp:latest

3.4 🎯 常见坑点

坑1:MySQL 容器挂载后启动失败

原因:宿主机挂载目录的权限与 MySQL 容器内 mysql 用户不匹配。

解决:

bash 复制代码
# 确保挂载目录的属主正确
chown -R 999:999 /data/mysql/data

坑2:容器重启数据丢了

原因:用了 bind mount(直接挂目录)但容器重建后目录变了,或者忘记挂载 -v 参数。

解决:使用命名 Volume(docker volume create),数据由 Docker 管理,不依赖具体容器。


四、Nginx 反向代理 + 负载均衡 + 动静分离

4.1 Nginx 配置文件

nginx 复制代码
# 定义后端应用集群
upstream app-cluster {
    # 轮询策略(默认)
    server 172.20.0.3:8080 weight=1;
    server 172.20.0.4:8080 weight=1;
    
    # 健康检查:连续 3 次失败标记为不可用
    # max_fails=3 fail_timeout=30s;
}

# 代理缓存配置
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=app_cache:10m max_size=100m inactive=60m;

server {
    listen 80;
    server_name example.com;

    # ===== 动静分离:静态资源直接由 Nginx 处理 =====
    location /static/ {
        alias /usr/share/nginx/html/static/;
        expires 30d;              # 浏览器缓存 30 天
        access_log off;           # 静态资源不记录日志
    }

    location ~* \.(css|js|png|jpg|gif|ico)$ {
        root /usr/share/nginx/html;
        expires 30d;
        access_log off;
    }

    # ===== 动态请求:反向代理到后端集群 =====
    location /api/ {
        proxy_pass http://app-cluster;
        
        # 传递真实客户端信息
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        
        # 代理缓存
        proxy_cache app_cache;
        proxy_cache_valid 200 10m;
        proxy_cache_valid 404 1m;
    }

    # ===== 默认页面 =====
    location / {
        root /usr/share/nginx/html;
        index index.html;
        try_files $uri $uri/ /index.html;
    }
}

4.2 核心配置解析

配置项 作用 面试常问
upstream 定义后端服务器集群 负载均衡策略有哪些?
weight 权重分配,数值越大分配越多请求 怎么实现不均匀负载?
proxy_pass 反向代理转发 proxy_pass 末尾加不加 / 的区别?
expires 30d 浏览器缓存静态资源 减少后端请求压力
proxy_cache Nginx 代理缓存 和浏览器缓存的区别?
try_files 前端路由支持(SPA 应用) Vue/React 路由刷新 404 怎么解决?

4.3 负载均衡策略

nginx 复制代码
upstream app-cluster {
    # 策略1:轮询(默认)
    server 172.20.0.3:8080;
    server 172.20.0.4:8080;

    # 策略2:权重
    server 172.20.0.3:8080 weight=3;  # 性能好的多分配
    server 172.20.0.4:8080 weight=1;

    # 策略3:ip_hash(同一用户始终访问同一节点,用于 Session 保持)
    # ip_hash;

    # 策略4:least_conn(最少连接数,空闲的节点多分配)
    # least_conn;
}

4.4 🎯 面试高频问题

Q:反向代理和正向代理有什么区别?

对比 正向代理 反向代理
代理对象 客户端 服务端
典型场景 VPN、科学上网 Nginx
客户端是否知道真实服务器 ❌ 不知道 ✅ 知道(但不直接访问)
一句话总结 代理客户端请求 代理服务端响应

Q:proxy_pass 末尾加不加 / 有什么区别?

nginx 复制代码
# 不加 /:/api/user → http://app-cluster/api/user(保留 /api 前缀)
location /api/ {
    proxy_pass http://app-cluster;
}

# 加 /:/api/user → http://app-cluster/user(去掉 /api 前缀)
location /api/ {
    proxy_pass http://app-cluster/;
}

五、Docker Compose 多服务编排

5.1 docker-compose.yml

yaml 复制代码
version: '3.8'

services:
  # ===== MySQL 数据库 =====
  mysql:
    image: mysql:5.7
    container_name: mysql
    environment:
      MYSQL_ROOT_PASSWORD: root123
      MYSQL_DATABASE: myapp
      MYSQL_CHARSET: utf8mb4
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/custom.cnf
      - ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "3306:3306"
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: always

  # ===== Redis 缓存 =====
  redis:
    image: redis:6-alpine
    container_name: redis
    command: redis-server --appendonly yes
    volumes:
      - redis-data:/data
    ports:
      - "6379:6379"
    networks:
      - app-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: always

  # ===== Spring Boot 应用(实例1)=====
  app1:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: app1
    environment:
      SPRING_PROFILES_ACTIVE: prod
    volumes:
      - app-logs:/app/logs
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    restart: always

  # ===== Spring Boot 应用(实例2)=====
  app2:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: app2
    environment:
      SPRING_PROFILES_ACTIVE: prod
    volumes:
      - app-logs:/app/logs
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network
    restart: always

  # ===== Nginx 反向代理 =====
  nginx:
    image: nginx:alpine
    container_name: nginx
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - nginx-static:/usr/share/nginx/html
      - nginx-cache:/var/cache/nginx
    depends_on:
      - app1
      - app2
    networks:
      - app-network
    restart: always

# ===== 数据卷 =====
volumes:
  mysql-data:
  redis-data:
  app-logs:
  nginx-static:
  nginx-cache:

# ===== 自定义网络 =====
networks:
  app-network:
    driver: bridge

5.2 关键配置解析

① depends_on + healthcheck:控制启动顺序

yaml 复制代码
depends_on:
  mysql:
    condition: service_healthy    # 等 MySQL 健康检查通过后才启动

如果只用 depends_on: mysql(不带 condition),只会等 MySQL 容器启动,不会等 MySQL 真正就绪。MySQL 启动到可连接通常需要几秒,这段时间内应用启动会报"连接数据库失败"。

② 自定义网络:容器间通信

yaml 复制代码
networks:
  app-network:
    driver: bridge

同一自定义网络下的容器可以通过 容器名 互相访问:

  • app1 连接 MySQL:jdbc:mysql://mysql:3306/myapp
  • app1 连接 Redis:redis://redis:6379

不需要写死 IP 地址!

③ restart: always

容器异常退出后自动重启,保证服务高可用。

5.3 常用编排命令

bash 复制代码
# 启动所有服务(后台运行)
docker-compose up -d

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs -f app1

# 重新构建并启动
docker-compose up -d --build

# 停止所有服务
docker-compose down

# 停止并删除数据卷(慎用!数据会丢)
docker-compose down -v

5.4 🎯 常见坑点

坑1:app1 和 app2 用了同一个镜像,但日志混在一起

解决:每个容器的日志挂载到不同目录,或在日志配置中加上容器标识。

坑2:Nginx 代理报 502 Bad Gateway

排查:

  1. docker-compose ps 检查 app1/app2 是否正常运行
  2. 检查 Nginx 配置的 upstream IP 是否正确(用容器 IP 还是容器名)
  3. 检查网络:Nginx 容器和 app 容器是否在同一网络

坑3:docker-compose up 后 MySQL 数据为空

init.sql 只在数据卷为空(首次启动)时执行。如果之前已经启动过,数据卷中有旧数据,init.sql 不会再次执行。需要 docker-compose down -v 清除数据卷后重新启动(注意:会丢数据!)。


六、整体架构图

复制代码
                    用户请求
                       │
                       ▼
               ┌──────────────┐
               │    Nginx     │  :80
               │  反向代理/负载均衡 │
               └──────┬───────┘
                      │
              ┌───────┴───────┐
              ▼               ▼
        ┌──────────┐    ┌──────────┐
        │  app1    │    │  app2    │  Spring Boot
        │  :8080   │    │  :8080   │
        └────┬─────┘    └────┬─────┘
             │               │
             └───────┬───────┘
                     │
              ┌──────┴──────┐
              ▼             ▼
        ┌──────────┐  ┌──────────┐
        │  MySQL   │  │  Redis   │
        │  :3306   │  │  :6379   │
        └──────────┘  └──────────┘

        所有容器运行在同一 Docker 自定义网络中
        通过容器名互相访问(无需写死 IP)

七、写在最后

学习建议

  1. Docker 不是"会 docker run"就够了,Dockerfile 优化(多阶段构建、镜像瘦身)、数据持久化、网络配置才是进阶重点
  2. Nginx 是运维必会组件,反向代理、负载均衡、动静分离、缓存策略这四项必须熟练配置
  3. docker-compose.yml 建议背下来,面试时可能让你现场写或改配置
  4. 实操建议:在自己的电脑上用 Docker Desktop 跑一遍这套编排,把 Nginx → app → MySQL 的链路跑通

🎯 面试高频三连问

Q1:Docker 的 Image 和 Container 有什么区别?

Image 是只读模板(类似类),Container 是 Image 的运行实例(类似对象)。一个 Image 可以启动多个 Container。

Q2:容器之间怎么通信?

三种方式:① 同一 Docker 网络下通过容器名访问(推荐);② 通过 IP 地址直接访问;③ 通过端口映射到宿主机,再通过宿主机 IP 访问。

Q3:docker-compose 的 depends_on 能保证服务就绪吗?

不能。depends_on 只保证容器启动顺序,不保证服务就绪。需要配合 healthcheck + condition: service_healthy 才能确保依赖服务真正可用。

相关推荐
懂软件的胡子个哥1 小时前
微信 API 消息回调怎么设计,才能避免丢消息和重复处理
运维·微信·架构·wechatapi·个人微信号二次开发
吉甫作诵1 小时前
Redis 常用命令大全:11 大类命令速查手册
运维·数据库·redis·缓存·nosql
师傅别念了3 小时前
Nginx 反向代理配置指南,看完这篇就够了
nginx
Acrellea3 小时前
告别粗放式能耗管理!安科瑞EIOT平台助力产业园区智慧低碳运营
运维·安全·能源
Databuff4 小时前
五款主流 SSH 免费工具介绍
运维·ssh·运维开发
菜鸟学编程o4 小时前
Linux常见指令
linux·运维·服务器
pt10435 小时前
AIOps机器学习——当警报阈值被调高之后
运维·人工智能·自动化
迪康coolmu5 小时前
企业文件外发管控落地实践 —— 基于迪康端点安全一体化管理系统
运维·网络·经验分享·安全
见闻小天地6 小时前
四方电气DL300变频器面向雕铣机多工况的技术参数与适配验证
运维·人工智能·业界资讯