Docker Compose 编排 Flask + MySQL + Redis:3 服务一键部署与健康检查配置

现代Web应用开发中,微服务架构已成为主流趋势。将应用拆分为多个独立服务虽然提高了灵活性和可维护性,但也带来了部署复杂度。本文将手把手教你如何使用Docker Compose编排一个完整的Python Web应用栈,包含Flask应用、MySQL数据库和Redis缓存服务,并实现生产级健康检查机制。

1. 项目架构设计与准备工作

我们先来看一个典型的Python Web应用技术栈组成:

  • Web框架 :Flask/Django等提供HTTP服务
  • 数据库 :MySQL/PostgreSQL等关系型数据库
  • 缓存 :Redis/Memcached等内存数据库
  • 应用服务器 :Gunicorn/uWSGI等WSGI服务器

传统部署方式需要分别在服务器上安装配置这些组件,而使用Docker Compose可以:

  1. 通过容器化隔离各服务依赖环境
  2. 使用声明式配置定义服务间关系
  3. 实现一键启动/停止整个应用栈

项目目录结构 建议如下:

flask-demo/
├── app/                  # Flask应用代码
│   ├── __init__.py
│   ├── routes.py
│   └── models.py
├── requirements.txt      # Python依赖
├── Dockerfile            # Flask应用镜像构建
├── docker-compose.yml    # 多服务编排
├── .env                  # 环境变量配置
└── init.sql              # 数据库初始化脚本

2. Flask应用容器化

首先我们需要将Flask应用打包为Docker镜像。采用多阶段构建可以显著减小最终镜像体积:

# 构建阶段
FROM python:3.11-slim as builder

WORKDIR /app
COPY requirements.txt .

# 安装构建依赖
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    gcc \
    python3-dev \
    && rm -rf /var/lib/apt/lists/*

# 创建虚拟环境并安装依赖
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"
RUN pip install --no-cache-dir -r requirements.txt

# 运行阶段
FROM python:3.11-slim

WORKDIR /app
COPY --from=builder /opt/venv /opt/venv
COPY . .

ENV PATH="/opt/venv/bin:$PATH"
ENV FLASK_APP=app.py
ENV FLASK_ENV=production

# 创建非root用户运行
RUN useradd -m appuser && chown -R appuser:appuser /app
USER appuser

EXPOSE 5000
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "app:app"]

关键优化点:

  1. 使用slim基础镜像减少体积
  2. 多阶段构建分离构建环境和运行环境
  3. 创建专用用户增强安全性
  4. 使用Gunicorn替代Flask开发服务器

对应的 requirements.txt 应包含:

flask==2.3.0
gunicorn==21.2.0
mysql-connector-python==8.1.0
redis==4.6.0

3. Docker Compose服务编排

下面是完整的 docker-compose.yml 文件,编排三个服务并配置它们之间的依赖关系:

version: '3.8'

services:
  app:
    build: .
    container_name: flask-app
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=db
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 10s
    networks:
      - app-network

  db:
    image: mysql:8.0
    container_name: mysql-db
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    ports:
      - "3306:3306"
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${DB_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    container_name: redis-cache
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    command: redis-server --appendonly yes
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  mysql-data:
  redis-data:

配置亮点:

  1. 服务发现 :通过服务名(db/redis)直接访问,Docker内置DNS解析
  2. 健康检查 :各服务定义健康检查策略,确保依赖服务就绪
  3. 数据持久化 :使用命名卷保存数据库和Redis数据
  4. 环境变量 :敏感信息通过.env文件配置

对应的 .env 文件示例:

DB_USER=appuser
DB_PASSWORD=securepassword
DB_NAME=flaskdb

4. 健康检查实现与优化

健康检查是生产环境部署的关键组件。我们在三个层面实现健康监控:

4.1 MySQL健康检查

healthcheck:
  test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${DB_PASSWORD}"]
  interval: 10s
  timeout: 5s
  retries: 3

4.2 Redis健康检查

healthcheck:
  test: ["CMD", "redis-cli", "ping"]
  interval: 10s
  timeout: 5s
  retries: 3

4.3 Flask应用健康检查端点

在Flask应用中添加 /health 端点:

@app.route('/health')
def health():
    try:
        # 检查数据库连接
        conn = mysql.connector.connect(
            host=os.getenv('DB_HOST'),
            user=os.getenv('DB_USER'),
            password=os.getenv('DB_PASSWORD'),
            database=os.getenv('DB_NAME')
        )
        conn.close()
        
        # 检查Redis连接
        redis_conn.ping()
        
        return jsonify({'status': 'healthy'}), 200
    except Exception as e:
        return jsonify({'status': 'unhealthy', 'error': str(e)}), 500

然后在Compose中配置:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:5000/health"]
  interval: 30s
  timeout: 10s
  retries: 3
  start_period: 10s

5. 数据库初始化与数据持久化

为了保证数据库服务重启后数据不丢失,我们采用两种持久化方案:

5.1 MySQL数据卷

volumes:
  - mysql-data:/var/lib/mysql

5.2 Redis AOF持久化

command: redis-server --appendonly yes
volumes:
  - redis-data:/data

数据库初始化脚本 init.sql 示例:

CREATE TABLE IF NOT EXISTS users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) NOT NULL,
    email VARCHAR(100) NOT NULL UNIQUE,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

INSERT INTO users (username, email) VALUES
('admin', 'admin@example.com'),
('user1', 'user1@example.com');

6. 网络配置与服务通信

Docker Compose默认会为应用创建专用网络,服务间通过服务名互相访问:

networks:
  app-network:
    driver: bridge

网络拓扑特点:

  1. 隔离性:与应用外部网络隔离
  2. 服务发现:容器间通过服务名解析IP
  3. 安全性:仅暴露必要端口到主机

7. 生产环境部署建议

将开发配置升级为生产配置需要考虑以下方面:

  1. 日志收集 :配置JSON格式日志并收集到ELK

    import json
    import logging
    
    logging.basicConfig(
        level=logging.INFO,
        format='{"time": "%(asctime)s", "level": "%(levelname)s", "message": "%(message)s"}'
    )
    
  2. 性能调优

    • MySQL:调整InnoDB缓冲池大小
    • Redis:配置最大内存限制
    • Gunicorn:根据CPU核心数设置worker数量
  3. 监控集成

    • Prometheus指标端点
    • 健康检查扩展为就绪检查/存活检查
  4. 安全加固

    • 使用非root用户运行容器
    • 限制容器资源使用
    • 定期更新基础镜像

完整的生产级 docker-compose.prod.yml 示例:

version: '3.8'

services:
  app:
    build: .
    image: my-registry/flask-app:${TAG:-latest}
    deploy:
      resources:
        limits:
          cpus: '1'
          memory: 512M
    ports:
      - "5000:5000"
    environment:
      - DB_HOST=db
      - DB_USER=${DB_USER}
      - DB_PASSWORD=${DB_PASSWORD}
      - REDIS_HOST=redis
      - LOG_LEVEL=INFO
    configs:
      - source: gunicorn_conf
        target: /app/gunicorn.conf.py
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    networks:
      - app-network

  db:
    image: mysql:8.0
    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 1G
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_PASSWORD}
      MYSQL_DATABASE: ${DB_NAME}
      MYSQL_INNODB_BUFFER_POOL_SIZE: 256M
    volumes:
      - mysql-data:/var/lib/mysql
      - ./init.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-uroot", "-p${DB_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-network

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 3
    networks:
      - app-network

networks:
  app-network:
    driver: bridge

volumes:
  mysql-data:
  redis-data:

configs:
  gunicorn_conf:
    file: ./gunicorn.conf.py

对应的Gunicorn配置文件 gunicorn.conf.py

import multiprocessing

bind = "0.0.0.0:5000"
workers = multiprocessing.cpu_count() * 2 + 1
worker_class = "gevent"
max_requests = 1000
timeout = 120
keepalive = 5
accesslog = "-"
errorlog = "-"
loglevel = "info"

8. 实际部署操作流程

8.1 开发环境部署

# 构建并启动服务
docker-compose up -d --build

# 查看服务状态
docker-compose ps

# 查看应用日志
docker-compose logs -f app

# 停止服务
docker-compose down

8.2 生产环境部署

# 构建生产镜像
docker-compose -f docker-compose.prod.yml build

# 推送镜像到仓库
docker push my-registry/flask-app:latest

# 服务器拉取并启动
docker-compose -f docker-compose.prod.yml up -d

# 滚动更新
docker-compose -f docker-compose.prod.yml pull app
docker-compose -f docker-compose.prod.yml up -d --no-deps app

8.3 常用维护命令

# 进入容器执行命令
docker-compose exec app flask shell

# 备份数据库
docker-compose exec db mysqldump -u root -p${DB_PASSWORD} ${DB_NAME} > backup.sql

# 查看服务资源使用
docker stats

# 更新服务配置
docker-compose up -d --force-recreate

9. 故障排查与调试技巧

遇到部署问题时,可以按照以下步骤排查:

  1. 检查容器状态

    docker-compose ps
    docker inspect <container_id>
    
  2. 查看服务日志

    docker-compose logs -f app
    docker-compose logs -f db
    
  3. 手动测试连接

    # 测试MySQL连接
    docker-compose exec db mysql -u root -p${DB_PASSWORD}
    
    # 测试Redis连接
    docker-compose exec redis redis-cli ping
    
  4. 进入容器调试

    docker-compose exec app bash
    curl http://localhost:5000/health
    
  5. 网络连通性测试

    docker-compose exec app ping db
    docker-compose exec app nc -zv db 3306
    

常见问题解决方案:

问题现象 可能原因 解决方案
应用启动失败 依赖服务未就绪 增加 depends_on 健康检查
数据库连接超时 网络配置错误 检查服务名称和网络配置
性能瓶颈 资源不足 调整CPU/内存限制
数据丢失 卷未正确挂载 验证卷挂载路径和权限

10. 扩展与进阶配置

10.1 添加Nginx反向代理

services:
  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - app
    networks:
      - app-network

示例 nginx.conf

events {
    worker_connections 1024;
}

http {
    upstream flask_app {
        server app:5000;
    }

    server {
        listen 80;
        
        location / {
            proxy_pass http://flask_app;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
        }
        
        location /static/ {
            alias /app/static/;
        }
    }
}

10.2 集成Celery异步任务

services:
  celery:
    build: .
    command: celery -A app.celery worker --loglevel=info
    environment:
      - BROKER_URL=redis://redis:6379/0
      - RESULT_BACKEND=redis://redis:6379/1
    depends_on:
      - redis
    networks:
      - app-network

10.3 多环境配置管理

通过多个Compose文件实现环境差异化:

# 开发环境
docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d

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

docker-compose.dev.yml 示例:

version: '3.8'

services:
  app:
    environment:
      - FLASK_ENV=development
      - DEBUG=True
    volumes:
      - ./app:/app

11. 性能优化实践

11.1 镜像构建优化

  1. 使用 .dockerignore 文件排除不需要的文件
  2. 多阶段构建减少最终镜像大小
  3. 合并RUN命令减少镜像层数

11.2 数据库优化

services:
  db:
    environment:
      - MYSQL_INNODB_BUFFER_POOL_SIZE=256M
      - MYSQL_INNODB_LOG_FILE_SIZE=64M
    command: 
      --max_connections=200
      --innodb_flush_log_at_trx_commit=2

11.3 Redis优化

services:
  redis:
    command: redis-server --appendonly yes --maxmemory 256mb --maxmemory-policy allkeys-lru

12. 安全最佳实践

  1. 最小权限原则

    • 使用非root用户运行容器
    • 限制容器能力 --cap-drop ALL
  2. 秘密管理

    services:
      app:
        secrets:
          - db_password
    
    secrets:
      db_password:
        file: ./secrets/db_password.txt
    
  3. 网络隔离

    networks:
      app-network:
        internal: true
    
  4. 镜像扫描

    docker scan my-flask-app
    

13. 监控与日志收集

13.1 Prometheus监控

services:
  prometheus:
    image: prom/prometheus
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - app-network

13.2 Grafana仪表盘

services:
  grafana:
    image: grafana/grafana
    ports:
      - "3000:3000"
    volumes:
      - grafana-data:/var/lib/grafana
    networks:
      - app-network

13.3 集中式日志

services:
  loki:
    image: grafana/loki
    ports:
      - "3100:3100"
    networks:
      - app-network

  promtail:
    image: grafana/promtail
    volumes:
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - ./promtail-config.yml:/etc/promtail/config.yml
    networks:
      - app-network

14. CI/CD集成示例

.gitlab-ci.yml 示例:

stages:
  - test
  - build
  - deploy

variables:
  DOCKER_HOST: tcp://docker:2375
  DOCKER_DRIVER: overlay2

services:
  - docker:dind

test:
  stage: test
  image: python:3.11
  script:
    - pip install -r requirements.txt
    - pytest

build:
  stage: build
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker-compose -f docker-compose.prod.yml build
    - docker push $CI_REGISTRY_IMAGE:latest

deploy:
  stage: deploy
  environment: production
  script:
    - ssh deploy@server "cd /app && docker-compose pull && docker-compose up -d"
  only:
    - main

15. 实际项目经验分享

在真实项目中部署这类技术栈时,有几个关键点值得注意:

  1. 数据库版本控制 :使用Flyway或Alembic管理数据库迁移,而不是直接执行SQL脚本
  2. 配置管理 :区分不同环境的配置,使用12-factor应用原则管理配置
  3. 启动顺序 :确保数据库完全初始化后再启动应用,可以添加初始化检查脚本
  4. 资源限制 :为每个服务设置合理的CPU和内存限制,避免单个服务耗尽资源
  5. 日志轮转 :配置日志轮转策略,避免日志占满磁盘空间

一个实用的初始化检查脚本示例:

import time
import mysql.connector
import redis
import os
import sys

def check_mysql(max_attempts=30, wait_seconds=1):
    for _ in range(max_attempts):
        try:
            conn = mysql.connector.connect(
                host=os.getenv('DB_HOST'),
                user=os.getenv('DB_USER'),
                password=os.getenv('DB_PASSWORD'),
                database=os.getenv('DB_NAME')
            )
            conn.close()
            return True
        except Exception:
            time.sleep(wait_seconds)
    return False

def check_redis(max_attempts=30, wait_seconds=1):
    for _ in range(max_attempts):
        try:
            r = redis.Redis(
                host=os.getenv('REDIS_HOST'),
                port=6379,
                db=0
            )
            return r.ping()
        except Exception:
            time.sleep(wait_seconds)
    return False

if __name__ == "__main__":
    if not check_mysql():
        print("MySQL check failed", file=sys.stderr)
        sys.exit(1)
    
    if not check_redis():
        print("Redis check failed", file=sys.stderr)
        sys.exit(1)
    
    print("All dependencies are ready")
    sys.exit(0)

在Dockerfile中作为健康检查使用:

HEALTHCHECK --interval=5s --timeout=3s --start-period=30s --retries=3 \
    CMD python healthcheck.py || exit 1

更多推荐