第13章:docker生产环境部署实战

第13章:生产环境部署实战

本章目标:通过真实案例,演示如何在生产环境中部署和管理容器化应用。


13.1 生产环境架构设计

13.1.1 典型生产架构

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    生产环境架构                               │
│                                                             │
│  用户 → 负载均衡器 → Nginx 反向代理 → 应用服务 → 数据库      │
│                                                             │
│  ┌─────────────────────────────────────────────────────┐   │
│  │                    负载均衡层                         │   │
│  │  ┌──────────────┐  ┌──────────────┐                │   │
│  │  │   Nginx/LB   │  │   Nginx/LB   │  (主备/双活)   │   │
│  │  └──────┬───────┘  └──────┬───────┘                │   │
│  └─────────┼─────────────────┼────────────────────────┘   │
│            │                 │                             │
│  ┌─────────┼─────────────────┼────────────────────────┐   │
│  │         ▼                 ▼        应用服务层         │   │
│  │  ┌──────────────┐  ┌──────────────┐                │   │
│  │  │   App ×3     │  │   App ×3     │  (多副本)      │   │
│  │  └──────┬───────┘  └──────┬───────┘                │   │
│  └─────────┼─────────────────┼────────────────────────┘   │
│            │                 │                             │
│  ┌─────────┼─────────────────┼────────────────────────┐   │
│  │         ▼                 ▼        数据层             │   │
│  │  ┌──────────────┐  ┌──────────────┐                │   │
│  │  │  MySQL主从    │  │   Redis集群   │                │   │
│  │  └──────────────┘  └──────────────┘                │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

13.1.2 生产环境清单

复制代码
生产环境检查清单:

□ 基础设施
  ├── □ 服务器配置(CPU/内存/磁盘)
  ├── □ 网络配置(VLAN/防火墙)
  ├── □ 域名和 SSL 证书
  └── □ 备份策略

□ Docker 环境
  ├── □ Docker 版本和配置
  ├── □ 镜像仓库
  ├── □ 日志配置
  └── □ 监控告警

□ 应用部署
  ├── □ Dockerfile 优化
  ├── □ docker-compose 配置
  ├── □ 环境变量管理
  └── □ 健康检查配置

□ 数据安全
  ├── □ 数据库持久化
  ├── □ 备份恢复策略
  ├── □ 密钥管理
  └── □ SSL/TLS 加密

□ 运维保障
  ├── □ 日志收集
  ├── □ 监控告警
  ├── □ 自动扩缩容
  └── □ 灰度发布

13.2 Nginx + PHP + MySQL + Redis 全栈部署

13.2.1 项目结构

复制代码
project/
├── docker-compose.yml          # 主配置
├── docker-compose.prod.yml     # 生产环境配置
├── .env                        # 环境变量
├── .env.prod                   # 生产环境变量
├── nginx/
│   ├── nginx.conf              # Nginx 主配置
│   ├── conf.d/
│   │   └── default.conf        # 站点配置
│   └── ssl/                    # SSL 证书
├── php/
│   ├── Dockerfile              # PHP 镜像
│   ├── php.ini                 # PHP 配置
│   �── opcache.ini              # OPcache 配置
│   └── conf.d/
│       └── app.ini             # 应用配置
├── mysql/
│   ├── my.cnf                  # MySQL 配置
│   └── initdb/                 # 初始化脚本
├── redis/
│   └── redis.conf              # Redis 配置
├── app/                        # 应用代码
│   ├── src/
│   ├── public/
│   └── composer.json
└── logs/                       # 日志目录

13.2.2 Docker Compose 配置

yaml 复制代码
# docker-compose.prod.yml
version: '3.8'

services:
  # Nginx 反向代理
  nginx:
    image: nginx:1.25-alpine
    container_name: prod-nginx
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./app/public:/app/public:ro
      - nginx-logs:/var/log/nginx
    networks:
      - frontend
    depends_on:
      - php
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost/health"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 512M

  # PHP-FPM 应用服务
  php:
    build:
      context: ./php
      dockerfile: Dockerfile
    container_name: prod-php
    volumes:
      - ./app:/app
      - ./php/php.ini:/usr/local/etc/php/php.ini:ro
      - ./php/opcache.ini:/usr/local/etc/php/conf.d/opcache.ini:ro
    environment:
      - APP_ENV=production
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_NAME=${DB_NAME}
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - REDIS_PASSWORD=${REDIS_PASSWORD}
    networks:
      - frontend
      - backend
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "php-fpm-healthcheck"]
      interval: 30s
      timeout: 5s
      retries: 3
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 1G

  # MySQL 数据库
  mysql:
    image: mysql:8.0
    container_name: prod-mysql
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_USER: ${DB_USER}
      MYSQL_PASSWORD: ${DB_PASSWORD}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/conf.d/custom.cnf:ro
      - ./mysql/initdb:/docker-entrypoint-initdb.d
    ports:
      - "127.0.0.1:3306:3306"  # 仅本地访问
    networks:
      - backend
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G

  # Redis 缓存
  redis:
    image: redis:7-alpine
    container_name: prod-redis
    command: redis-server /etc/redis/redis.conf
    volumes:
      - redis-data:/data
      - ./redis/redis.conf:/etc/redis/redis.conf:ro
    ports:
      - "127.0.0.1:6379:6379"  # 仅本地访问
    networks:
      - backend
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M

networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true  # 内部网络,不对外暴露

volumes:
  mysql-data:
    driver: local
  redis-data:
    driver: local
  nginx-logs:
    driver: local

13.2.3 Nginx 配置

nginx 复制代码
# nginx/nginx.conf
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 1024;
    use epoll;
    multi_accept on;
}

http {
    include /etc/nginx/mime.types;
    default_type application/octet-stream;

    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
                    '$status $body_bytes_sent "$http_referer" '
                    '"$http_user_agent" "$http_x_forwarded_for"';

    access_log /var/log/nginx/access.log main;

    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;

    # Gzip 压缩
    gzip on;
    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_types text/plain text/css text/xml text/javascript 
               application/json application/javascript application/xml+rss;

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Referrer-Policy "no-referrer-when-downgrade" always;

    include /etc/nginx/conf.d/*.conf;
}
nginx 复制代码
# nginx/conf.d/default.conf
upstream php-upstream {
    server php:9000;
}

server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

server {
    listen 443 ssl http2;
    server_name example.com;

    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;
    ssl_prefer_server_ciphers on;

    root /app/public;
    index index.php index.html;

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

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

    # PHP 处理
    location ~ \.php$ {
        try_files $uri =404;
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass php-upstream;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
        fastcgi_read_timeout 300;
    }

    # 禁止访问隐藏文件
    location ~ /\. {
        deny all;
        access_log off;
        log_not_found off;
    }

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

13.2.4 PHP 配置

dockerfile 复制代码
# php/Dockerfile
FROM php:8.2-fpm-alpine

# 安装系统依赖
RUN apk add --no-cache \
    freetype-dev \
    libjpeg-turbo-dev \
    libpng-dev \
    libzip-dev \
    icu-dev \
    oniguruma-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) \
        pdo \
        pdo_mysql \
        mysqli \
        bcmath \
        gd \
        zip \
        intl \
        mbstring \
        opcache \
        pcntl

# 安装 Redis 扩展
RUN apk add --no-cache $PHPIZE_DEPS \
    && pecl install redis \
    && docker-php-ext-enable redis

# 清理
RUN apk del --purge $PHPIZE_DEPS

# 复制配置文件
COPY php.ini /usr/local/etc/php/php.ini
COPY opcache.ini /usr/local/etc/php/conf.d/opcache.ini

# 设置工作目录
WORKDIR /app

# 安装 Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

EXPOSE 9000

CMD ["php-fpm"]
ini 复制代码
; php/php.ini
[PHP]
memory_limit = 256M
upload_max_filesize = 64M
post_max_size = 64M
max_execution_time = 300
max_input_time = 300
date.timezone = Asia/Shanghai

[Session]
session.save_handler = redis
session.save_path = "tcp://redis:6379?auth=your_password"
session.gc_maxlifetime = 1440
ini 复制代码
; php/opcache.ini
[opcache]
opcache.enable=1
opcache.memory_consumption=128
opcache.interned_strings_buffer=8
opcache.max_accelerated_files=10000
opcache.revalidate_freq=60
opcache.save_comments=1
opcache.fast_shutdown=1
opcache.enable_cli=0

13.2.5 MySQL 配置

ini 复制代码
# mysql/my.cnf
[mysqld]
character-set-server = utf8mb4
collation-server = utf8mb4_unicode_ci
default-storage-engine = InnoDB

# 性能优化
innodb_buffer_pool_size = 1G
innodb_log_file_size = 256M
innodb_flush_log_at_trx_commit = 2
innodb_flush_method = O_DIRECT

# 连接配置
max_connections = 200
wait_timeout = 600
interactive_timeout = 600

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

# 二进制日志(用于主从复制和备份)
log-bin = mysql-bin
binlog_expire_logs_seconds = 604800
max_binlog_size = 100M

# 安全配置
sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION

13.3 灰度发布策略

13.3.1 蓝绿部署

yaml 复制代码
# 蓝绿部署示例
version: '3.8'

services:
  # 蓝环境(当前版本)
  app-blue:
    image: myapp:v1.0
    container_name: app-blue
    networks:
      - app-network
    deploy:
      replicas: 3

  # 绿环境(新版本)
  app-green:
    image: myapp:v2.0
    container_name: app-green
    networks:
      - app-network
    deploy:
      replicas: 3

  # Nginx 负载均衡(切换流量)
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    networks:
      - app-network
nginx 复制代码
# nginx/nginx.conf - 蓝绿切换
upstream app {
    # 蓝环境(当前)
    server app-blue:8080;
    # 绿环境(新版本)- 切换时注释/取消注释
    # server app-green:8080;
}

13.3.2 金丝雀发布

yaml 复制代码
# 金丝雀发布示例
version: '3.8'

services:
  # 稳定版本(90% 流量)
  app-stable:
    image: myapp:v1.0
    deploy:
      replicas: 9

  # 金丝雀版本(10% 流量)
  app-canary:
    image: myapp:v2.0
    deploy:
      replicas: 1

13.3.3 滚动更新

bash 复制代码
# Docker Swarm 滚动更新
docker service update \
  --image myapp:v2.0 \
  --update-parallelism 1 \
  --update-delay 30s \
  --update-failure-action rollback \
  myapp-service

# 参数说明:
# --update-parallelism 1    每次更新1个实例
# --update-delay 30s        每次更新间隔30秒
# --update-failure-action rollback  失败时自动回滚

13.4 自动扩缩容

13.4.1 Docker Swarm 自动扩缩容

bash 复制代码
# 设置服务自动扩缩容
docker service scale myapp=3

# 根据负载手动扩缩容
docker service scale myapp=5
docker service scale myapp=2

13.4.2 结合 Prometheus 的自动扩缩容

yaml 复制代码
# prometheus-rules.yml
groups:
- name: scaling-rules
  rules:
  - record: app:cpu:avg
    expr: avg(rate(container_cpu_usage_seconds_total{image="myapp"}[5m]))

  - alert: ScaleUp
    expr: app:cpu:avg > 0.7
    for: 5m
    labels:
      action: scale-up

  - alert: ScaleDown
    expr: app:cpu:avg < 0.3
    for: 10m
    labels:
      action: scale-down

13.5 数据备份与恢复

13.5.1 MySQL 备份策略

bash 复制代码
# 全量备份
docker exec prod-mysql mysqldump -uroot -p${DB_ROOT_PASSWORD} \
  --all-databases --single-transaction --routines --triggers \
  | gzip > /backup/mysql/full_$(date +%Y%m%d_%H%M%S).sql.gz

# 增量备份(基于 binlog)
docker exec prod-mysql mysqlbinlog --read-from-remote-server \
  --host=master-host --user=repl --password=xxx \
  mysql-bin.000001 > /backup/mysql/incremental.sql

# 恢复
gunzip < /backup/mysql/full_20240115_030000.sql.gz | \
  docker exec -i prod-mysql mysql -uroot -p${DB_ROOT_PASSWORD}

13.5.2 自动化备份脚本

bash 复制代码
#!/bin/bash
# backup.sh - 自动化备份脚本

set -e

# 配置
BACKUP_DIR="/backup/mysql"
DATE=$(date +%Y%m%d_%H%M%S)
RETENTION_DAYS=7
DB_ROOT_PASSWORD="${DB_ROOT_PASSWORD}"

# 创建备份目录
mkdir -p ${BACKUP_DIR}

# MySQL 全量备份
echo "[$(date)] 开始 MySQL 备份..."
docker exec prod-mysql mysqldump -uroot -p${DB_ROOT_PASSWORD} \
  --all-databases --single-transaction --routines --triggers \
  | gzip > ${BACKUP_DIR}/full_${DATE}.sql.gz

# 验证备份文件
if [ -f "${BACKUP_DIR}/full_${DATE}.sql.gz" ]; then
  SIZE=$(du -h "${BACKUP_DIR}/full_${DATE}.sql.gz" | cut -f1)
  echo "[$(date)] 备份完成: full_${DATE}.sql.gz (${SIZE})"
else
  echo "[$(date)] 备份失败!"
  exit 1
fi

# 清理过期备份
echo "[$(date)] 清理 ${RETENTION_DAYS} 天前的备份..."
find ${BACKUP_DIR} -name "full_*.sql.gz" -mtime +${RETENTION_DAYS} -delete

# 上传到远程存储(可选)
# aws s3 sync ${BACKUP_DIR} s3://my-backup-bucket/mysql/

echo "[$(date)] 备份任务完成"
bash 复制代码
# 添加到 crontab(每天凌晨3点执行)
# crontab -e
0 3 * * * /path/to/backup.sh >> /var/log/mysql-backup.log 2>&1

13.6 动手实验

实验 13.1:部署完整生产环境

bash 复制代码
# 1. 创建项目结构
mkdir -p ~/production/{nginx/{conf.d,ssl},php,mysql/{conf.d,initdb},redis,app/public,logs}

# 2. 创建 docker-compose.prod.yml(使用上面的配置)

# 3. 创建配置文件(使用上面的配置)

# 4. 创建 .env 文件
cat > .env << 'EOF'
DB_NAME=myapp
DB_USER=appuser
DB_PASSWORD=apppass123
DB_ROOT_PASSWORD=rootpass123
REDIS_PASSWORD=redis123
EOF

# 5. 启动生产环境
docker compose -f docker-compose.prod.yml up -d

# 6. 查看服务状态
docker compose -f docker-compose.prod.yml ps

# 7. 查看日志
docker compose -f docker-compose.prod.yml logs -f

# 8. 测试访问
curl -k https://localhost

实验 13.2:灰度发布

bash 复制代码
# 1. 部署 v1.0 版本
docker compose up -d

# 2. 验证 v1.0
curl http://localhost
# 输出: Hello from v1.0

# 3. 启动 v2.0 版本(金丝雀)
docker compose up -d --scale app-v2=1

# 4. 逐步切换流量
# 修改 Nginx 配置,添加 v2.0 后端

# 5. 验证
curl http://localhost
# 部分请求返回 v1.0,部分返回 v2.0

# 6. 完全切换到 v2.0
# 修改 Nginx 配置,移除 v1.0 后端

# 7. 停止 v1.0
docker compose stop app-v1

13.7 本章小结

生产要素 关键配置
负载均衡 Nginx 反向代理、多副本
数据持久化 命名卷、定期备份
安全加固 非 root 用户、只读文件系统
监控告警 Prometheus + Grafana
日志管理 ELK/Loki 日志收集
灰度发布 蓝绿/金丝雀/滚动更新
备份恢复 自动化备份脚本

13.8 课后练习

  1. 实践题:部署完整的 Nginx + PHP + MySQL + Redis 生产环境。
  2. 进阶题:配置灰度发布策略,实现 v1.0 到 v2.0 的平滑升级。
  3. 运维题:编写自动化备份脚本,并添加到 crontab。

📖 下一章:Docker 与 CI/CD ------ 实现自动化构建和部署

相关推荐
爱喝水的鱼丶1 小时前
SAP-ABAP:接口 vs 抽象类:ABAP OOP两类扩展方式的差异与选型原则
运维·性能优化·sap·abap·erp·经验交流
iCxhust1 小时前
linux目录是否保存在硬盘 启动后读入解析的
linux·运维·服务器
敖行客 Allthinker2 小时前
企业级多台服务器组装 K3s 高性能集群实战指南
运维·服务器·团队开发
TTBIGDATA3 小时前
【Ambari Plus】10.HBase 安装
大数据·运维·hadoop·ambari·hdp·cdh·bigtop
hj2862514 小时前
Docker 容器化技术标准化笔记
java·笔记·docker
艾伦_耶格宇4 小时前
【ELK】-2 ELK的搭建
运维·elk
xcLeigh4 小时前
KES运维自动化与脚本体系实战
运维·数据库·自动化·脚本·数据迁移·kes
潘正翔4 小时前
docker基础_镜像使用
linux·运维·服务器·docker·容器·centos·devops
GuHenryCheng4 小时前
【ESP32】ESP-IDF开发环境搭建(cursor)
git·stm32·单片机·学习