第02章 容器化:Docker打包你的Agent
第02章 容器化:Docker打包你的Agent
“Build once, run anywhere — 但前提是你的 Dockerfile 写对了。” —— 容器化工程实践
Docker 是 Agent 生产化的起点。它解决了"在我机器上能跑"的经典问题,让你的 Agent 在开发、测试、生产环境中行为完全一致。但一个草率的 Dockerfile 会带来镜像过大、安全漏洞、启动慢等问题。本章告诉你怎么写对。
2.1 Dockerfile 最佳实践
# ====================================================
# Agent 生产级 Dockerfile(多阶段构建)
# ====================================================
# 阶段1:构建阶段(安装依赖)
# 使用 slim 版本减少基础镜像大小
FROM python:3.11-slim AS builder
# 安装构建依赖(只在构建阶段需要)
RUN apt-get update && apt-get install -y \
gcc \
&& rm -rf /var/lib/apt/lists/*
# 设置工作目录
WORKDIR /build
# 先复制依赖文件(利用层缓存:依赖不变时不重新安装)
COPY requirements.txt .
# 安装依赖到独立目录(方便复制到下一阶段)
RUN pip install --no-cache-dir --prefix=/install -r requirements.txt
# 阶段2:运行阶段(最终镜像)
FROM python:3.11-slim AS runner
# 安全实践:不以 root 用户运行
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
# 从构建阶段复制安装好的依赖
COPY --from=builder /install /usr/local
# 复制应用代码
COPY --chown=appuser:appuser app/ ./app/
# 切换到非 root 用户
USER appuser
# 健康检查(Kubernetes/Docker 会用这个判断容器是否健康)
HEALTHCHECK --interval=30s --timeout=10s --start-period=30s --retries=3 \
CMD python -c "import httpx; httpx.get('http://localhost:8000/health').raise_for_status()"
# 暴露端口
EXPOSE 8000
# 启动命令:使用 uvicorn 而非 python main.py
# --workers 1:容器内单进程,由 K8s 负责水平扩展
# --host 0.0.0.0:允许容器外访问
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "1"]
# ====================================================
# Python 代码:Dockerfile 分析和建议
# ====================================================
"""
常见 Dockerfile 反模式和修复方案:
❌ 反模式1:把所有东西装进一个阶段
FROM python:3.11
RUN pip install ... && apt-get install gcc ...
COPY . .
→ 镜像包含了 gcc、临时文件等不必要的东西,轻松 1GB+
✅ 修复:多阶段构建(如上所示)
构建阶段安装依赖,运行阶段只复制必要文件
→ 镜像从 1.2GB 缩到 200-400MB
❌ 反模式2:COPY . . 放在依赖安装之前
COPY . .
RUN pip install -r requirements.txt
→ 任何代码变更都会让 pip install 重新执行
✅ 修复:先 COPY requirements.txt,再 COPY 代码
利用 Docker 层缓存:依赖不变时,pip install 直接用缓存
❌ 反模式3:以 root 用户运行
(没有 USER 指令,默认是 root)
→ 如果应用被入侵,攻击者拿到 root 权限
✅ 修复:创建非特权用户并切换
❌ 反模式4:把密钥写进镜像
ENV OPENAI_API_KEY=sk-xxxxx
→ 密钥会保存在镜像层里,docker inspect 就能看到
✅ 修复:密钥通过运行时环境变量注入(第04章详细讲)
"""
import subprocess
import json
from dataclasses import dataclass
from typing import List, Tuple
@dataclass
class DockerImageMetrics:
"""Docker 镜像指标"""
image_name: str
size_mb: float
layers: int
has_root_user: bool
has_healthcheck: bool
def analyze_dockerfile(content: str) -> List[str]:
"""
分析 Dockerfile,返回改进建议
这是简化版,实际可以用 hadolint 等工具
"""
suggestions = []
lines = content.lower()
if "from python" in lines and "slim" not in lines and "alpine" not in lines:
suggestions.append("⚠️ 建议使用 python:x.y-slim 替代完整镜像,可减少 60% 镜像大小")
if "as builder" not in lines:
suggestions.append("⚠️ 建议使用多阶段构建(添加 AS builder 阶段)")
if "user " not in lines:
suggestions.append("⚠️ 添加非 root 用户(安全实践): RUN useradd -r appuser && USER appuser")
if "healthcheck" not in lines:
suggestions.append("⚠️ 添加 HEALTHCHECK 指令,用于容器编排的健康探测")
if "copy . ." in lines:
# 检查顺序
copy_all_pos = lines.find("copy . .")
requirements_pos = lines.find("requirements")
if requirements_pos > copy_all_pos:
suggestions.append("⚠️ COPY requirements.txt 应该在 COPY . . 之前,以利用层缓存")
if "--no-cache-dir" not in lines and "pip install" in lines:
suggestions.append("💡 pip install 添加 --no-cache-dir 标志,减少镜像大小")
if not suggestions:
suggestions.append("✅ Dockerfile 看起来不错!")
return suggestions
# 示例:分析一个有问题的 Dockerfile
bad_dockerfile = """
FROM python:3.11
COPY . .
RUN pip install -r requirements.txt
CMD python app.py
"""
print("分析 Dockerfile 问题:")
for suggestion in analyze_dockerfile(bad_dockerfile):
print(f" {suggestion}")
2.2 .dockerignore 和镜像优化
# ====================================================
# 镜像优化:.dockerignore 和层缓存
# ====================================================
"""
.dockerignore 内容(放在项目根目录):
.git
.gitignore
__pycache__
*.pyc
*.pyo
.pytest_cache
.venv
venv
env
.env
.env.* # 环境变量文件!不能进镜像
tests/ # 测试文件不需要在生产镜像里
docs/
*.md
Makefile
docker-compose*.yml
.DS_Store
【为什么要排除 .env 文件?】
如果 .env 文件进了 Docker 镜像,那么:
1. 所有能拉取这个镜像的人都能看到你的密钥
2. 镜像层是持久化的,即使删除 .env 后重新构建,
如果你重用了之前的层,旧版本中的密钥依然在镜像历史里
正确做法:密钥通过运行时注入(第04章),不进镜像。
"""
def generate_dockerignore() -> str:
"""生成标准的 .dockerignore 内容"""
return """\
# Python
__pycache__/
*.py[cod]
*.pyo
*.pyd
.Python
.venv/
venv/
env/
# 测试和开发工具
tests/
.pytest_cache/
.coverage
htmlcov/
.mypy_cache/
.ruff_cache/
# 密钥和配置(绝不进镜像)
.env
.env.*
*.secret
secrets/
# 版本控制
.git/
.gitignore
# 文档和元数据
*.md
docs/
Makefile
# IDE
.vscode/
.idea/
*.swp
# OS
.DS_Store
Thumbs.db
"""
# 演示:构建参数说明
BUILD_COMMANDS = {
"开发构建": "docker build -t my-agent:dev .",
"生产构建": "docker build --target runner -t my-agent:v1.0.0 .",
"查看镜像大小": "docker images my-agent",
"查看镜像层": "docker history my-agent:v1.0.0",
"安全扫描": "docker scout cves my-agent:v1.0.0", # Docker Scout
"运行容器": (
"docker run -p 8000:8000 "
"-e OPENAI_API_KEY=$OPENAI_API_KEY " # 运行时注入密钥
"--name my-agent "
"my-agent:v1.0.0"
),
}
print(".dockerignore 模板:")
print(generate_dockerignore())
print("\n常用 Docker 命令:")
for purpose, cmd in BUILD_COMMANDS.items():
print(f" [{purpose}] {cmd}")
本章小结
-
多阶段构建是生产 Dockerfile 的标配:构建阶段装编译工具和安装依赖,运行阶段只有运行时需要的文件。镜像大小从 1GB+ 降到 200-400MB,攻击面也大幅减少。
-
COPY 的顺序决定缓存的效率:先 COPY requirements.txt 并安装依赖,再 COPY 代码。这样代码变更时不会触发重新安装依赖,CI/CD 构建时间从分钟级降到秒级。
-
不以 root 用户运行容器:这是安全最佳实践,也是许多企业的强制要求。一行
USER appuser可以显著降低被入侵时的影响范围。 -
.env 文件绝对不能进 Docker 镜像:即使 Dockerfile 里看似删除了,Docker 层历史里仍然保留。密钥只能通过运行时环境变量注入,永远不要在构建时写入镜像。
-
HEALTHCHECK 是容器编排的必要配置:没有 HEALTHCHECK,Kubernetes/Docker Compose 不知道你的容器是否真的"健康"——进程活着不等于服务可用。
# 核心行动:用这个模板重写你的 Dockerfile
# 三步完成容器化改造:
# 1. 创建 Dockerfile(用本章多阶段构建模板)
# 2. 创建 .dockerignore(用本章模板)
# 3. 测试:docker build + docker run,验证功能正常
# 快速检验:
# docker image ls | grep your-agent # 查看镜像大小(目标<500MB)
# docker inspect --format='{{.Config.User}}' your-agent # 应该是 appuser,不是 root
本章提示词模板
【模板1:Dockerfile 生成提示词】
我需要为以下 Python 服务生成一个生产级的 Dockerfile:
服务类型:{service_type}(如 FastAPI Web 服务 / 定时任务 / 消息消费者)
Python 版本:{python_version}
依赖描述:{dependencies_description}
特殊要求:{special_requirements}(如需要系统库 / GPU 支持 / 特定文件权限)
请生成:
1. 多阶段构建的 Dockerfile(构建阶段 + 运行阶段)
2. 对应的 .dockerignore 文件
3. 启动命令(CMD)的建议(包括 workers 数量的建议)
4. 如果有特殊依赖需要系统包,请说明安装方法
5. 列出这个 Dockerfile 中做了哪些安全加固
【模板2:镜像优化诊断提示词】
我的 Docker 镜像有以下问题需要优化:
当前镜像大小:{image_size}
构建时间:{build_time}
已知问题:{known_issues}
Dockerfile 内容:
{dockerfile_content}
请帮我:
1. 找出导致镜像过大的主要原因
2. 提供具体的优化方案(改哪些行,怎么改)
3. 预估优化后的镜像大小
4. 有没有潜在的安全问题需要修复?
5. 如何验证优化后的镜像功能与之前完全一致?