文章目录
-
- [1 镜像体积:被忽视的性能瓶颈](#1 镜像体积:被忽视的性能瓶颈)
- [2 多阶段构建:体积从 1.2GB 到 80MB](#2 多阶段构建:体积从 1.2GB 到 80MB)
- [3 基础镜像选型:slim 与 alpine 的权衡](#3 基础镜像选型:slim 与 alpine 的权衡)
- [4 .dockerignore:从源头减少构建上下文](#4 .dockerignore:从源头减少构建上下文)
- [5 安全底线:非 root 用户](#5 安全底线:非 root 用户)
- [6 健康检查:让容器自我感知状态](#6 健康检查:让容器自我感知状态)
- [7 信号处理与优雅退出](#7 信号处理与优雅退出)
- [8 docker-compose 编排](#8 docker-compose 编排)
- [9 镜像安全扫描](#9 镜像安全扫描)
- [10 实战:图书管理 API 的完整 Docker 化](#10 实战:图书管理 API 的完整 Docker 化)
- [11 总结](#11 总结)
1 镜像体积:被忽视的性能瓶颈
FROM python:3.12 在没有网络缓存的情况下首次拉取耗时可能超过 30 秒,镜像体积达到 900MB+------而其中 80% 的内容与实际运行的 Python 服务无关。镜像体积不是纯粹的美学问题,它直接影响三个生产指标:
python
# 三个指标的数学关系
镜像体积 → 拉取时间 → 冷启动延迟
镜像体积 → 存储成本(容器注册表)
镜像体积 → 安全攻击面(CVEs 数量与镜像大小正相关)
以一个 1.2GB 的镜像为例:冷启动拉取耗时约 8 分钟(10Mbps 网络),而优化后的 80MB 镜像只需 30 秒。这个差距在 Auto Scaling 场景(每次扩容都需拉取镜像)中会被无限放大。
2 多阶段构建:体积从 1.2GB 到 80MB
多阶段构建利用 Docker 的分层缓存机制,将"构建依赖"和"运行时依赖"分离:
dockerfile
# ── 阶段 1:构建器 ──────────────────────────────────────────
FROM python:3.12-slim AS builder
WORKDIR /build
# 只复制依赖文件,利用层缓存
COPY requirements.txt .
RUN pip install \
--no-cache-dir \
--target=/build/site-packages \
-r requirements.txt
# ── 阶段 2:运行时(最终镜像)────────────────────────────────
FROM python:3.12-slim AS runtime
# 创建非 root 用户
RUN useradd --create-home --shell /bin/bash appuser
WORKDIR /home/appuser
# 只复制构建产物,不复制源码和构建工具
COPY --from=builder /build/site-packages ./site-packages
# 设置 PYTHONPATH,指向自定义 site-packages
ENV PYTHONPATH=/home/appuser/site-packages
# 复制源码
COPY --chown=appuser:appuser ./app ./app
USER appuser
# 健康检查
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"
EXPOSE 8000
CMD ["python", "-m", "app.main"]
关键优化点分析:
| 优化项 | 作用 | 效果 |
|---|---|---|
--target=/build/site-packages |
不在运行时镜像中安装 pip,只复制编译好的包 | 体积 ↓ 50% |
python:3.12-slim vs python:3.12 |
slim 不含开发工具链和文档 | 基础镜像 ↓ 70% |
--chown=appuser:appuser |
应用代码由非 root 用户拥有 | 安全加固 |
--no-cache-dir |
pip 不在镜像中缓存下载的 wheel | 体积再 ↓ 10% |
3 基础镜像选型:slim 与 alpine 的权衡
#mermaid-svg-3xtm9SJBtDQLVE0l{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-3xtm9SJBtDQLVE0l .error-icon{fill:#552222;}#mermaid-svg-3xtm9SJBtDQLVE0l .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-3xtm9SJBtDQLVE0l .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-3xtm9SJBtDQLVE0l .marker{fill:#333333;stroke:#333333;}#mermaid-svg-3xtm9SJBtDQLVE0l .marker.cross{stroke:#333333;}#mermaid-svg-3xtm9SJBtDQLVE0l svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-3xtm9SJBtDQLVE0l p{margin:0;}#mermaid-svg-3xtm9SJBtDQLVE0l .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l .cluster-label text{fill:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l .cluster-label span{color:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l .cluster-label span p{background-color:transparent;}#mermaid-svg-3xtm9SJBtDQLVE0l .label text,#mermaid-svg-3xtm9SJBtDQLVE0l span{fill:#333;color:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l .node rect,#mermaid-svg-3xtm9SJBtDQLVE0l .node circle,#mermaid-svg-3xtm9SJBtDQLVE0l .node ellipse,#mermaid-svg-3xtm9SJBtDQLVE0l .node polygon,#mermaid-svg-3xtm9SJBtDQLVE0l .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-3xtm9SJBtDQLVE0l .rough-node .label text,#mermaid-svg-3xtm9SJBtDQLVE0l .node .label text,#mermaid-svg-3xtm9SJBtDQLVE0l .image-shape .label,#mermaid-svg-3xtm9SJBtDQLVE0l .icon-shape .label{text-anchor:middle;}#mermaid-svg-3xtm9SJBtDQLVE0l .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-3xtm9SJBtDQLVE0l .rough-node .label,#mermaid-svg-3xtm9SJBtDQLVE0l .node .label,#mermaid-svg-3xtm9SJBtDQLVE0l .image-shape .label,#mermaid-svg-3xtm9SJBtDQLVE0l .icon-shape .label{text-align:center;}#mermaid-svg-3xtm9SJBtDQLVE0l .node.clickable{cursor:pointer;}#mermaid-svg-3xtm9SJBtDQLVE0l .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-3xtm9SJBtDQLVE0l .arrowheadPath{fill:#333333;}#mermaid-svg-3xtm9SJBtDQLVE0l .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-3xtm9SJBtDQLVE0l .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-3xtm9SJBtDQLVE0l .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3xtm9SJBtDQLVE0l .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-3xtm9SJBtDQLVE0l .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3xtm9SJBtDQLVE0l .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-3xtm9SJBtDQLVE0l .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-3xtm9SJBtDQLVE0l .cluster text{fill:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l .cluster span{color:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-3xtm9SJBtDQLVE0l .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-3xtm9SJBtDQLVE0l rect.text{fill:none;stroke-width:0;}#mermaid-svg-3xtm9SJBtDQLVE0l .icon-shape,#mermaid-svg-3xtm9SJBtDQLVE0l .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-3xtm9SJBtDQLVE0l .icon-shape p,#mermaid-svg-3xtm9SJBtDQLVE0l .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-3xtm9SJBtDQLVE0l .icon-shape .label rect,#mermaid-svg-3xtm9SJBtDQLVE0l .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-3xtm9SJBtDQLVE0l .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-3xtm9SJBtDQLVE0l .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-3xtm9SJBtDQLVE0l :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} FROM python:3.12
镜像体积: ~900MB
FROM python:3.12-slim
镜像体积: ~150MB
基础系统: Debian
兼容性: ✅ 最佳
FROM python:3.12-alpine
镜像体积: ~55MB
基础系统: Alpine musl
兼容性: ⚠️ 三个已知坑
坑1: musl 与 glibc 不兼容
PostgreSQL/MySQL 客户端库
多线程库可能异常
坑2: 无 gcc make
安装 C 扩展包(如 lxml,
Pillow, cryptography)失败
坑3: Alpine 默认时区 UTC
涉及时间计算时需手动
安装 tzdata
推荐用于生产
绕过: 使用 slim 或手动装 glibc
绕过: apk add gcc g++ musl-dev python3-dev libffi-dev
绕过: apk add tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
alpine 适用场景 :对体积极端敏感(边缘计算、IoT 设备)、且依赖的 Python 包全部为纯 Python 实现(无 C 扩展)。大多数情况下,python:3.12-slim 是更安全的选择。
4 .dockerignore:从源头减少构建上下文
docker build 会将构建上下文(Build Context,当前目录及其子目录)打包发送给 Docker daemon。上下文过大是构建卡顿的常见原因:
gitignore
# .dockerignore --- 与 .gitignore 类似的排除规则
# Git 仓库(通常数十 MB 到数百 MB)
.git
.gitignore
# Python 字节码
__pycache__
*.py[co]
*.egg-info
.eggs
# 虚拟环境
venv/
env/
.venv/
# 测试与文档
tests/
docs/
*.md
# Node.js(如果有前端)
node_modules/
# IDE 配置
.vscode/
.idea/
*.swp
# Docker 相关(避免递归包含)
Dockerfile
docker-compose*.yml
# 大文件
*.mp4
*.zip
*.tar.gz
data/
*.csv
*.parquet
一个常见误区是"只排除 .git 就够了"------实际上 __pycache__、.venv、.pytest_cache 这些目录在本地开发时积累很快,如果不排除,每次构建都会将它们打包发送。
5 安全底线:非 root 用户
默认情况下,容器以 root 身份运行。如果容器存在漏洞被攻破,攻击者获得的就是主机上的 root 权限:
dockerfile
# 错误示范:容器以 root 运行
FROM python:3.12
COPY . .
CMD ["python", "app.py"] # root 运行
# 正确示范:切换到普通用户
FROM python:3.12-slim
RUN useradd --create-home --shell /bin/bash --uid 1000 appuser
COPY --chown=appuser:appuser . /home/appuser/app
USER appuser
CMD ["python", "/home/appuser/app/app.py"]
注意 :服务端口号小于 1024(如 80、443)时,需要 root 权限绑定。此时可以在 Dockerfile 中使用
gosu或su-exec工具来"临时提升到 root 执行绑定操作,再降回普通用户"。
6 健康检查:让容器自我感知状态
HEALTHCHECK 指令告诉 Docker 如何判断容器是否"健康":
dockerfile
HEALTHCHECK --interval=30s \
--timeout=5s \
--start-period=30s \
--retries=3 \
CMD python -c "
import urllib.request
import os
try:
# 基础连通性
resp = urllib.request.urlopen('http://localhost:8000/health', timeout=3)
if resp.status != 200:
exit(1)
# 深度检查:数据库连通性
import psycopg2
conn = psycopg2.connect(os.environ['DATABASE_URL'])
conn.close()
except Exception:
exit(1)
"
/health 端点的实现不应只返回 HTTP 200------它应该检查下游依赖(数据库、Redis、外部 API),并在依赖不可用时返回非 200 状态码或主动退出:
python
# app/api/health.py
from fastapi import APIRouter, Response
import psycopg2, redis
router = APIRouter()
@router.get("/health")
def health_check(response: Response):
checks = {}
# 1. 数据库检查
try:
conn = psycopg2.connect(os.environ["DATABASE_URL"])
conn.close()
checks["database"] = "ok"
except Exception as e:
checks["database"] = f"error: {e}"
response.status_code = 503 # Service Unavailable
# 2. Redis 检查
try:
r = redis.from_url(os.environ["REDIS_URL"])
r.ping()
checks["redis"] = "ok"
except Exception as e:
checks["redis"] = f"error: {e}"
response.status_code = 503
return {"status": "ok" if response.status_code == 200 else "degraded", "checks": checks}
7 信号处理与优雅退出
当执行 docker stop 时,Docker 向容器内的 PID 1 进程发送 SIGTERM 信号。默认等待 10 秒后若进程未退出,则发送 SIGKILL 强制终止。如果 Python 进程收到 SIGTERM 时没有正确关闭数据库连接,就会产生"连接未正确归还连接池"的警告:
python
# app/main.py --- FastAPI 优雅退出
from contextlib import asynccontextmanager
import asyncio
@asynccontextmanager
async def lifespan(app):
# 启动时:预热连接池
await db_pool.initialize()
redis_client = await aioredis.connect(REDIS_URL)
yield
# 关闭时:等待中的请求继续处理,然后关闭资源
await asyncio.sleep(0.5) # 给在途请求 0.5 秒的缓冲时间
await db_pool.close()
await redis_client.close()
app = FastAPI(lifespan=lifespan)
对应的 Dockerfile 需要确保容器等待时间足够:
dockerfile
# 给予容器充足的优雅退出时间(默认 10s)
STOPSIGNAL SIGTERM
# docker-compose.yml 中可覆盖
# stop_grace_period: 30s
8 docker-compose 编排
对于本地开发和测试环境,docker-compose.yml 提供了声明式的多服务编排:
yaml
# docker-compose.yml
version: "3.9"
services:
# ── Python API 服务 ────────────────────────────────────
api:
build:
context: .
dockerfile: Dockerfile
target: runtime
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql+asyncpg://postgres:secret@db:5432/appdb
- REDIS_URL=redis://cache:6379/0
depends_on:
db:
condition: service_healthy # 等待数据库健康后再启动
cache:
condition: service_started # 只等待 Redis 启动
healthcheck:
test: ["CMD", "python", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"]
interval: 30s
timeout: 5s
retries: 3
restart: unless-stopped
# ── PostgreSQL 数据库 ──────────────────────────────────
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_PASSWORD: secret
volumes:
- postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
# ── Redis 缓存 ──────────────────────────────────────────
cache:
image: redis:7-alpine
volumes:
- redis_data:/data
command: redis-server --appendonly yes
restart: unless-stopped
volumes:
postgres_data:
redis_data:
关键配置说明:
depends_on+condition: service_healthy:数据库健康检查通过后才启动 API,避免 API 启动时数据库尚未就绪导致的连接失败。target: runtime:在多阶段构建中只构建runtime阶段,跳过builder阶段,减少构建产物暴露。restart: unless-stopped:容器退出后自动重启,但手动docker stop时不重启------这是生产环境的标准配置。
9 镜像安全扫描
镜像构建完成后,使用 trivy 扫描已知 CVE:
bash
# 安装 trivy
brew install trivy # macOS
# 或
wget https://github.com/aquasecurity/trivy/releases/latest/download/trivy_Linux-64bit.tar.gz
# 扫描镜像(推荐在 CI/CD 中运行)
trivy image --severity HIGH,CRITICAL \
--exit-code 1 \
--ignore-unfixed \
myregistry.io/app-api:latest
# 扫描结果示例:
# python:3.12-slim (debian 12.5)
# ==========================
# Total: 47 (HIGH: 12, CRITICAL: 3)
#
# CRITICAL: CVE-2024-XXXX openssl < 3.0.13 -> 升级基础镜像到 python:3.12.1
# CRITICAL: CVE-2024-YYYY libssl < 3.0.16 -> 同上
常见的 CVE 修复策略:
| CVE 类型 | 修复方式 |
|---|---|
| 基础镜像系统漏洞 | 升级 python:3.12-slim 到最新小版本 |
| Python 包漏洞 | pip-audit 扫描依赖,pip install --upgrade 修复 |
| 已知无法修复的漏洞 | trivy ignore --policy trivy-policy.yaml 文档化忽略原因 |
10 实战:图书管理 API 的完整 Docker 化
将 Python 实战系列 #01 的图书管理 API(FastAPI + SQLAlchemy + PostgreSQL)完整 Docker 化:
dockerfile
# Dockerfile
FROM python:3.12-slim AS builder
WORKDIR /build
COPY requirements.txt .
RUN pip install --no-cache-dir --target=/build/site-packages -r requirements.txt
FROM python:3.12-slim AS runtime
RUN apt-get update && apt-get install -y --no-install-recommends \
libpq5 \
&& rm -rf /var/lib/apt/lists/* \
&& useradd --create-home --shell /bin/bash appuser
WORKDIR /home/appuser
ENV PYTHONPATH=/home/appuser/site-packages
COPY --from=builder /build/site-packages ./site-packages
COPY --chown=appuser:appuser ./app ./app
COPY --chown=appuser:appuser ./alembic ./alembic
USER appuser
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
EXPOSE 8000
CMD ["python", "-m", "app.main"]
对应的 docker-compose.yml 启动完整技术栈:
bash
# 启动所有服务
docker compose up --build -d
# 查看服务状态
docker compose ps
# 查看 API 日志
docker compose logs -f api
# 初始化数据库(首次运行)
docker compose exec api alembic upgrade head
# 压测验证
docker compose exec api python -c "
import httpx, time
for i in range(100):
r = httpx.get('http://localhost:8000/books?page=1&page_size=10')
print(f'Status: {r.status_code}, Time: {r.elapsed.total_seconds()*1000:.1f}ms')
"
#mermaid-svg-66oymbUjLOlPDjvN{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-66oymbUjLOlPDjvN .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-66oymbUjLOlPDjvN .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-66oymbUjLOlPDjvN .error-icon{fill:#552222;}#mermaid-svg-66oymbUjLOlPDjvN .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-66oymbUjLOlPDjvN .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-66oymbUjLOlPDjvN .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-66oymbUjLOlPDjvN .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-66oymbUjLOlPDjvN .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-66oymbUjLOlPDjvN .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-66oymbUjLOlPDjvN .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-66oymbUjLOlPDjvN .marker{fill:#333333;stroke:#333333;}#mermaid-svg-66oymbUjLOlPDjvN .marker.cross{stroke:#333333;}#mermaid-svg-66oymbUjLOlPDjvN svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-66oymbUjLOlPDjvN p{margin:0;}#mermaid-svg-66oymbUjLOlPDjvN .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-66oymbUjLOlPDjvN .cluster-label text{fill:#333;}#mermaid-svg-66oymbUjLOlPDjvN .cluster-label span{color:#333;}#mermaid-svg-66oymbUjLOlPDjvN .cluster-label span p{background-color:transparent;}#mermaid-svg-66oymbUjLOlPDjvN .label text,#mermaid-svg-66oymbUjLOlPDjvN span{fill:#333;color:#333;}#mermaid-svg-66oymbUjLOlPDjvN .node rect,#mermaid-svg-66oymbUjLOlPDjvN .node circle,#mermaid-svg-66oymbUjLOlPDjvN .node ellipse,#mermaid-svg-66oymbUjLOlPDjvN .node polygon,#mermaid-svg-66oymbUjLOlPDjvN .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-66oymbUjLOlPDjvN .rough-node .label text,#mermaid-svg-66oymbUjLOlPDjvN .node .label text,#mermaid-svg-66oymbUjLOlPDjvN .image-shape .label,#mermaid-svg-66oymbUjLOlPDjvN .icon-shape .label{text-anchor:middle;}#mermaid-svg-66oymbUjLOlPDjvN .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-66oymbUjLOlPDjvN .rough-node .label,#mermaid-svg-66oymbUjLOlPDjvN .node .label,#mermaid-svg-66oymbUjLOlPDjvN .image-shape .label,#mermaid-svg-66oymbUjLOlPDjvN .icon-shape .label{text-align:center;}#mermaid-svg-66oymbUjLOlPDjvN .node.clickable{cursor:pointer;}#mermaid-svg-66oymbUjLOlPDjvN .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-66oymbUjLOlPDjvN .arrowheadPath{fill:#333333;}#mermaid-svg-66oymbUjLOlPDjvN .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-66oymbUjLOlPDjvN .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-66oymbUjLOlPDjvN .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-66oymbUjLOlPDjvN .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-66oymbUjLOlPDjvN .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-66oymbUjLOlPDjvN .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-66oymbUjLOlPDjvN .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-66oymbUjLOlPDjvN .cluster text{fill:#333;}#mermaid-svg-66oymbUjLOlPDjvN .cluster span{color:#333;}#mermaid-svg-66oymbUjLOlPDjvN div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-66oymbUjLOlPDjvN .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-66oymbUjLOlPDjvN rect.text{fill:none;stroke-width:0;}#mermaid-svg-66oymbUjLOlPDjvN .icon-shape,#mermaid-svg-66oymbUjLOlPDjvN .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-66oymbUjLOlPDjvN .icon-shape p,#mermaid-svg-66oymbUjLOlPDjvN .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-66oymbUjLOlPDjvN .icon-shape .label rect,#mermaid-svg-66oymbUjLOlPDjvN .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-66oymbUjLOlPDjvN .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-66oymbUjLOlPDjvN .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-66oymbUjLOlPDjvN :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 运行时阶段 (runtime)
构建阶段 (builder)
docker-compose 编排
service_healthy
api 服务
(FastAPI)
db 服务
(PostgreSQL)
cache 服务
(Redis)
requirements.txt
pip install
--target=/build/site-packages
编译好的 site-packages
python:3.12-slim
(~150MB)
COPY --from=builder
/build/site-packages
appuser 用户
非 root
EXPOSE 8000
HEALTHCHECK
11 总结
Python 服务的 Docker 化不是简单地把代码塞进容器------它涉及镜像体积优化、安全加固、健康检查、优雅退出等多个工程维度。
| 优化维度 | 关键做法 | 量化效果 |
|---|---|---|
| 镜像体积 | 多阶段构建 + slim 基础镜像 | 1.2GB → 80MB |
| 安全性 | 非 root 用户 + 镜像 CVE 扫描 | 消除容器逃逸 root 风险 |
| 可观测性 | 健康检查端点(DB+Redis 深度检查) | K8s 自动发现不健康 Pod |
| 启动顺序 | depends_on + healthcheck condition | 避免连接失败 race condition |
| 优雅退出 | SIGTERM 处理 + lifespan 上下文 | 连接池正确关闭,无泄漏 |
扩展阅读
- Docker 多阶段构建文档:https://docs.docker.com/build/building/multi-stage/
python:3.12-slim镜像构建历史:https://github.com/docker-library/python- OWASP Docker Security Cheat Sheet:https://cheatsheetseries.owasp.org/cheatsheets/Docker_Security_Cheat_Sheet.html
如果本文对你有帮助,欢迎点赞、关注!