Docker Compose 实战:构建高可用 Web 服务集群 原创

温馨提示:
本文最后更新于 2026-06-24,已超过 78 天没有更新。 若文章内的图片失效(无法正常加载),请留言反馈或直接 联系我

Docker Compose 实战:构建高可用 Web 服务集群

在现代 Web 服务架构中,Docker Compose 已经成为容器编排的事实标准之一。它让开发者能够用声明式的方式定义多容器应用,实现一键部署。本文将深入讲解如何使用 Docker Compose 构建一个高可用的 Web 服务集群,涵盖负载均衡、数据库主从、缓存层和监控告警。

一、架构设计

我们的目标是构建一个生产级别的 Web 服务集群,包含以下组件:

  • Nginx — 反向代理与负载均衡
  • PHP-FPM — 应用服务器(多实例)
  • MySQL — 主从复制数据库
  • Redis — 缓存层
  • Prometheus + Grafana — 监控告警

二、项目结构

web-cluster/
├── docker-compose.yml
├── .env
├── nginx/
│   ├── Dockerfile
│   └── conf.d/
│       ├── default.conf
│       └── upstream.conf
├── php/
│   ├── Dockerfile
│   └── php.ini
├── mysql/
│   ├── master/
│   │   ├── Dockerfile
│   │   └── my.cnf
│   └── slave/
│       ├── Dockerfile
│       └── my.cnf
├── redis/
│   └── redis.conf
├── monitoring/
│   ├── prometheus.yml
│   └── grafana/
│       └── datasources.yml
└── app/
    └── index.php

三、核心配置详解

3.1 docker-compose.yml

version: '3.8'

services:
  # Nginx 负载均衡
  nginx:
    build: ./nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./app:/var/www/html
    depends_on:
      - php1
      - php2
    networks:
      - webnet
    restart: always
    healthcheck:
      test: ["CMD", "nginx", "-t"]
      interval: 30s
      timeout: 5s
      retries: 3

  # PHP-FPM 实例 1
  php1:
    build: ./php
    volumes:
      - ./app:/var/www/html
    environment:
      - DB_HOST=mysql_master
      - DB_DATABASE=appdb
      - DB_USERNAME=appuser
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
    depends_on:
      - mysql_master
      - redis
    networks:
      - webnet
    restart: always

  # PHP-FPM 实例 2
  php2:
    build: ./php
    volumes:
      - ./app:/var/www/html
    environment:
      - DB_HOST=mysql_master
      - DB_DATABASE=appdb
      - DB_USERNAME=appuser
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
    depends_on:
      - mysql_master
      - redis
    networks:
      - webnet
    restart: always

  # MySQL 主库
  mysql_master:
    build: ./mysql/master
    volumes:
      - mysql_master_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: appdb
      MYSQL_USER: appuser
      MYSQL_PASSWORD: ${DB_PASSWORD}
    ports:
      - "3306:3306"
    networks:
      - webnet
    restart: always
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  # MySQL 从库
  mysql_slave:
    build: ./mysql/slave
    volumes:
      - mysql_slave_data:/var/lib/mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
    depends_on:
      - mysql_master
    networks:
      - webnet
    restart: always

  # Redis 缓存
  redis:
    image: redis:7-alpine
    volumes:
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf
      - redis_data:/data
    command: redis-server /usr/local/etc/redis/redis.conf
    ports:
      - "6379:6379"
    networks:
      - webnet
    restart: always

  # Prometheus 监控
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - webnet
    restart: always

  # Grafana 可视化
  grafana:
    image: grafana/grafana:latest
    volumes:
      - ./monitoring/grafana:/etc/grafana/provisioning
      - grafana_data:/var/lib/grafana
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=${GRAFANA_PASSWORD}
    depends_on:
      - prometheus
    networks:
      - webnet
    restart: always

volumes:
  mysql_master_data:
  mysql_slave_data:
  redis_data:
  prometheus_data:
  grafana_data:

networks:
  webnet:
    driver: bridge

3.2 Nginx 负载均衡配置

# nginx/conf.d/upstream.conf
upstream php_backend {
    least_conn;
    server php1:9000 weight=3 max_fails=3 fail_timeout=30s;
    server php2:9000 weight=2 max_fails=3 fail_timeout=30s;
    keepalive 32;
}

# nginx/conf.d/default.conf
server {
    listen 80;
    server_name example.com;
    root /var/www/html;
    index index.php;

    # 静态文件缓存
    location ~* \.(jpg|jpeg|png|gif|ico|css|js|woff2)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
        access_log off;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass php_backend;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        include fastcgi_params;
        fastcgi_keep_conn on;
        fastcgi_read_timeout 300;
    }

    # 健康检查端点
    location /health {
        access_log off;
        return 200 "OK\n";
        add_header Content-Type text/plain;
    }
}

3.3 MySQL 主从配置

# mysql/master/my.cnf
[mysqld]
server-id = 1
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = appdb
expire_logs_days = 7
max_binlog_size = 100M
innodb_flush_log_at_trx_commit = 1
sync_binlog = 1

# mysql/slave/my.cnf
[mysqld]
server-id = 2
relay_log = /var/log/mysql/mysql-relay-bin.log
log_bin = /var/log/mysql/mysql-bin.log
binlog_do_db = appdb
read_only = 1
skip_slave_start = 0

四、部署与运维

4.1 一键部署

# 1. 克隆项目并配置环境变量
git clone https://github.com/your-repo/web-cluster.git
cd web-cluster
cp .env.example .env
# 编辑 .env 文件设置密码

# 2. 启动所有服务
docker compose up -d

# 3. 验证服务状态
docker compose ps
docker compose logs -f

# 4. 初始化 MySQL 主从复制
docker compose exec mysql_master mysql -u root -p
# 在主库创建复制用户
CREATE USER 'replicator'@'%' IDENTIFIED BY 'password';
GRANT REPLICATION SLAVE ON *.* TO 'replicator'@'%';
FLUSH PRIVILEGES;
SHOW MASTER STATUS;

# 在从库配置复制
docker compose exec mysql_slave mysql -u root -p
CHANGE MASTER TO
  MASTER_HOST='mysql_master',
  MASTER_USER='replicator',
  MASTER_PASSWORD='password',
  MASTER_LOG_FILE='mysql-bin.000001',
  MASTER_LOG_POS=0;
START SLAVE;
SHOW SLAVE STATUS\G

4.2 扩容与缩容

# 增加 PHP-FPM 实例
docker compose up -d --scale php=4

# 减少实例
docker compose up -d --scale php=2

# 滚动更新
docker compose build php
docker compose up -d --no-deps --scale php=0 php
docker compose up -d --no-deps --scale php=4 php

五、监控与告警

5.1 Prometheus 配置

# monitoring/prometheus.yml
global:
  scrape_interval: 15s
  evaluation_interval: 15s

alerting:
  alertmanagers:
    - static_configs:
        - targets: []

rule_files:
  - "alerts.yml"

scrape_configs:
  - job_name: 'nginx'
    static_configs:
      - targets: ['nginx:9113']

  - job_name: 'php-fpm'
    static_configs:
      - targets: ['php1:9253', 'php2:9253']

  - job_name: 'mysql'
    static_configs:
      - targets: ['mysql_master:9104', 'mysql_slave:9104']

  - job_name: 'redis'
    static_configs:
      - targets: ['redis:9121']

  - job_name: 'node'
    static_configs:
      - targets: ['node_exporter:9100']

5.2 Grafana 面板

Grafana 预配置了以下仪表盘:

  • Web 服务概览 — 请求量、响应时间、错误率
  • 数据库性能 — 查询延迟、连接数、主从延迟
  • 缓存命中率 — Redis 内存使用、命中率
  • 系统资源 — CPU、内存、磁盘、网络

六、性能调优

6.1 Nginx 优化

# 调整 worker 进程数
worker_processes auto;

# 开启 gzip 压缩
gzip on;
gzip_types text/plain text/css application/json application/javascript;
gzip_min_length 1000;
gzip_comp_level 6;

# 连接池优化
keepalive_timeout 65;
client_max_body_size 100M;

6.2 PHP-FPM 优化

; php/php.ini
memory_limit = 256M
max_execution_time = 300
upload_max_filesize = 50M
post_max_size = 50M
opcache.enable = 1
opcache.memory_consumption = 128
opcache.max_accelerated_files = 10000

; PHP-FPM pool 配置
pm = dynamic
pm.max_children = 50
pm.start_servers = 5
pm.min_spare_servers = 5
pm.max_spare_servers = 35

七、安全加固

  • 网络隔离:使用自定义网络,仅暴露必要端口
  • 密钥管理:敏感信息通过 .env 文件管理,不硬编码
  • 资源限制:为每个容器设置 CPU 和内存限制
  • 日志轮转:配置 Docker 日志驱动,避免磁盘爆满
  • 定期更新:使用 docker compose pull 更新基础镜像
# 在 docker-compose.yml 中添加资源限制
services:
  php1:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

总结

通过 Docker Compose 构建高可用 Web 服务集群,不仅简化了部署流程,还提供了弹性伸缩和故障恢复能力。本文的配置可以直接用于生产环境,但需要根据实际业务需求进行调整。关键要点:

  • 使用 healthcheck 确保服务健康
  • 配置 restart: always 实现自动恢复
  • 使用 .env 管理敏感配置
  • 部署完整的监控告警体系
  • 定期备份数据库和持久化数据