MonkeyCode容器化部署实战:从Dockerfile到Kubernetes上云

容器化是现代部署的标配

还在用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
运行用户 root appuser 安全提升
最终镜像 1.2GB 180MB -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部署

# k8s/celery-deployment.yaml - MonkeyCode生成
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-celery-worker
  namespace: myapp-production
  labels:
    app: myapp
    component: celery-worker
spec:
  replicas: 2
  selector:
    matchLabels:
      app: myapp
      component: celery-worker
  template:
    metadata:
      labels:
        app: myapp
        component: celery-worker
    spec:
      containers:
        - name: celery-worker
          image: registry.example.com/myapp-api:latest
          command: ["celery", "-A", "app.celery_app", "worker", "--loglevel=info", "--concurrency=4"]
          envFrom:
            - configMapRef:
                name: myapp-config
            - secretRef:
                name: myapp-secrets
          resources:
            requests:
              cpu: "500m"
              memory: "512Mi"
            limits:
              cpu: "2000m"
              memory: "1Gi"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp-celery-beat
  namespace: myapp-production
  labels:
    app: myapp
    component: celery-beat
spec:
  replicas: 1  # Beat只能运行1个副本
  selector:
    matchLabels:
      app: myapp
      component: celery-beat
  template:
    metadata:
      labels:
        app: myapp
        component: celery-beat
    spec:
      containers:
        - name: celery-beat
          image: registry.example.com/myapp-api:latest
          command: ["celery", "-A", "app.celery_app", "beat", "--loglevel=info"]
          envFrom:
            - configMapRef:
                name: myapp-config
            - secretRef:
                name: myapp-secrets

6. 数据库部署(StatefulSet)

# k8s/postgres-statefulset.yaml - MonkeyCode生成
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: postgres
  namespace: myapp-production
spec:
  serviceName: postgres-service
  replicas: 1  # 生产环境建议用云RDS
  selector:
    matchLabels:
      app: myapp
      component: postgres
  template:
    metadata:
      labels:
        app: myapp
        component: postgres
    spec:
      containers:
        - name: postgres
          image: postgres:16-alpine
          ports:
            - containerPort: 5432
          env:
            - name: POSTGRES_DB
              value: myapp
            - name: POSTGRES_USER
              value: appuser
            - name: POSTGRES_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: myapp-secrets
                  key: DB_PASSWORD
            - name: PGDATA
              value: /var/lib/postgresql/data/pgdata
          resources:
            requests:
              cpu: "500m"
              memory: "1Gi"
            limits:
              cpu: "2000m"
              memory: "4Gi"
          volumeMounts:
            - name: postgres-data
              mountPath: /var/lib/postgresql/data
          livenessProbe:
            exec:
              command: ["pg_isready", "-U", "appuser", "-d", "myapp"]
            initialDelaySeconds: 30
            periodSeconds: 10
          readinessProbe:
            exec:
              command: ["pg_isready", "-U", "appuser", "-d", "myapp"]
            initialDelaySeconds: 5
            periodSeconds: 5
  volumeClaimTemplates:
    - metadata:
        name: postgres-data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 50Gi
---
apiVersion: v1
kind: Service
metadata:
  name: postgres-service
  namespace: myapp-production
spec:
  selector:
    app: myapp
    component: postgres
  ports:
    - port: 5432
      targetPort: 5432
  clusterIP: None  # Headless Service(StatefulSet用)

7. CI/CD流水线(GitHub Actions → K8s)

# .github/workflows/deploy.yml - MonkeyCode生成
name: Build and Deploy

on:
  push:
    branches: [main]

env:
  REGISTRY: registry.example.com
  IMAGE_NAME: myapp-api

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - run: pip install -r requirements.txt
      - run: pytest tests/ -v --cov=app --cov-report=xml
      - uses: codecov/codecov-action@v3

  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Login to Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.REGISTRY }}
          username: ${{ secrets.REGISTRY_USERNAME }}
          password: ${{ secrets.REGISTRY_PASSWORD }}
      
      - name: Build and Push
        uses: docker/build-push-action@v5
        with:
          context: .
          push: true
          tags: |
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
            ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-from: type=registry,ref=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
          cache-to: type=inline

  deploy:
    needs: build
    runs-on: ubuntu-latest
    environment: production
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to K8s
        uses: steebchen/kubectl@v2.1.1
        with:
          config: ${{ secrets.KUBE_CONFIG }}
          command: set image deployment/myapp-api api=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} -n myapp-production
      
      - name: Verify Deployment
        run: |
          kubectl rollout status deployment/myapp-api -n myapp-production --timeout=300s
          kubectl get pods -n myapp-production -l app=myapp,component=api

8. 一键部署脚本

#!/bin/bash
# scripts/deploy.sh - MonkeyCode生成

set -euo pipefail

ENV=${1:-production}
IMAGE_TAG=${2:-latest}

echo " Deploying myapp to $ENV (tag: $IMAGE_TAG)"

# 创建命名空间
kubectl apply -f k8s/namespace.yaml

# 应用配置
kubectl apply -f k8s/configmap.yaml

# 应用数据库
kubectl apply -f k8s/postgres-statefulset.yaml

# 等待数据库就绪
echo "⏳ Waiting for PostgreSQL..."
kubectl wait --for=condition=ready pod -l app=myapp,component=postgres -n myapp-$ENV --timeout=120s

# 应用API
kubectl set image deployment/myapp-api api=registry.example.com/myapp-api:$IMAGE_TAG -n myapp-$ENV
kubectl rollout status deployment/myapp-api -n myapp-$ENV --timeout=300s

# 应用Celery
kubectl set image deployment/myapp-celery-worker celery-worker=registry.example.com/myapp-api:$IMAGE_TAG -n myapp-$ENV

# 应用HPA
kubectl apply -f k8s/hpa.yaml

echo " Deployment complete!"
kubectl get all -n myapp-$ENV

容器化部署的关键是分层设计:Dockerfile多阶段构建减小镜像,K8s分层配置(ConfigMap/Secret分离),HPA自动扩缩容应对流量。MonkeyCode能生成完整的K8s YAML模板,但资源限制和HPA阈值需要根据实际负载调优。

文章摘自:https://www.cnblogs.com/jaryn/p/20218903