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 + PostgreSQL1. 多阶段构建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: 804. 水平自动扩缩容(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