容器化是现代部署的标配

还在用scp把jar包传到服务器上?还在手动配环境?容器化让部署变得可重复、可回滚、可扩展。但Dockerfile写不好,镜像1GB+;K8s配置不对,服务天天重启。

这篇文章用MonkeyCode生成完整的容器化部署方案,从Dockerfile优化到K8s上云。

给MonkeyCode的统一Prompt

为Python Web应用生成完整的容器化部署方案,要求:
1. 多阶段构建Dockerfile(最小镜像)
2. Docker Compose本地开发环境
3. Kubernetes部署YAML(Deployment + Service + Ingress)
4. 健康检查和就绪探针
5. 资源限制(CPU/内存)
6. 水平自动扩缩容(HPA)
7. ConfigMap和Secret管理
8. 滚动更新策略
9. 日志收集配置

应用:FastAPI后端 + Celery异步任务 + Redis + PostgreSQL

1. 多阶段构建Dockerfile

# Dockerfile - MonkeyCode生成

# ===== 阶段1:构建依赖 =====
FROM python:3.11-slim AS builder

WORKDIR /build

# 先复制依赖文件(利用Docker缓存层)
COPY requirements.txt .

# 安装依赖到虚拟环境
RUN python -m venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

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

# 安装系统运行时依赖(仅必需)
RUN apt-get update && \
    apt-get install -y --no-install-recommends \
    libpq5 \
    curl && \
    rm -rf /var/lib/apt/lists/*

# 从builder复制虚拟环境
COPY --from=builder /opt/venv /opt/venv
ENV PATH="/opt/venv/bin:$PATH"

# 创建非root用户
RUN groupadd -r appuser && useradd -r -g appuser -d /app -s /sbin/nologin appuser

WORKDIR /app

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

# 切换到非root用户
USER appuser

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

# 暴露端口
EXPOSE 8000

# 启动命令
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]

Dockerfile优化对比

优化项优化前优化后效果
基础镜像python:3.11 (1.02GB)python:3.11-slim (150MB)-85%
多阶段构建builder + runtime去除编译工具链
pip缓存保留--no-cache-dir-200MB
apt缓存保留rm -rf-50MB
运行用户rootappuser安全提升
最终镜像1.2GB180MB-85%

2. Docker Compose本地开发

# docker-compose.yml - MonkeyCode生成
version: '3.8'

services:
  # PostgreSQL数据库
  postgres:
    image: postgres:16-alpine
    environment:
      POSTGRES_DB: myapp
      POSTGRES_USER: appuser
      POSTGRES_PASSWORD: ${DB_PASSWORD:-devpassword}
    ports:
      - "5432:5432"
    volumes:
      - postgres-data:/var/lib/postgresql/data
      - ./scripts/init-db.sql:/docker-entrypoint-initdb.d/init.sql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U appuser -d myapp"]
      interval: 5s
      timeout: 5s
      retries: 5

  # Redis缓存
  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD:-devpassword} --maxmemory 256mb --maxmemory-policy allkeys-lru
    ports:
      - "6379:6379"
    volumes:
      - redis-data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD:-devpassword}", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  # FastAPI后端
  api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql+asyncpg://appuser:${DB_PASSWORD:-devpassword}@postgres:5432/myapp
      - REDIS_URL=redis://:${REDIS_PASSWORD:-devpassword}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-devpassword}@redis:6379/1
      - ENVIRONMENT=development
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
    volumes:
      - ./app:/app/app  # 开发时热重载
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

  # Celery Worker
  celery-worker:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - DATABASE_URL=postgresql+asyncpg://appuser:${DB_PASSWORD:-devpassword}@postgres:5432/myapp
      - REDIS_URL=redis://:${REDIS_PASSWORD:-devpassword}@redis:6379/0
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-devpassword}@redis:6379/1
    depends_on:
      redis:
        condition: service_healthy
      postgres:
        condition: service_healthy
    command: celery -A app.celery_app worker --loglevel=info --concurrency=4

  # Celery Beat(定时任务)
  celery-beat:
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-devpassword}@redis:6379/1
    depends_on:
      redis:
        condition: service_healthy
    command: celery -A app.celery_app beat --loglevel=info

  # Flower(Celery监控)
  flower:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "5555:5555"
    environment:
      - CELERY_BROKER_URL=redis://:${REDIS_PASSWORD:-devpassword}@redis:6379/1
    depends_on:
      redis:
        condition: service_healthy
    command: celery -A app.celery_app flower --port=5555

volumes:
  postgres-data:
  redis-data:

3. Kubernetes部署

# k8s/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
  name: myapp-production
  labels:
    environment: production
# k8s/configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: myapp-config
  namespace: myapp-production
data:
  ENVIRONMENT: "production"
  LOG_LEVEL: "info"
  DATABASE_HOST: "postgres-service"
  DATABASE_PORT: "5432"
  DATABASE_NAME: "myapp"
  REDIS_HOST: "redis-service"
  REDIS_PORT: "6379"
  REDIS_DB: "0"
  CELERY_BROKER_DB: "1"
  UVICORN_WORKERS: "4"
---
apiVersion: v1
kind: Secret
metadata:
  name: myapp-secrets
  namespace: myapp-production
type: Opaque
stringData:
  DATABASE_URL: "postgresql+asyncpg://appuser:CHANGE_ME@postgres-service:5432/myapp"
  REDIS_URL: "redis://:CHANGE_ME@redis-service:6379/0"
  CELERY_BROKER_URL: "redis://:CHANGE_ME@redis-service:6379/1"
  SECRET_KEY: "CHANGE_ME_TO_RANDOM_STRING"
# k8s/api-deployment.yaml - MonkeyCode生成
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-api
  namespace: myapp-production
  labels:
    app: myapp
    component: api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: myapp
      component: api
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1        # 滚动更新时最多多出1个Pod
      maxUnavailable: 0   # 更新期间不允许有Pod不可用
  template:
    metadata:
      labels:
        app: myapp
        component: api
    spec:
      containers:
        - name: api
          image: registry.example.com/myapp-api:latest
          ports:
            - containerPort: 8000
              protocol: TCP
          envFrom:
            - configMapRef:
                name: myapp-config
            - secretRef:
                name: myapp-secrets
          resources:
            requests:
              cpu: "250m"    # 0.25核
              memory: "256Mi"
            limits:
              cpu: "1000m"   # 1核
              memory: "512Mi"
          livenessProbe:
            httpGet:
              path: /health
              port: 8000
            initialDelaySeconds: 15
            periodSeconds: 20
            timeoutSeconds: 5
            failureThreshold: 3
          readinessProbe:
            httpGet:
              path: /health/ready
              port: 8000
            initialDelaySeconds: 5
            periodSeconds: 10
            timeoutSeconds: 3
            failureThreshold: 3
          volumeMounts:
            - name: tmp
              mountPath: /tmp
      volumes:
        - name: tmp
          emptyDir: {}
      terminationGracePeriodSeconds: 30
# k8s/api-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: myapp-api-service
  namespace: myapp-production
spec:
  selector:
    app: myapp
    component: api
  ports:
    - port: 80
      targetPort: 8000
      protocol: TCP
  type: ClusterIP
---
# Ingress(Nginx Ingress Controller)
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: myapp-ingress
  namespace: myapp-production
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/proxy-body-size: "50m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "300"
    nginx.ingress.kubernetes.io/rate-limit: "100"
    cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.myapp.com
      secretName: myapp-tls
  rules:
    - host: api.myapp.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: myapp-api-service
                port:
                  number: 80

4. 水平自动扩缩容(HPA)

# k8s/hpa.yaml - MonkeyCode生成
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: myapp-api-hpa
  namespace: myapp-production
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: myapp-api
  minReplicas: 3
  maxReplicas: 20
  metrics:
    # CPU使用率超过70%时扩容
    - type: Resource
      resource:
        name: cpu
        target:
          type: Utilization
          averageUtilization: 70
    # 内存使用率超过80%时扩容
    - type: Resource
      resource:
        name: memory
        target:
          type: Utilization
          averageUtilization: 80
    # 基于QPS自定义指标(需安装Prometheus Adapter)
    - type: Pods
      pods:
        metric:
          name: http_requests_per_second
        target:
          type: AverageValue
          averageValue: "1000"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 60
      policies:
        - type: Pods
          value: 2
          periodSeconds: 60
    scaleDown:
      stabilizationWindowSeconds: 300  # 缩容冷却5分钟
      policies:
        - type: Percent
          value: 10
          periodSeconds: 60

5. Celery Worker部署


更多推荐