第 19 章 · Docker 生产交付、K8s 入门与 FastAPI 对比
本章目标:为 api-demo 编写多阶段 Dockerfile、.dockerignore 与非 root 用户;使用 docker-compose.prod.yml 编排 app + Gunicorn + Nginx + PostgreSQL + Redis;理解 K8s 核心对象并阅读虚构 api.example.com YAML;说明与 xiaozi-cloud 衔接;全面对照 FastAPI vs Flask;回答面试框架选型;完成 19 章结业总结与 100 分进阶验收表(选修)。
学时建议:6~8 小时(含 3 小时容器演练)
前置:ch11 Gunicorn/Nginx;ch18 可观测性;ch10 Redis/Celery。K8s 深度见 xiaozi-cloud。
19.1 从「能跑」到「可交付」
源码 ──docker build──► registry.example.com/api-demo:1.0.0 ──pull──► K8s / VM
api.example.com
| 交付物 | 说明 |
|---|---|
| Docker 镜像 | 不可变制品,含依赖与代码 |
| compose / K8s YAML | 进程拓扑与配置 |
.env.example | 文档化变量,不含密钥 |
虚构 api-demo、api.example.com、registry.example.com;严禁真实镜像地址、Kubeconfig、数据库密码。
19.2 多阶段 Dockerfile
api-demo/Dockerfile:
FROM python:3.12-slim AS builder
WORKDIR /build
RUN apt-get update && apt-get install -y --no-install-recommends gcc libpq-dev \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN python -m venv /opt/venv && /opt/venv/bin/pip install -r requirements.txt gunicorn
FROM python:3.12-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends libpq5 curl \
&& rm -rf /var/lib/apt/lists/* \
&& groupadd --gid 1000 app && useradd --uid 1000 --gid app --create-home app
WORKDIR /app
ENV PATH="/opt/venv/bin:$PATH" PYTHONUNBUFFERED=1 APP_ENV=production
COPY --from=builder /opt/venv /opt/venv
COPY --chown=app:app . .
USER app
EXPOSE 5000
HEALTHCHECK CMD curl -f http://127.0.0.1:5000/health || exit 1
CMD ["gunicorn", "wsgi:app", "-c", "gunicorn.conf.py"]
| 实践 | 原因 |
|---|---|
| 多阶段 | 最终镜像无 gcc,更小 |
USER app | 非 root,降权限 |
HEALTHCHECK | 编排器可重启不健康实例 |
docker build -t api-demo:1.0.0 .
docker run --rm -p 5000:5000 -e SECRET_KEY=dev-only api-demo:1.0.0
19.3 .dockerignore
.git .venv __pycache__ .pytest_cache .coverage
instance/*.db uploads/* .env .env.* !.env.example
docs/ tests/ *.md Dockerfile* docker-compose*.yml
勿将 .env 打进镜像;忽略测试/docs 加快构建。
19.4 docker-compose.prod.yml
api-demo/docker-compose.prod.yml:
services:
db:
image: postgres:16-alpine
environment:
POSTGRES_USER: api_demo
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-changeme-db}
POSTGRES_DB: api_demo
volumes: [pg_data:/var/lib/postgresql/data]
healthcheck:
test: ["CMD-SHELL", "pg_isready -U api_demo"]
interval: 10s
redis:
image: redis:7-alpine
command: redis-server --appendonly yes
volumes: [redis_data:/data]
app:
build: .
image: api-demo:1.0.0
depends_on:
db: {condition: service_healthy}
environment:
SECRET_KEY: ${SECRET_KEY}
DATABASE_URL: postgresql+psycopg2://api_demo:${POSTGRES_PASSWORD:-changeme-db}@db:5432/api_demo
REDIS_URL: redis://redis:6379/0
CELERY_BROKER_URL: redis://redis:6379/1
volumes: [uploads_data:/app/instance/uploads]
expose: ["5000"]
restart: unless-stopped
worker:
image: api-demo:1.0.0
command: celery -A api_demo.celery_app worker --loglevel=INFO
depends_on: [app, redis]
environment:
SECRET_KEY: ${SECRET_KEY}
DATABASE_URL: postgresql+psycopg2://api_demo:${POSTGRES_PASSWORD:-changeme-db}@db:5432/api_demo
CELERY_BROKER_URL: redis://redis:6379/1
restart: unless-stopped
nginx:
image: nginx:1.25-alpine
depends_on: [app]
ports: ["80:80", "443:443"]
volumes:
- ./deploy/nginx/api-demo.conf:/etc/nginx/conf.d/default.conf:ro
- ./deploy/certs:/etc/nginx/certs:ro
- uploads_data:/var/www/uploads:ro
restart: unless-stopped
volumes: {pg_data: {}, redis_data: {}, uploads_data: {}}
deploy/nginx/api-demo.conf 要点:
upstream api_demo_upstream { server app:5000; }
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/certs/api.example.com.pem;
ssl_certificate_key /etc/nginx/certs/api.example.com.key;
client_max_body_size 10m;
location /metrics { deny all; }
location / {
proxy_pass http://api_demo_upstream;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
}
}
cp .env.example .env
docker compose -f docker-compose.prod.yml up -d --build
| 服务 | 角色 |
|---|---|
| app | Gunicorn + Flask |
| worker | Celery(ch10) |
| nginx | TLS、反向代理、静态/上传 |
| db / redis | 持久化与缓存 |
19.5 Kubernetes 核心概念
Internet → Ingress(api.example.com) → Service → Deployment(3 Pods)
↑ ConfigMap / Secret
| 对象 | 作用 |
|---|---|
| Deployment | 镜像、副本数、滚动更新 |
| Service | 稳定虚拟 IP,负载到 Pod |
| Ingress | HTTP 路由、TLS、域名 |
| ConfigMap | 非敏感配置 |
| Secret | 敏感配置(勿明文进 Git) |
19.6 K8s YAML 示例(虚构)
deploy/k8s/configmap.yaml:
apiVersion: v1
kind: ConfigMap
metadata: {name: api-demo-config, namespace: api-demo-staging}
data: