容器化是现代部署的标配
还在用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
