Python API → Docker 容器化
将一个 FastAPI 应用从开发到容器化的完整流程,包含 Dockerfile 编写、Compose 编排、数据持久化
你将学到
- 开发一个 FastAPI 应用
- 编写 Dockerfile 容器化应用
- 使用 Docker Compose 编排多服务
- 实现数据持久化
前置知识
架构设计
┌─────────────────────────────────────┐
│ Docker Compose │
├─────────────────────────────────────┤
│ ┌─────────┐ ┌─────────────┐ │
│ │ FastAPI │ │ PostgreSQL │ │
│ │ :8000 │──────│ :5432 │ │
│ └─────────┘ └─────────────┘ │
│ │ │
│ ┌────┴────┐ │
│ │ Volume │ │
│ └─────────┘ │
└─────────────────────────────────────┘
实现步骤
第一步:FastAPI 应用
# app/main.py
from fastapi import FastAPI, Depends, HTTPException
from sqlalchemy.orm import Session
from pydantic import BaseModel
from typing import List
from datetime import datetime
from .database import get_db, engine, Base
from .models import Todo
Base.metadata.create_all(bind=engine)
app = FastAPI(title="Todo API")
class TodoCreate(BaseModel):
title: str
description: str = None
class TodoResponse(BaseModel):
id: int
title: str
description: str
completed: bool
created_at: datetime
class Config:
from_attributes = True
@app.get("/")
def root():
return {"message": "Todo API v1.0"}
@app.get("/health")
def health():
return {"status": "healthy"}
@app.post("/todos", response_model=TodoResponse)
def create_todo(todo: TodoCreate, db: Session = Depends(get_db)):
db_todo = Todo(title=todo.title, description=todo.description)
db.add(db_todo)
db.commit()
db.refresh(db_todo)
return db_todo
@app.get("/todos", response_model=List[TodoResponse])
def list_todos(skip: int = 0, limit: int = 10, db: Session = Depends(get_db)):
return db.query(Todo).offset(skip).limit(limit).all()
@app.put("/todos/{todo_id}", response_model=TodoResponse)
def update_todo(todo_id: int, db: Session = Depends(get_db)):
todo = db.query(Todo).filter(Todo.id == todo_id).first()
if not todo:
raise HTTPException(status_code=404, detail="Todo not found")
todo.completed = True
db.commit()
return todo
# app/database.py
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./app.db")
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# app/models.py
from sqlalchemy import Column, Integer, String, Boolean, DateTime
from sqlalchemy.sql import func
from .database import Base
class Todo(Base):
__tablename__ = "todos"
id = Column(Integer, primary_key=True, index=True)
title = Column(String, nullable=False)
description = Column(String)
completed = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
第二步:依赖文件
# requirements.txt
fastapi==0.109.0
uvicorn==0.27.0
sqlalchemy==2.0.25
psycopg2-binary==2.9.9
第三步:Dockerfile
# Dockerfile
FROM python:3.11-slim AS builder
WORKDIR /app
COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt
FROM python:3.11-slim
RUN groupadd -r appuser && useradd -r -g appuser appuser
WORKDIR /app
COPY --from=builder /root/.local /home/appuser/.local
COPY --chown=appuser:appuser . .
USER appuser
ENV PATH=/home/appuser/.local/bin:$PATH
EXPOSE 8000
HEALTHCHECK --interval=30s --timeout=3s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')" || exit 1
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
第四步:Docker Compose
# docker-compose.yml
version: "3.8"
services:
api:
build: .
ports:
- "8000:8000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/todoapp
depends_on:
db:
condition: service_healthy
restart: unless-stopped
db:
image: postgres:15-alpine
environment:
- POSTGRES_DB=todoapp
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres-data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d todoapp"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
postgres-data:
第五步:启动和测试
# 构建并启动
docker-compose up -d
# 查看状态
docker-compose ps
# 测试 API
curl http://localhost:8000/todos
# 创建 Todo
curl -X POST http://localhost:8000/todos \
-H "Content-Type: application/json" \
-d '{"title": "学习 Docker", "description": "完成 Docker 课程"}'
# 查看日志
docker-compose logs -f api
# 停止服务
docker-compose down
完整项目结构
todo-api/
├── app/
│ ├── __init__.py
│ ├── main.py # FastAPI 应用
│ ├── database.py # 数据库连接
│ └── models.py # SQLAlchemy 模型
├── Dockerfile
├── docker-compose.yml
└── requirements.txt
最佳实践
- 健康检查:确保应用真正可用
- 依赖等待:使用 depends_on + healthcheck
- 数据持久化:使用 volumes 保存数据
- 环境变量:通过环境变量配置数据库连接
- 自动重启:设置 restart: unless-stopped
常见问题
Q: 如何查看数据库数据?
A: docker-compose exec db psql -U user -d todoapp
Q: 如何重置数据库?
A: docker-compose down -v 删除数据卷。
Q: 如何更新应用?
A: 修改代码后 docker-compose up -d --build。
扩展挑战
- 添加 Redis 缓存
- 实现数据库迁移(Alembic)
- 添加 Nginx 反向代理
相关课程
| 课程 | 相关章节 |
|---|---|
| Python | Web开发入门:Flask 框架简介 |
| Docker | Dockerfile 基础 |
| Docker | Docker Compose 基础 |
| Docker | 命名卷 |