FastAPI Pydantic Settings多环境配置完全指南
📂 所属阶段:第五阶段 — 工程化与部署(实战篇)
🔗 相关章节:FastAPIdependency-injection · FastAPI安全认证
目录
环境配置管理概述
你是否遇到过这样的情况:本地开发时用 SQLite 调试一切正常,推送到服务器后却因为数据库 URL 没有及时修改,导致生产环境也连接着开发库,甚至不小心把测试数据写进了正式数据库?这就是“硬编码配置”带来的典型灾难。
硬编码配置的危害:
- 环境切换时容易忘记修改
- 密钥、密码等敏感信息可能泄露到仓库
- 代码在不同环境中不可重用
遵循 12-Factor App 的配置原则,我们应该将配置与代码完全分离,通过环境变量或配置文件动态注入。这样做的好处非常明显:
- 环境隔离:开发、测试、生产环境互不干扰
- 安全保护:密码、密钥等敏感信息不会硬编码在代码中
- 灵活部署:不修改一行代码,就能完成环境切换
💡 实战对比
下面的例子展示了硬编码配置的问题,以及如何用环境驱动的配置来避免。
# ❌ 错误示范:硬编码配置
DATABASE_URL = "sqlite:///dev.db" # 上线时忘记修改,生产环境用了 SQLite
SECRET_KEY = "dev-secret-123" # 密钥暴露在代码中,安全风险极高
DEBUG = True # 生产环境开启调试模式,敏感信息可能泄露
# ✅ 正确做法:环境驱动的配置
# 不同环境使用不同的 .env 文件,代码完全统一
# .env.development → 本地开发
# .env.testing → 自动化测试
# .env.production → 生产环境
通过读取不同的环境文件,应用不必再关心身处哪个环境,真正做到代码不变,配置可变。
Pydantic Settings基础
Pydantic Settings 是 FastAPI 生态中处理配置的神器。它基于 Pydantic 的数据校验能力,可以帮你用类型安全的方式读取环境变量或配置文件,并且支持自动验证。
安装
pip install pydantic-settings fastapi
定义基础配置类
我们先创建一个 BaseConfig,把所有环境共有的配置项放在这里。通过 model_config 指定默认的环境文件名,同时禁止未定义的额外字段,避免配置混乱。
# config/base.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field, field_validator
from typing import List
class BaseConfig(BaseSettings):
# 配置文件加载规则
model_config = SettingsConfigDict(
env_file=".env", # 默认读取 .env 文件
env_file_encoding="utf-8",
extra="forbid" # 不允许出现未定义的配置项
)
# 应用基本信息
app_name: str = Field(default="FastAPI App", min_length=1)
app_version: str = "1.0.0"
environment: str = Field(
default="development",
pattern=r"^(development|testing|production)$"
)
# 调试与服务器设置
debug: bool = False
host: str = "127.0.0.1"
port: int = Field(default=8000, ge=1, le=65535)
# 跨域资源共享(CORS)白名单
cors_allow_origins: List[str] = Field(default_factory=list)
# 便利的属性,用于判断当前环境
@property
def is_production(self) -> bool:
return self.environment == "production"
@property
def is_development(self) -> bool:
return self.environment == "development"
# 解析逗号分隔的 CORS 来源字符串(环境变量常以字符串形式传入)
@field_validator("cors_allow_origins", mode="before")
@classmethod
def parse_cors_origins(cls, v):
if isinstance(v, str):
return [origin.strip() for origin in v.split(",") if origin.strip()]
return v
环境特定的配置类
通过继承 BaseConfig,我们分别为开发环境和生产环境创建各自的配置类。生产环境需要关闭调试选项,并使用更健壮的数据库,甚至可以加入额外的验证规则。
# config/environments.py
from .base import BaseConfig
from pydantic import Field
class DevelopmentConfig(BaseConfig):
debug: bool = True
database_url: str = "sqlite+aiosqlite:///dev.db"
cors_allow_origins: List[str] = ["http://localhost:3000"]
class ProductionConfig(BaseConfig):
debug: bool = False
database_url: str = Field(description="生产数据库URL", min_length=10)
cors_allow_origins: List[str] = ["https://yourapp.com"]
# 强制检查:生产环境绝不能使用 SQLite
@field_validator("database_url")
@classmethod
def validate_prod_db(cls, v):
if "sqlite" in v.lower():
raise ValueError("生产环境不能使用SQLite")
return v
配置工厂:按环境自动加载
我们编写一个工厂函数,根据 ENVIRONMENT 环境变量的值加载对应的配置类。为了提升性能,还用 lru_cache 缓存配置实例,避免重复初始化。
# config/__init__.py
import os
from functools import lru_cache
from .environments import DevelopmentConfig, ProductionConfig, BaseConfig
def get_config() -> BaseConfig:
"""根据 ENVIRONMENT 环境变量动态选择配置类"""
env = os.getenv("ENVIRONMENT", "development").lower()
config_map = {
"development": DevelopmentConfig,
"production": ProductionConfig
}
return config_map.get(env, DevelopmentConfig)()
@lru_cache()
def get_cached_config() -> BaseConfig:
"""带缓存的配置获取函数,避免重复创建实例"""
return get_config()
在 FastAPI 应用中,你可以轻松使用依赖注入获取配置:
from fastapi import FastAPI, Depends
from config import get_cached_config, BaseConfig
app = FastAPI()
@app.get("/info")
def info(config: BaseConfig = Depends(get_cached_config)):
return {
"app_name": config.app_name,
"environment": config.environment,
"debug": config.debug
}
多环境配置策略
Pydantic Settings 读取配置时会遵循明确的优先级。这一特性让我们可以用更灵活的方式覆盖设置。
环境变量优先级(从高到低)
- 直接传递给配置类的参数(如
Config(key="value"))
- 系统环境变量
.env 文件
- 字段默认值
这意味着:即使 .env 文件中设置了 DEBUG=true,如果你在启动命令中通过 export DEBUG=false 覆盖,程序会优先使用系统环境变量的值。
使用不同的 .env 文件
为每个环境准备一个专属的 .env 文件是一个好习惯。文件内容示例:
# .env.development
ENVIRONMENT=development
DEBUG=true
DATABASE_URL=sqlite+aiosqlite:///dev.db
CORS_ALLOW_ORIGINS=http://localhost:3000
# .env.production
ENVIRONMENT=production
DEBUG=false
DATABASE_URL=postgresql+asyncpg://user:pass@prod-db:5432/app
CORS_ALLOW_ORIGINS=https://yourapp.com
安全警告:所有 .env 文件都应该加入 .gitignore,防止将敏感信息提交到版本控制。但你可以创建一个 env.example 模板,供团队成员参考。
# .gitignore
.env*
!env.example
Docker 多环境部署
在 Docker 或 Docker Compose 中,可以通过 env_file 指定环境文件,也可以直接注入环境变量。下面是一个典型的开发/生产两用配置:
# docker-compose.yml
version: '3.8'
services:
app:
build: .
environment:
- ENVIRONMENT=${ENVIRONMENT:-development}
env_file:
- .env.${ENVIRONMENT:-development}
ports:
- "8000:8000"
depends_on:
- db
db:
image: postgres:15
environment:
- POSTGRES_DB=app
- POSTGRES_USER=user
- POSTGRES_PASSWORD=pass
启动时指定环境变量即可切换:
# 开发环境
docker-compose up
# 生产环境
export ENVIRONMENT=production
docker-compose up
配置验证与类型安全
Pydantic 的一大杀手锏就是强大的类型验证。我们不仅可以限定字段类型,还可以利用枚举和字面量类型让配置的含义更加明确,避免拼写错误。
# config/validated.py
from .base import BaseConfig
from enum import Enum
from typing import Literal
class LogLevel(str, Enum):
DEBUG = "DEBUG"
INFO = "INFO"
WARNING = "WARNING"
ERROR = "ERROR"
class ValidatedConfig(BaseConfig):
log_level: LogLevel = LogLevel.INFO
cache_strategy: Literal["memory", "redis"] = "memory"
@field_validator("cache_strategy")
@classmethod
def check_redis_url(cls, v, info):
if v == "redis":
# 如果选择 redis,必须提供 REDIS_URL
redis_url = info.data.get("redis_url")
if not redis_url:
raise ValueError("使用 redis 缓存模式时必须设置 redis_url")
return v
通过这种方式,错误的配置值会在应用启动时立即抛出异常,真正做到“早发现、早修复”。
敏感信息安全管理
敏感信息(数据库密码、JWT 密钥、第三方 API 密钥)必须妥善保管。Pydantic Settings 完全支持从外部 Secret 存储中读取。
密钥管理的最佳实践
- 本地开发:使用
.env 文件,并将文件排除在版本控制之外
- 生产环境:使用 Docker Secrets、Kubernetes Secrets 或者云平台的密钥管理服务(如 AWS Secrets Manager)
- 绝不硬编码:代码中任何地方都不应出现明文密钥
安全的密钥读取函数
你可以编写一个辅助函数,优先从 Docker Secrets 挂载的目录中读取密钥,其次再从环境变量获取。
# config/security.py
import os
from typing import Optional
def get_secret(key: str, default: Optional[str] = None) -> Optional[str]:
"""
优先从 /run/secrets/<key> 读取(Docker Secrets 标准路径),
如果不存在则回退到环境变量。
"""
secret_path = f"/run/secrets/{key}"
if os.path.exists(secret_path):
with open(secret_path, "r") as f:
return f.read().strip()
return os.getenv(key, default)
在配置类中,你可以这样使用它:
from config.security import get_secret
class ProductionConfig(BaseConfig):
secret_key: str = Field(default_factory=lambda: get_secret("APP_SECRET_KEY", "change_me"))
生产部署最佳实践
上线前的安全检查清单
部署前务必逐项确认:
自动化部署脚本示例
下面脚本会先检查必需的环境变量,再基于 Docker Compose 构建、启动服务,并执行简单的健康检查。
#!/bin/bash
set -e
# 列出所有运行时必须提供的环境变量
REQUIRED_VARS=("PROD_DATABASE_URL" "PROD_JWT_SECRET")
for var in "${REQUIRED_VARS[@]}"; do
if [ -z "${!var}" ]; then
echo "❌ 缺失必需环境变量: $var"
exit 1
fi
done
# 构建镜像
docker build -t my-fastapi-app .
# 使用指定的生产配置文件启动
docker-compose -f docker-compose.prod.yml up -d
# 简单健康检查
sleep 10
if curl -f http://localhost/health >/dev/null 2>&1; then
echo "✅ 部署成功"
else
echo "❌ 部署失败,请检查日志"
exit 1
fi
总结
FastAPI 搭配 Pydantic Settings 提供了一套严谨且易用的配置管理方案,核心优势包括:
- 环境隔离:通过
.env 文件或环境变量切换,保持代码纯净
- 类型安全:完整的类型提示和验证机制,杜绝运行时配置错误
- 安全无忧:敏感信息可与代码完全分离,匹配云原生安全实践
- 云原生友好:完美适配 Docker、Kubernetes 等容器化平台
💡 关键要点:在项目初期就建立良好的配置管理体系,能为后续的持续集成、多环境部署以及安全合规铺平道路。在配置管理上花的每一分钟,都会在后期为你节省大量的维护时间。
🔗 扩展阅读