PDFTranslator服务Docker容器化部署最佳实践

前言

在团队内部署文档翻译服务时,我们通常面临几个挑战:

  • 环境一致性:开发、测试、生产环境差异导致的诡异问题
  • 依赖管理:AI翻译依赖的Python包、模型权重、系统库等
  • 弹性扩缩容:不同时间段的翻译请求量差异巨大
  • 资源隔离:避免单个翻译任务占用过多GPU/CPU影响其他任务

Docker容器化是解决这些问题的标准方案。本文以PDFTranslator翻译服务为例,分享一套完整的容器化部署方案,包括Dockerfile、docker-compose、Nginx反向代理、生产环境调优等实战内容。

环境准备

  • Docker 20.10+
  • Docker Compose v2.0+
  • 推荐运行环境:Linux(Ubuntu 20.04+ / CentOS 8+)
  • 至少 2 核 4GB 内存(小团队场景)

项目结构

pdf-translator-deploy/
├── docker/
│   ├── Dockerfile
│   ├── requirements.txt
│   └── entrypoint.sh
├── nginx/
│   ├── nginx.conf
│   └── conf.d/
│       └── translator.conf
├── docker-compose.yml
├── .env.example
└── README.md

Step 1: Dockerfile

多阶段构建镜像,减小最终镜像体积。

# docker/Dockerfile
# ============================================
# Stage 1: 依赖构建
# ============================================
FROM python:3.11-slim AS builder

WORKDIR /build

# 安装系统依赖(构建阶段需要的编译工具)
RUN apt-get update && apt-get install -y --no-install-recommends \
    gcc \
    g++ \
    libffi-dev \
    libssl-dev \
    && rm -rf /var/lib/apt/lists/*

# 复制依赖文件
COPY requirements.txt .

# 使用 wheels 缓存加速构建
RUN pip wheel --no-cache-dir --wheel-dir /wheels \
    -r requirements.txt

# ============================================
# Stage 2: 运行时镜像
# ============================================
FROM python:3.11-slim AS runtime

# 元数据
LABEL maintainer="ops@example.com" \
      version="1.0.0" \
      description="PDFTranslator Service Container"

# 安装运行时系统依赖(精简版)
RUN apt-get update && apt-get install -y --no-install-recommends \
    libgomp1 \
    libxml2 \
    libxslt1.1 \
    fonts-noto-cjk \
    fonts-noto-color-emoji \
    curl \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# 创建非root用户(安全最佳实践)
RUN groupadd -r appuser && useradd -r -g appuser -d /app appuser

WORKDIR /app

# 复制 wheels 并安装
COPY --from=builder /wheels /wheels
COPY requirements.txt .
RUN pip install --no-cache-dir --no-index --find-links=/wheels -r requirements.txt \
    && rm -rf /wheels

# 复制应用代码
COPY --chown=appuser:appuser . /app

# 设置权限
RUN chmod +x /app/entrypoint.sh

USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

EXPOSE 8000

ENTRYPOINT ["/app/entrypoint.sh"]
CMD ["gunicorn", "app:app", "--config", "gunicorn_conf.py"]
# docker/requirements.txt
flask==3.0.0
gunicorn==21.2.0
requests==2.31.0
pdfplumber==0.10.4
PyMuPDF==1.23.21
python-dotenv==1.0.0
redis==5.0.1
celery==5.3.4
prometheus-client==0.19.0
# docker/entrypoint.sh
#!/bin/bash
set -e

echo "=== PDFTranslator Service Starting ==="
echo "Working directory: $(pwd)"
echo "User: $(whoami)"
echo "Python version: $(python --version)"

# 等待依赖服务(Redis等)
if [ -n "$REDIS_HOST" ]; then
    echo "Waiting for Redis at $REDIS_HOST:${REDIS_PORT:-6379}..."
    until python -c "import socket; s=socket.socket(); s.settimeout(2); s.connect(('$REDIS_HOST', ${REDIS_PORT:-6379}))" 2>/dev/null; do
        echo "  Redis not ready, retrying in 2s..."
        sleep 2
    done
    echo "Redis is ready!"
fi

# 执行传入的命令
exec "$@"

Step 2: docker-compose.yml

# docker-compose.yml
version: '3.8'

services:
  # ==========================================
  # 主翻译服务
  # ==========================================
  translator-app:
    build:
      context: ./docker
      dockerfile: Dockerfile
    image: pdf-translator:latest
    container_name: pdf-translator-app
    restart: unless-stopped
    environment:
      - FLASK_ENV=production
      - REDIS_HOST=redis
      - REDIS_PORT=6379
      - MAX_UPLOAD_MB=20
      - WORKERS=4
      - LOG_LEVEL=INFO
    volumes:
      - ./logs:/app/logs
      - ./uploads:/app/uploads
    depends_on:
      redis:
        condition: service_healthy
    networks:
      - translator-net
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 4G
        reservations:
          cpus: '0.5'
          memory: 1G
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    logging:
      driver: json-file
      options:
        max-size: "10m"
        max-file: "3"

  # ==========================================
  # 异步任务队列(Celery worker)
  # ==========================================
  translator-worker:
    image: pdf-translator:latest
    container_name: pdf-translator-worker
    restart: unless-stopped
    command: celery -A tasks.celery_app worker --loglevel=info --concurrency=2
    environment:
      - REDIS_HOST=redis
      - CELERY_BROKER_URL=redis://redis:6379/1
    volumes:
      - ./logs:/app/logs
    depends_on:
      - redis
      - translator-app
    networks:
      - translator-net
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 8G

  # ==========================================
  # Redis 缓存与队列
  # ==========================================
  redis:
    image: redis:7.2-alpine
    container_name: pdf-translator-redis
    restart: unless-stopped
    command: redis-server --maxmemory 512mb --maxmemory-policy allkeys-lru
    volumes:
      - redis-data:/data
    networks:
      - translator-net
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5

  # ==========================================
  # Nginx 反向代理
  # ==========================================
  nginx:
    image: nginx:1.25-alpine
    container_name: pdf-translator-nginx
    restart: unless-stopped
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./ssl:/etc/nginx/ssl:ro
      - ./logs/nginx:/var/log/nginx
    depends_on:
      - translator-app
    networks:
      - translator-net

volumes:
  redis-data:
    driver: local

networks:
  translator-net:
    driver: bridge

Step 3: 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 4096;
    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" '
                    'rt=$request_time';

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

    # 性能优化
    sendfile on;
    tcp_nopush on;
    tcp_nodelay on;
    keepalive_timeout 65;
    types_hash_max_size 2048;
    server_tokens off;

    # 上传文件大小限制(PDF翻译需要支持20MB)
    client_max_body_size 25M;

    # 压缩
    gzip on;
    gzip_vary on;
    gzip_min_length 1024;
    gzip_types text/plain text/css application/json application/javascript 
               application/xml+rss application/atom+xml image/svg+xml;

    # 包含其他配置
    include /etc/nginx/conf.d/*.conf;
}
# nginx/conf.d/translator.conf
# 强制 HTTPS
server {
    listen 80;
    server_name translate.example.com;
    
    # Let's Encrypt 验证路径
    location /.well-known/acme-challenge/ {
        root /var/www/certbot;
    }
    
    # 其他请求强制跳转 HTTPS
    location / {
        return 301 https://$server_name$request_uri;
    }
}

# HTTPS 主服务
server {
    listen 443 ssl http2;
    server_name translate.example.com;

    # SSL 证书
    ssl_certificate /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 1d;
    ssl_session_tickets off;

    # 安全响应头
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;

    # PDF 翻译接口(大文件上传)
    location /api/translate {
        proxy_pass http://translator-app:8000;
        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_set_header X-Forwarded-Proto $scheme;
        
        # 长超时(PDF翻译可能需要3-5分钟)
        proxy_connect_timeout 60s;
        proxy_send_timeout 300s;
        proxy_read_timeout 300s;
        
        # 缓冲设置
        proxy_request_buffering off;
        proxy_buffering off;
    }

    # 静态资源
    location /static/ {
        alias /app/static/;
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    # 健康检查端点
    location /health {
        access_log off;
        proxy_pass http://translator-app:8000/health;
    }

    # 限流(防止滥用)
    location / {
        limit_req zone=translator burst=20 nodelay;
        proxy_pass http://translator-app:8000;
        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_set_header X-Forwarded-Proto $scheme;
    }
}

# 限流配置(在 http 块中添加)
# limit_req_zone $binary_remote_addr zone=translator:10m rate=10r/s;

Step 4: Gunicorn 配置

# docker/gunicorn_conf.py
import multiprocessing
import os

# 基础配置
bind = "0.0.0.0:8000"
workers = int(os.getenv("WORKERS", multiprocessing.cpu_count() * 2 + 1))
worker_class = "gthread"  # 使用线程处理 IO 密集型请求
threads = 2

# 超时设置
timeout = 300  # 5分钟超时(PDF翻译可能耗时较长)
graceful_timeout = 60
keepalive = 5

# 性能优化
max_requests = 1000
max_requests_jitter = 100
preload_app = True

# 日志
accesslog = "-"
errorlog = "-"
loglevel = os.getenv("LOG_LEVEL", "info").lower()
access_log_format = '%(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s" %(L)s'

# 进程名
proc_name = "pdf-translator"

Step 5: 部署与运维命令

# ==========================================
# 首次部署
# ==========================================

# 1. 复制环境变量模板
cp .env.example .env
vim .env  # 修改配置

# 2. 构建镜像
docker-compose build --no-cache

# 3. 启动服务
docker-compose up -d

# 4. 查看启动日志
docker-compose logs -f translator-app

# ==========================================
# 日常运维
# ==========================================

# 查看服务状态
docker-compose ps

# 重启某个服务
docker-compose restart translator-app

# 查看资源使用
docker stats pdf-translator-app

# 查看实时日志
docker-compose logs -f --tail=100

# 进入容器调试
docker-compose exec translator-app bash

# ==========================================
# 扩缩容
# ==========================================

# 水平扩展 worker 节点(适合 CPU 密集型场景)
docker-compose up -d --scale translator-worker=3

# 临时调整 app 实例数
docker-compose up -d --scale translator-app=2

# ==========================================
# 滚动更新
# ==========================================

# 1. 重新构建镜像
docker-compose build translator-app

# 2. 滚动重启(不中断服务)
docker-compose up -d --no-deps --build translator-app

# 3. 清理旧镜像
docker image prune -f

Step 6: 监控与日志

# 添加到 docker-compose.yml(Prometheus + Grafana 监控)
  prometheus:
    image: prom/prometheus:latest
    container_name: pdf-translator-prometheus
    volumes:
      - ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
      - prometheus-data:/prometheus
    ports:
      - "9090:9090"
    networks:
      - translator-net

  grafana:
    image: grafana/grafana:latest
    container_name: pdf-translator-grafana
    volumes:
      - grafana-data:/var/lib/grafana
    ports:
      - "3000:3000"
    depends_on:
      - prometheus
    networks:
      - translator-net

volumes:
  prometheus-data:
  grafana-data:

性能调优 Checklist

部署完成后,建议按以下顺序进行性能验证:

  • 冷启动时间:首次请求 vs 第 100 次请求的响应时间对比
  • 并发能力:用 wrk 或 ab 测试并发 100 时的 TPS
  • 内存泄漏:跑 24 小时压测,观察内存变化趋势
  • GPU 利用率:如果是 GPU 版本,用 nvidia-smi 监控利用率
  • 磁盘 IO:翻译过程中是否有大量磁盘读写,必要时换 SSD
  • 网络带宽:大文件上传时是否打满带宽

常见问题排查

问题排查方向
服务启动失败docker-compose logs translator-app 查看启动日志
上传 413 错误检查 Nginx client_max_body_size 和 Flask MAX_CONTENT_LENGTH
翻译超时调整 Nginx proxy_read_timeout 和 Gunicorn timeout
内存占用高检查是否有未释放的文件句柄,添加 worker_max_requests
Redis 连接失败检查网络连通性:docker-compose exec translator-app ping redis

总结

容器化部署的核心价值不在于技术本身,而在于**"一次构建,到处运行"的可重复性**。无论是开发自测、CI集成、生产部署,都能基于同一套镜像完成。

对于 PDF 翻译这种IO 密集 + CPU 密集混合型的服务,关键是:

  1. 合理拆分:Web 服务 + Worker 队列独立扩缩容
  2. 资源限制:避免单个任务占用全部资源
  3. 优雅降级:超时和异常情况下给出友好提示
  4. 可观测性:完整的日志、监控、告警体系

这套方案已经在多个生产环境稳定运行半年以上,可以作为团队内部部署文档翻译服务的参考模板。

参考资料


标签:Docker、容器化、Nginx、DevOps、AI服务部署

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值