Docker Compose:让多容器应用一键起飞 🚀

248 阅读8分钟

Docker Compose:让多容器应用一键起飞 🚀

"曾经我也是一个手动启动容器的少年,直到我的膝盖中了一箭。" —— 某位忘记 --link 参数的运维工程师

引言:容器化的烦恼与救赎

想象一下这样的场景:你开发了一个完美的Java应用,需要MySQL数据库、Redis缓存、RabbitMQ消息队列,可能还有个Nginx做反向代理。每天上班第一件事就是:

docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=secret mysql:8.0
docker run -d --name redis redis:alpine
docker run -d --name rabbitmq rabbitmq:management
docker run -d --name app --link mysql --link redis my-java-app

然后发现端口冲突了,网络配置错了,环境变量漏了... 一天的好心情就此终结!这就是Docker Compose存在的意义——用一杯咖啡的时间,解决你一天的容器管理烦恼

什么是Docker Compose?

官方定义

Docker Compose是用于定义和运行多容器Docker应用程序的工具。通过一个YAML文件配置所有服务,然后使用一条命令即可创建并启动所有服务。

通俗解释

"容器乐团指挥家" 👨‍🎤:

  • 你写乐谱(docker-compose.yml)
  • Compose是乐团指挥
  • 每个容器是乐手(服务)
  • 一声令下,所有乐手协调演奏(应用启动)

核心优势

  1. 一键启停docker-compose up / down
  2. 环境即代码:YAML文件版本化管理
  3. 服务编排:处理容器间依赖关系
  4. 配置共享:网络、卷、环境变量统一管理
  5. 开发生产一致性:消除"在我机器上是好的"问题

安装指南(含避坑提示)

# Linux/macOS
sudo curl -L "https://github.com/docker/compose/releases/download/v2.23.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose

# Windows (Docker Desktop已内置)

⚠️ 避坑提示

  1. 版本兼容性:Docker Compose版本需与Docker Engine匹配
  2. 文件权限:Linux系统注意/usr/local/bin权限
  3. 路径问题:Windows用户确保Docker安装路径在系统PATH中

验证安装:

docker-compose --version
# 输出:Docker Compose version v2.23.0

核心概念速成

概念说明示例
Service一个容器化的应用服务web, db, cache
Project一组关联服务的集合当前目录名
Volume持久化数据存储db-data
Network容器间通信网络app-network
Environment容器运行时环境变量SPRING_DATASOURCE_URL

完整Java项目实战:博客系统

项目结构

blog-system/
├── docker-compose.yml
├── backend/
│   ├── Dockerfile
│   ├── pom.xml
│   └── src/... (Spring Boot应用)
├── frontend/
│   └── ... (可选Vue/React前端)
└── init-scripts/
    └── init.sql (数据库初始化)

Spring Boot应用代码(精简版)

BlogPost.java

@Entity
public class BlogPost {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String title;
    private String content;
    private LocalDateTime createdAt;
    
    // getters/setters
}

PostController.java

@RestController
@RequestMapping("/api/posts")
public class PostController {
    
    @Autowired
    private PostRepository postRepository;
    
    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @GetMapping
    public List<BlogPost> getAllPosts() {
        // 使用Redis缓存
        ValueOperations<String, String> ops = redisTemplate.opsForValue();
        String cacheKey = "all_posts";
        
        String cached = ops.get(cacheKey);
        if (cached != null) {
            return objectMapper.readValue(cached, new TypeReference<>() {});
        }
        
        List<BlogPost> posts = postRepository.findAll();
        ops.set(cacheKey, objectMapper.writeValueAsString(posts), 30, TimeUnit.MINUTES);
        return posts;
    }
}

Dockerfile (backend/Dockerfile)

# 构建阶段
FROM maven:3.8.6-eclipse-temurin-17 AS builder
WORKDIR /app
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 运行阶段
FROM eclipse-temurin:17-jre-alpine
WORKDIR /app
COPY --from=builder /app/target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]

docker-compose.yml (精华所在)

version: '3.9'

services:
  # Spring Boot应用服务
  backend:
    build: ./backend
    ports:
      - "8080:8080"
    environment:
      SPRING_DATASOURCE_URL: jdbc:mysql://db:3306/blog?useSSL=false
      SPRING_DATASOURCE_USERNAME: root
      SPRING_DATASOURCE_PASSWORD: rootpass
      SPRING_REDIS_HOST: redis
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    networks:
      - blog-network
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/actuator/health"]
      interval: 30s
      timeout: 10s
      retries: 3

  # MySQL数据库
  db:
    image: mysql:8.0
    command: --default-authentication-plugin=mysql_native_password
    environment:
      MYSQL_ROOT_PASSWORD: rootpass
      MYSQL_DATABASE: blog
    volumes:
      - db-data:/var/lib/mysql
      - ./init-scripts:/docker-entrypoint-initdb.d
    networks:
      - blog-network
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 10

  # Redis缓存
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
    networks:
      - blog-network
    volumes:
      - redis-data:/data

  # 可选:Nginx反向代理
  proxy:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - backend
    networks:
      - blog-network

# 资源定义
volumes:
  db-data:
  redis-data:

networks:
  blog-network:
    driver: bridge

启动魔法 ✨

# 构建并启动所有服务(后台模式)
docker-compose up -d --build

# 查看运行状态
docker-compose ps

# 查看实时日志
docker-compose logs -f backend

# 停止并清理
docker-compose down -v

Docker Compose工作原理

  1. 解析阶段:读取docker-compose.yml文件
  2. 资源创建
    • 创建专属网络(默认bridge)
    • 创建命名卷(volumes)
  3. 服务启动
    • 按依赖顺序启动服务(depends_on)
    • 构建镜像(如果配置build)
    • 挂载卷和配置文件
  4. 服务发现
    • 通过服务名自动DNS解析(如db → 172.18.0.2)
  5. 生命周期管理
    • 监控容器状态
    • 处理日志流
    • 响应停止信号
graph TD
    A[用户执行 docker-compose up] --> B[解析YAML配置]
    B --> C[创建网络]
    B --> D[创建卷]
    C --> E[按顺序启动服务]
    D --> E
    E --> F[构建镜像]
    E --> G[拉取镜像]
    F --> H[启动容器]
    G --> H
    H --> I[健康检查]
    I --> J[服务就绪]

与其他容器编排工具对比

特性Docker ComposeKubernetes (Minikube)Docker Swarm
学习曲线⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
本地开发体验⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
生产环境适用性⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️⭐️
服务发现内置DNSCoreDNS内置DNS
负载均衡基础高级(Service/Ingress)内置
扩展性单机大规模集群中等集群
典型使用场景本地开发生产部署中小型生产

结论

  • 开发环境:Compose是黄金标准
  • 生产环境:Kubernetes是行业标准
  • 过渡方案:Compose适合CI/CD流水线测试

十大避坑指南 🚧

  1. 依赖陷阱
    depends_on只保证启动顺序,不保证服务就绪
    解决方案:添加健康检查 + 应用启动重试逻辑

  2. 端口冲突
    端口已在使用中错误频发
    解决方案:使用动态端口 "${WEB_PORT:-8080}:8080"

  3. 文件权限问题
    容器内应用无法写入挂载目录
    解决方案:Linux下使用:Z挂载选项

    volumes:
      - ./data:/app/data:Z
    
  4. 环境变量污染
    .env文件覆盖系统环境变量
    解决方案:明确指定env文件

    docker-compose --env-file .env.prod up
    
  5. 镜像版本冻结
    使用latest标签导致不可控更新
    解决方案:固定版本号 redis:7.0-alpine

  6. 资源泄露
    长期运行产生僵尸容器
    解决方案:定期清理

    docker-compose down --rmi all -v
    
  7. 日志爆炸
    容器日志占满磁盘
    解决方案:配置日志轮转

    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    
  8. Windows路径问题
    卷挂载路径格式错误
    解决方案:使用WSL2或统一路径格式

    volumes:
      - /c/Users/project/data:/app/data
    
  9. 缓存污染
    构建时未正确利用缓存
    解决方案:优化Dockerfile顺序

    # 先复制pom.xml下载依赖
    COPY pom.xml .
    RUN mvn dependency:go-offline
    
    # 再复制源代码
    COPY src ./src
    
  10. 敏感信息泄露
    密码硬编码在YAML中
    解决方案:使用Docker secrets或环境变量文件

    environment:
      DB_PASSWORD_FILE: /run/secrets/db_pass
    secrets:
      - db_pass
    

七大最佳实践 💡

  1. 版本控制
    明确指定Compose文件版本和镜像版本

  2. 环境分离
    使用多个Compose文件:

    # 基础配置
    docker-compose -f docker-compose.yml 
    # 覆盖开发配置
    -f docker-compose.dev.yml up
    
  3. 资源限制
    防止单个容器耗尽资源:

    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    
  4. 健康检查
    确保服务真正可用:

    healthcheck:
      test: ["CMD-SHELL", "curl -f http://localhost:3000 || exit 1"]
      interval: 30s
      timeout: 10s
      retries: 3
    
  5. 高效构建
    .dockerignore文件加速构建:

    **/node_modules
    **/target
    .git
    *.log
    
  6. 网络隔离
    自定义网络提升安全性:

    networks:
      frontend:
        driver: bridge
      backend:
        driver: bridge
    
  7. 配置管理
    使用Config集中管理配置:

    configs:
      nginx-config:
        file: ./nginx/nginx.conf
    services:
      nginx:
        configs:
          - source: nginx-config
            target: /etc/nginx/nginx.conf
    

面试考点精析 💼

常见问题

  1. Compose如何实现服务发现?
    答:自动创建默认网络,为每个容器设置DNS记录,可通过服务名访问

  2. docker-compose updocker-compose run 区别?
    答:up启动所有服务,run在指定服务上执行一次性命令

  3. 如何实现滚动更新?
    答:Compose本身不支持,但可通过命令序列实现:

    docker-compose up --force-recreate --no-deps -d backend
    
  4. Compose文件版本兼容性?
    答:v3+需要Docker Engine 17.06.0+,v2.4+支持扩展字段

  5. 生产环境使用Compose的注意事项?
    答:添加资源限制、配置健康检查、启用TLS加密、使用配置管理

实战题

场景:现有Spring Boot应用需要连接PostgreSQL和Redis,要求:

  • PostgreSQL需要初始化脚本
  • 应用需等待数据库就绪
  • Redis需要持久化存储
  • 所有服务在隔离网络中

编写docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_started
    environment:
      DB_URL: jdbc:postgresql://db:5432/mydb
      REDIS_HOST: redis
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8080/health"]
    
  db:
    image: postgres:14-alpine
    volumes:
      - db-data:/var/lib/postgresql/data
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    environment:
      POSTGRES_PASSWORD: secret
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
    
  redis:
    image: redis:7-alpine
    command: redis-server --save 60 1 --loglevel warning
    volumes:
      - redis-data:/data

volumes:
  db-data:
  redis-data:

总结:为什么你需要Docker Compose?

  1. 开发效率倍增器:从手动操作到一键启停
  2. 环境一致性保证:从"在我机器上正常"到处处一致
  3. 架构可视化:YAML即文档,新人快速上手
  4. 平滑学习曲线:掌握容器编排的第一步
  5. 生产过渡桥梁:Compose配置可转为Kubernetes资源

"优秀的开发者用容器打包应用,卓越的开发者用Compose编排未来。"

无论你是全栈工程师、DevOps还是SRE,掌握Docker Compose都是现代应用开发的必修课。它可能不是最终的生产部署方案,但绝对是通往容器化世界最友好的大门!

行动号召:现在就打开终端,创建一个docker-compose.yml,让你的多容器应用飞起来吧!遇到问题?在评论区留言,我们一起解决~