018-Docker容器化部署

学习目标

通过本章节的学习,您将能够:

  • 理解Poetry项目容器化的最佳实践
  • 掌握多阶段Docker构建优化技巧
  • 学会创建高效、安全的生产级Docker镜像
  • 实现容器编排和服务发现
  • 优化镜像大小和构建时间
  • 处理容器环境下的依赖管理
  • 实现容器化的开发和部署工作流

Docker与Poetry概述

容器化的优势

一致性环境:

  • 开发、测试、生产环境完全一致
  • 消除"在我机器上能运行"的问题
  • 简化部署和扩展

依赖隔离:

  • 完全隔离的运行环境
  • 避免系统依赖冲突
  • 精确的版本控制

可移植性:

  • 跨平台部署
  • 云原生支持
  • 微服务架构友好

Poetry在容器中的挑战

# 常见挑战和解决方案
挑战:
  - 镜像大小过大
  - 构建时间过长
  - 依赖缓存失效
  - 安全漏洞风险
  - 多阶段构建复杂性

解决方案:
  - 多阶段构建
  - 智能缓存策略
  - 最小化基础镜像
  - 安全扫描集成
  - 构建优化技巧

基础Dockerfile配置

简单Dockerfile

# Dockerfile (基础版本)
FROM python:3.11-slim

# 设置工作目录
WORKDIR /app

# 安装Poetry
RUN pip install poetry==1.7.1

# 配置Poetry
ENV POETRY_NO_INTERACTION=1 \
    POETRY_VENV_IN_PROJECT=1 \
    POETRY_CACHE_DIR=/tmp/poetry_cache

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 安装依赖
RUN poetry install --only=main && rm -rf $POETRY_CACHE_DIR

# 复制应用代码
COPY src/ ./src/

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["poetry", "run", "python", "-m", "src.main"]

优化的Dockerfile

# Dockerfile (优化版本)
FROM python:3.11-slim as base

# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    && rm -rf /var/lib/apt/lists/*

# Poetry安装阶段
FROM base as poetry

ENV POETRY_HOME="/opt/poetry" \
    POETRY_VERSION=1.7.1

# 安装Poetry
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH="$POETRY_HOME/bin:$PATH"

# 配置Poetry
ENV POETRY_NO_INTERACTION=1 \
    POETRY_VENV_IN_PROJECT=1 \
    POETRY_CACHE_DIR=/opt/poetry_cache

# 依赖安装阶段
FROM poetry as deps

WORKDIR /app

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 安装依赖
RUN poetry install --only=main --no-root && rm -rf $POETRY_CACHE_DIR

# 生产阶段
FROM python:3.11-slim as production

# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 设置工作目录
WORKDIR /app

# 从deps阶段复制虚拟环境
COPY --from=deps /app/.venv /app/.venv

# 复制应用代码
COPY src/ ./src/
COPY pyproject.toml ./

# 设置PATH以使用虚拟环境
ENV PATH="/app/.venv/bin:$PATH"

# 切换到非root用户
USER appuser

# 健康检查
HEALTHCHECK --interval=30s --timeout=30s --start-period=5s --retries=3 \
    CMD python -c "import requests; requests.get('http://localhost:8000/health')" || exit 1

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["python", "-m", "src.main"]

高级多阶段构建

完整的多阶段构建

# Dockerfile (完整多阶段版本)
# 基础镜像
FROM python:3.11-slim as base

# 设置环境变量
ENV PYTHONUNBUFFERED=1 \
    PYTHONDONTWRITEBYTECODE=1 \
    PIP_NO_CACHE_DIR=1 \
    PIP_DISABLE_PIP_VERSION_CHECK=1 \
    DEBIAN_FRONTEND=noninteractive

# 安装系统依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    curl \
    git \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# Poetry安装阶段
FROM base as poetry-base

ENV POETRY_HOME="/opt/poetry" \
    POETRY_VERSION=1.7.1 \
    POETRY_CACHE_DIR=/opt/poetry_cache

# 安装Poetry
RUN curl -sSL https://install.python-poetry.org | python3 - \
    && ln -s $POETRY_HOME/bin/poetry /usr/local/bin/poetry

# 配置Poetry
ENV POETRY_NO_INTERACTION=1 \
    POETRY_VENV_IN_PROJECT=1

# 开发依赖阶段
FROM poetry-base as dev-deps

WORKDIR /app

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 安装所有依赖(包括开发依赖)
RUN poetry install --with dev,test && rm -rf $POETRY_CACHE_DIR

# 生产依赖阶段
FROM poetry-base as prod-deps

WORKDIR /app

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 只安装生产依赖
RUN poetry install --only=main --no-root && rm -rf $POETRY_CACHE_DIR

# 测试阶段
FROM dev-deps as test

# 复制源代码
COPY src/ ./src/
COPY tests/ ./tests/
COPY pyproject.toml ./

# 运行测试
RUN poetry run pytest tests/ --cov=src --cov-report=xml

# 构建阶段
FROM prod-deps as build

# 复制源代码
COPY src/ ./src/
COPY README.md LICENSE pyproject.toml ./

# 构建包
RUN poetry build

# 生产阶段
FROM python:3.11-slim as production

# 创建用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

# 安装运行时依赖
RUN apt-get update && apt-get install -y \
    curl \
    && rm -rf /var/lib/apt/lists/* \
    && apt-get clean

# 设置工作目录
WORKDIR /app

# 从构建阶段复制虚拟环境
COPY --from=prod-deps /app/.venv /app/.venv

# 复制应用代码
COPY --from=build /app/src ./src
COPY --from=build /app/pyproject.toml ./

# 设置环境变量
ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONPATH="/app"

# 创建必要的目录
RUN mkdir -p /app/logs /app/data \
    && chown -R appuser:appuser /app

# 切换到非root用户
USER appuser

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

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["python", "-m", "src.main"]

# 开发阶段(可选)
FROM dev-deps as development

# 复制源代码
COPY . .

# 安装项目
RUN poetry install

# 设置开发环境变量
ENV FLASK_ENV=development \
    FLASK_DEBUG=1

# 启动开发服务器
CMD ["poetry", "run", "python", "-m", "src.main", "--reload"]

针对不同环境的构建目标

# 构建脚本
#!/bin/bash
# build.sh

set -e

# 默认参数
TARGET="production"
TAG="latest"
REGISTRY=""

# 解析命令行参数
while [[ $# -gt 0 ]]; do
    case $1 in
        --target)
            TARGET="$2"
            shift 2
            ;;
        --tag)
            TAG="$2"
            shift 2
            ;;
        --registry)
            REGISTRY="$2"
            shift 2
            ;;
        *)
            echo "未知参数: $1"
            exit 1
            ;;
    esac
done

# 构建镜像
echo "构建目标: $TARGET"
echo "标签: $TAG"

case $TARGET in
    development)
        docker build --target development -t myapp:dev-$TAG .
        ;;
    test)
        docker build --target test -t myapp:test-$TAG .
        ;;
    production)
        docker build --target production -t myapp:$TAG .
        if [ -n "$REGISTRY" ]; then
            docker tag myapp:$TAG $REGISTRY/myapp:$TAG
            docker push $REGISTRY/myapp:$TAG
        fi
        ;;
    *)
        echo "无效的构建目标: $TARGET"
        exit 1
        ;;
esac

echo "构建完成!"

Docker Compose配置

开发环境配置

# docker-compose.dev.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: development
    ports:
      - "8000:8000"
    volumes:
      - .:/app
      - poetry_cache:/opt/poetry_cache
    environment:
      - ENV=development
      - DEBUG=true
      - DATABASE_URL=postgresql://user:password@db:5432/myapp_dev
      - REDIS_URL=redis://redis:6379/0
    depends_on:
      - db
      - redis
    command: poetry run python -m src.main --reload

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp_dev
      POSTGRES_USER: user
      POSTGRES_PASSWORD: password
    ports:
      - "5432:5432"
    volumes:
      - postgres_dev_data:/var/lib/postgresql/data

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"

  mailhog:
    image: mailhog/mailhog
    ports:
      - "1025:1025"
      - "8025:8025"

volumes:
  postgres_dev_data:
  poetry_cache:

生产环境配置

# docker-compose.prod.yml
version: '3.8'

services:
  app:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    ports:
      - "8000:8000"
    environment:
      - ENV=production
      - DATABASE_URL=${DATABASE_URL}
      - REDIS_URL=${REDIS_URL}
      - SECRET_KEY=${SECRET_KEY}
    depends_on:
      - db
      - redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: ${POSTGRES_DB}
      POSTGRES_USER: ${POSTGRES_USER}
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backups:/backups
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    command: redis-server --appendonly yes
    volumes:
      - redis_data:/data
    restart: unless-stopped

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - app
    restart: unless-stopped

volumes:
  postgres_data:
  redis_data:

测试环境配置

# docker-compose.test.yml
version: '3.8'

services:
  test:
    build:
      context: .
      dockerfile: Dockerfile
      target: test
    environment:
      - ENV=test
      - DATABASE_URL=postgresql://test:test@test_db:5432/test_db
    depends_on:
      - test_db
    command: poetry run pytest tests/ --cov=src --cov-report=xml

  test_db:
    image: postgres:15
    environment:
      POSTGRES_DB: test_db
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
    tmpfs:
      - /var/lib/postgresql/data

  integration_test:
    build:
      context: .
      dockerfile: Dockerfile
      target: development
    environment:
      - ENV=test
      - DATABASE_URL=postgresql://test:test@test_db:5432/test_db
      - REDIS_URL=redis://test_redis:6379/0
    depends_on:
      - test_db
      - test_redis
    command: poetry run pytest tests/integration/ -v

  test_redis:
    image: redis:7-alpine
    command: redis-server --save ""

镜像优化策略

大小优化

# 优化镜像大小的Dockerfile
FROM python:3.11-alpine as base

# 安装系统依赖(Alpine版本)
RUN apk add --no-cache \
    gcc \
    musl-dev \
    libffi-dev \
    openssl-dev \
    curl

# Poetry安装
FROM base as poetry
RUN pip install poetry==1.7.1
ENV POETRY_NO_INTERACTION=1 \
    POETRY_VENV_IN_PROJECT=1

# 依赖安装
FROM poetry as deps
WORKDIR /app
COPY pyproject.toml poetry.lock ./
RUN poetry install --only=main --no-root \
    && find /app/.venv -name "*.pyc" -delete \
    && find /app/.venv -name "__pycache__" -type d -exec rm -rf {} + \
    && rm -rf /root/.cache

# 最终镜像
FROM python:3.11-alpine as production

# 只安装运行时依赖
RUN apk add --no-cache curl

# 创建用户
RUN addgroup -g 1000 appuser && \
    adduser -D -u 1000 -G appuser appuser

WORKDIR /app

# 复制虚拟环境和代码
COPY --from=deps /app/.venv /app/.venv
COPY src/ ./src/

# 设置权限
RUN chown -R appuser:appuser /app
USER appuser

ENV PATH="/app/.venv/bin:$PATH"
EXPOSE 8000
CMD ["python", "-m", "src.main"]

构建时间优化

# 构建时间优化的Dockerfile
FROM python:3.11-slim as base

# 使用BuildKit缓存挂载
FROM base as poetry
RUN --mount=type=cache,target=/root/.cache/pip \
    pip install poetry==1.7.1

FROM poetry as deps
WORKDIR /app

# 利用Docker层缓存
COPY pyproject.toml poetry.lock ./

# 使用Poetry缓存挂载
RUN --mount=type=cache,target=/root/.cache/pypoetry \
    poetry install --only=main --no-root

# 并行构建优化
FROM deps as app
COPY src/ ./src/
RUN poetry install --only=main

# 最终镜像
FROM python:3.11-slim as production
COPY --from=app /app/.venv /app/.venv
COPY --from=app /app/src /app/src
WORKDIR /app
ENV PATH="/app/.venv/bin:$PATH"
CMD ["python", "-m", "src.main"]

安全优化

# 安全优化的Dockerfile
FROM python:3.11-slim as base

# 更新系统包
RUN apt-get update && apt-get upgrade -y \
    && apt-get install -y --no-install-recommends \
    curl \
    && rm -rf /var/lib/apt/lists/*

# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser appuser

FROM base as poetry
USER root
RUN pip install poetry==1.7.1
USER appuser

FROM poetry as deps
WORKDIR /app
USER root
COPY pyproject.toml poetry.lock ./
RUN poetry install --only=main --no-root
USER appuser

FROM python:3.11-slim as production

# 安全更新
RUN apt-get update && apt-get upgrade -y \
    && apt-get install -y --no-install-recommends curl \
    && rm -rf /var/lib/apt/lists/* \
    && groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app

# 复制文件并设置权限
COPY --from=deps --chown=appuser:appuser /app/.venv /app/.venv
COPY --chown=appuser:appuser src/ ./src/

# 移除不必要的权限
RUN chmod -R 755 /app && \
    find /app -type f -exec chmod 644 {} \;

USER appuser

# 安全配置
ENV PATH="/app/.venv/bin:$PATH" \
    PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1

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

EXPOSE 8000
CMD ["python", "-m", "src.main"]

容器编排

Kubernetes部署

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: myapp

---
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
  namespace: myapp
data:
  ENV: "production"
  LOG_LEVEL: "INFO"
  WORKERS: "4"

---
# k8s/secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  namespace: myapp
type: Opaque
data:
  DATABASE_URL: <base64-encoded-url>
  SECRET_KEY: <base64-encoded-key>

---
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
  namespace: myapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
  template:
    metadata:
      labels:
        app: myapp
    spec:
      containers:
      - name: myapp
        image: myregistry/myapp:latest
        ports:
        - containerPort: 8000
        env:
        - name: ENV
          valueFrom:
            configMapKeyRef:
              name: myapp-config
              key: ENV
        - name: DATABASE_URL
          valueFrom:
            secretKeyRef:
              name: myapp-secrets
              key: DATABASE_URL
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8000
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8000
          initialDelaySeconds: 5
          periodSeconds: 5

---
# k8s/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-service
  namespace: myapp
spec:
  selector:
    app: myapp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 8000
  type: ClusterIP

---
# k8s/ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: myapp
  annotations:
    kubernetes.io/ingress.class: nginx
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  tls:
  - hosts:
    - myapp.example.com
    secretName: myapp-tls
  rules:
  - host: myapp.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: myapp-service
            port:
              number: 80

Docker Swarm部署

# docker-stack.yml
version: '3.8'

services:
  app:
    image: myregistry/myapp:latest
    ports:
      - "8000:8000"
    environment:
      - ENV=production
    secrets:
      - database_url
      - secret_key
    configs:
      - source: app_config
        target: /app/config.toml
    deploy:
      replicas: 3
      update_config:
        parallelism: 1
        delay: 10s
        failure_action: rollback
      restart_policy:
        condition: on-failure
        delay: 5s
        max_attempts: 3
      resources:
        limits:
          memory: 512M
          cpus: '0.5'
        reservations:
          memory: 256M
          cpus: '0.25'
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s
    networks:
      - app_network

  db:
    image: postgres:15
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: user
      POSTGRES_PASSWORD_FILE: /run/secrets/db_password
    secrets:
      - db_password
    volumes:
      - postgres_data:/var/lib/postgresql/data
    deploy:
      replicas: 1
      placement:
        constraints:
          - node.role == manager
    networks:
      - app_network

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
      - "443:443"
    configs:
      - source: nginx_config
        target: /etc/nginx/nginx.conf
    deploy:
      replicas: 2
    networks:
      - app_network

networks:
  app_network:
    driver: overlay

volumes:
  postgres_data:

secrets:
  database_url:
    external: true
  secret_key:
    external: true
  db_password:
    external: true

configs:
  app_config:
    external: true
  nginx_config:
    external: true

开发工作流

本地开发环境

#!/bin/bash
# dev-setup.sh

set -e

echo "设置本地开发环境..."

# 构建开发镜像
docker-compose -f docker-compose.dev.yml build

# 启动服务
docker-compose -f docker-compose.dev.yml up -d

# 等待服务启动
echo "等待服务启动..."
sleep 10

# 运行数据库迁移
docker-compose -f docker-compose.dev.yml exec app poetry run alembic upgrade head

# 创建测试数据
docker-compose -f docker-compose.dev.yml exec app poetry run python scripts/create_test_data.py

echo "开发环境已就绪!"
echo "应用地址: http://localhost:8000"
echo "数据库地址: localhost:5432"
echo "邮件测试: http://localhost:8025"

热重载开发

# Dockerfile.dev
FROM python:3.11-slim

WORKDIR /app

# 安装Poetry
RUN pip install poetry==1.7.1

# 配置Poetry
ENV POETRY_NO_INTERACTION=1 \
    POETRY_VENV_IN_PROJECT=1

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 安装依赖(包括开发依赖)
RUN poetry install

# 安装开发工具
RUN poetry add --group dev watchdog[watchmedo]

# 复制源代码(在运行时通过volume挂载)
COPY . .

# 启动开发服务器
CMD ["poetry", "run", "watchmedo", "auto-restart", "--directory=./src", "--pattern=*.py", "--recursive", "--", "python", "-m", "src.main"]

测试工作流

#!/bin/bash
# test.sh

set -e

echo "运行测试套件..."

# 构建测试镜像
docker-compose -f docker-compose.test.yml build

# 运行单元测试
echo "运行单元测试..."
docker-compose -f docker-compose.test.yml run --rm test

# 运行集成测试
echo "运行集成测试..."
docker-compose -f docker-compose.test.yml run --rm integration_test

# 运行端到端测试
echo "运行端到端测试..."
docker-compose -f docker-compose.test.yml run --rm e2e_test

# 清理
docker-compose -f docker-compose.test.yml down -v

echo "所有测试完成!"

生产部署策略

蓝绿部署

#!/bin/bash
# blue-green-deploy.sh

set -e

REGISTRY="myregistry.com"
APP_NAME="myapp"
NEW_VERSION=$1

if [ -z "$NEW_VERSION" ]; then
    echo "使用方法: $0 <version>"
    exit 1
fi

# 获取当前活跃环境
CURRENT_ENV=$(docker service ls --filter name=${APP_NAME} --format "{{.Name}}" | grep -E "(blue|green)" | head -1)

if [[ $CURRENT_ENV == *"blue"* ]]; then
    NEW_ENV="green"
    OLD_ENV="blue"
else
    NEW_ENV="blue"
    OLD_ENV="green"
fi

echo "当前环境: $OLD_ENV"
echo "新环境: $NEW_ENV"
echo "部署版本: $NEW_VERSION"

# 部署新版本到新环境
echo "部署到 $NEW_ENV 环境..."
docker service create \
    --name ${APP_NAME}-${NEW_ENV} \
    --replicas 3 \
    --network app_network \
    --label environment=$NEW_ENV \
    ${REGISTRY}/${APP_NAME}:${NEW_VERSION}

# 等待新环境就绪
echo "等待新环境就绪..."
sleep 30

# 健康检查
echo "执行健康检查..."
for i in {1..10}; do
    if curl -f http://${APP_NAME}-${NEW_ENV}:8000/health; then
        echo "健康检查通过"
        break
    fi
    if [ $i -eq 10 ]; then
        echo "健康检查失败,回滚部署"
        docker service rm ${APP_NAME}-${NEW_ENV}
        exit 1
    fi
    sleep 10
done

# 切换流量
echo "切换流量到新环境..."
docker service update \
    --label-add active=true \
    ${APP_NAME}-${NEW_ENV}

docker service update \
    --label-rm active \
    ${APP_NAME}-${OLD_ENV}

# 更新负载均衡器配置
echo "更新负载均衡器..."
# 这里需要根据实际的负载均衡器进行配置

# 等待流量切换完成
sleep 30

# 移除旧环境
echo "移除旧环境..."
docker service rm ${APP_NAME}-${OLD_ENV}

echo "部署完成!"

滚动更新

#!/bin/bash
# rolling-update.sh

set -e

REGISTRY="myregistry.com"
APP_NAME="myapp"
NEW_VERSION=$1

if [ -z "$NEW_VERSION" ]; then
    echo "使用方法: $0 <version>"
    exit 1
fi

echo "开始滚动更新到版本: $NEW_VERSION"

# 更新Docker服务
docker service update \
    --image ${REGISTRY}/${APP_NAME}:${NEW_VERSION} \
    --update-parallelism 1 \
    --update-delay 30s \
    --update-failure-action rollback \
    --update-monitor 60s \
    ${APP_NAME}

# 监控更新状态
echo "监控更新进度..."
while true; do
    STATUS=$(docker service inspect ${APP_NAME} --format '{{.UpdateStatus.State}}')
    case $STATUS in
        "completed")
            echo "更新完成!"
            break
            ;;
        "rollback_completed")
            echo "更新失败,已回滚"
            exit 1
            ;;
        "updating")
            echo "更新中..."
            sleep 10
            ;;
        *)
            echo "未知状态: $STATUS"
            sleep 5
            ;;
    esac
done

# 验证部署
echo "验证部署..."
docker service ps ${APP_NAME}

监控和日志

容器监控

# monitoring/docker-compose.monitoring.yml
version: '3.8'

services:
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9090:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
      - '--web.console.libraries=/etc/prometheus/console_libraries'
      - '--web.console.templates=/etc/prometheus/consoles'

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
      - ./grafana/dashboards:/etc/grafana/provisioning/dashboards
      - ./grafana/datasources:/etc/grafana/provisioning/datasources

  cadvisor:
    image: gcr.io/cadvisor/cadvisor:latest
    ports:
      - "8080:8080"
    volumes:
      - /:/rootfs:ro
      - /var/run:/var/run:rw
      - /sys:/sys:ro
      - /var/lib/docker/:/var/lib/docker:ro

  node-exporter:
    image: prom/node-exporter:latest
    ports:
      - "9100:9100"
    volumes:
      - /proc:/host/proc:ro
      - /sys:/host/sys:ro
      - /:/rootfs:ro
    command:
      - '--path.procfs=/host/proc'
      - '--path.sysfs=/host/sys'
      - '--collector.filesystem.ignored-mount-points=^/(sys|proc|dev|host|etc)($$|/)'

volumes:
  prometheus_data:
  grafana_data:

日志聚合

# logging/docker-compose.logging.yml
version: '3.8'

services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:8.5.0
    environment:
      - discovery.type=single-node
      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
      - xpack.security.enabled=false
    ports:
      - "9200:9200"
    volumes:
      - elasticsearch_data:/usr/share/elasticsearch/data

  logstash:
    image: docker.elastic.co/logstash/logstash:8.5.0
    ports:
      - "5000:5000"
    volumes:
      - ./logstash.conf:/usr/share/logstash/pipeline/logstash.conf
    depends_on:
      - elasticsearch

  kibana:
    image: docker.elastic.co/kibana/kibana:8.5.0
    ports:
      - "5601:5601"
    environment:
      - ELASTICSEARCH_HOSTS=http://elasticsearch:9200
    depends_on:
      - elasticsearch

  filebeat:
    image: docker.elastic.co/beats/filebeat:8.5.0
    volumes:
      - ./filebeat.yml:/usr/share/filebeat/filebeat.yml
      - /var/lib/docker/containers:/var/lib/docker/containers:ro
      - /var/run/docker.sock:/var/run/docker.sock:ro
    depends_on:
      - logstash

volumes:
  elasticsearch_data:

安全最佳实践

镜像安全扫描

#!/bin/bash
# security-scan.sh

set -e

IMAGE_NAME=$1

if [ -z "$IMAGE_NAME" ]; then
    echo "使用方法: $0 <image_name>"
    exit 1
fi

echo "扫描镜像: $IMAGE_NAME"

# 使用Trivy扫描
echo "运行Trivy安全扫描..."
trivy image --severity HIGH,CRITICAL $IMAGE_NAME

# 使用Clair扫描
echo "运行Clair安全扫描..."
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock \
    quay.io/coreos/clair-local-scan:latest \
    --clair-addr=http://clair:6060 \
    $IMAGE_NAME

# 使用Snyk扫描
echo "运行Snyk安全扫描..."
snyk container test $IMAGE_NAME

echo "安全扫描完成"

运行时安全

# security/docker-compose.security.yml
version: '3.8'

services:
  app:
    image: myapp:latest
    security_opt:
      - no-new-privileges:true
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    read_only: true
    tmpfs:
      - /tmp
      - /var/run
    user: "1000:1000"
    environment:
      - PYTHONDONTWRITEBYTECODE=1
      - PYTHONUNBUFFERED=1
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

实践练习

练习1:基础容器化

创建一个简单的FastAPI应用并容器化:

# src/main.py
from fastapi import FastAPI
import uvicorn

app = FastAPI(title="My Poetry App")

@app.get("/")
def read_root():
    return {"message": "Hello from Poetry Docker!"}

@app.get("/health")
def health_check():
    return {"status": "healthy"}

if __name__ == "__main__":
    uvicorn.run(app, host="0.0.0.0", port=8000)
# 练习Dockerfile
FROM python:3.11-slim

WORKDIR /app

# 安装Poetry
RUN pip install poetry==1.7.1

# 配置Poetry
ENV POETRY_NO_INTERACTION=1 \
    POETRY_VENV_IN_PROJECT=1

# 复制依赖文件
COPY pyproject.toml poetry.lock ./

# 安装依赖
RUN poetry install --only=main

# 复制应用代码
COPY src/ ./src/

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["poetry", "run", "python", "-m", "src.main"]

练习2:多阶段构建优化

优化上述Dockerfile,实现多阶段构建:

# 优化后的Dockerfile
FROM python:3.11-slim as base
# ... 实现多阶段构建

练习3:完整的开发环境

创建包含数据库、缓存、监控的完整开发环境:

# docker-compose.full.yml
version: '3.8'
# ... 完整的服务配置

总结

本章节详细介绍了Poetry项目的Docker容器化部署:

核心要点

  1. 容器化策略:

    • 多阶段构建优化
    • 镜像大小和安全优化
    • 构建时间优化
  2. 编排部署:

    • Docker Compose配置
    • Kubernetes部署
    • Docker Swarm集群
  3. 生产实践:

    • 蓝绿部署策略
    • 滚动更新机制
    • 监控和日志管理
  4. 安全考虑:

    • 镜像安全扫描
    • 运行时安全配置
    • 密钥管理策略

最佳实践

  1. 镜像优化:使用多阶段构建,最小化镜像大小
  2. 安全第一:非root用户运行,定期安全扫描
  3. 监控完善:健康检查、日志聚合、性能监控
  4. 自动化部署:CI/CD集成,自动化测试和部署

通过本章节的学习,您应该能够为Poetry项目构建高效、安全、可扩展的容器化部署方案。

下一步

在下一章节019-团队协作最佳实践中,我们将学习如何在团队环境中有效使用Poetry,包括代码规范、协作流程、版本管理和团队配置标准化。


更新时间: 2024-12-19 | 更新内容: 初始版本,包含完整的Docker容器化部署指南

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐