基于FastAPI、Pydantic与Apollo的现代化配置管理实践
在现代Web应用开发中,尤其是在微服务架构下,配置管理是一个至关重要的环节。一个优秀的配置方案需要满足以下几点:支持不同环境(开发、测试、生产)的隔离,能够安全地处理敏感信息(如数据库密码、API密钥),并且易于维护和更新。
本文将介绍一种基于Python FastAPI框架的现代化配置管理方案。该方案利用pydantic-settings进行类型安全的环境变量加载,并集成Apollo配置中心,实现一个分层、灵活且强大的配置初始化流程。
核心理念:分层加载与集中管理
我们的配置加载遵循一个清晰的优先级顺序,这使得本地开发和生产部署都变得非常简单:
- 本地.env文件:为本地开发提供便利,可以存放非敏感的默认配置或开发环境特定的配置。
- 环境变量:这是容器化部署(如Docker、Kubernetes)的标准实践。环境变量可以覆盖.env文件中的同名配置。
- Apollo配置中心:作为最终的、最高优先级的配置源。应用启动时,从Apollo获取配置并注入到环境变量中,从而覆盖任何本地或预设的环境变量。
这种分层设计的好处是:
- 开发友好:开发者只需维护一个
.env文件即可快速启动项目。 - 部署灵活:运维人员可以通过标准的环境变量来配置容器。
- 生产稳健:所有生产环境的配置都由Apollo集中管理,支持动态更新、权限控制和版本历史,大大提高了安全性和可维护性。
技术选型
- FastAPI: 高性能的现代Web框架。
- Pydantic-settings: pydantic的扩展,可以轻松地从环境变量中加载配置并进行类型验证,生成一个强类型的配置对象。
- Apollo: 一个功能强大的分布式配置中心,广泛应用于生产环境。
实现步骤详解
我们将通过一个具体的代码示例来展示如何实现这一整套配置流程。
1. 项目结构
首先,我们约定一个项目结构。配置文件config.py位于config目录中,而.env文件位于项目的根目录。
/my-fastapi-app
|-- main.py
|-- .env
|-- requirements.txt
|-- /config
| |-- config.py
2. 定义配置模型
我们使用pydantic-settings的BaseSettings来定义配置模型。这将配置分为两部分:一部分是连接Apollo所需的配置,另一部分是应用自身的业务配置。
config/config.py:
import os
import cachetools
from dotenv import load_dotenv
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import MySQLDsn, RedisDsn
from typing import Optional
# 计算项目根目录和.env文件路径
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
ENV_PATH = os.path.join(BASE_DIR, '.env')
class ApolloConnectionSettings(BaseSettings):
"""
Apollo 连接配置。
从项目根目录的 .env 文件或环境变量加载。
这些字段设为Optional,是为了在不使用Apollo的场景下(如纯本地开发)也能正常工作。
"""
APOLLO_URL: Optional[str] = None
APOLLO_APP_ID: Optional[str] = None
APOLLO_SECRET: Optional[str] = None
APOLLO_NAMESPACE: Optional[str] = None
APOLLO_CLUSTER: Optional[str] = None
APOLLO_OVERRIDE: Optional[bool] = None
APOLLO_ENABLE: Optional[bool] = None
class AppSettings(BaseSettings):
"""
应用程序主配置。
该模型定义了应用所需的所有配置项。
pydantic-settings会自动从环境变量中读取并填充这些字段。
"""
# ---- 固定元数据 ----
PROJECT_NAME: str = "my-fastapi-project"
PROJECT_DESC: str = "一个很棒的FastAPI项目"
PROJECT_VERSION: str = "0.1.0"
API_V1_STR: str = "/api/v1"
# ---- 环境配置 ----
ENVIRONMENT: str = "dev"
DEBUG: bool = False
# ---- 数据库配置 (从Apollo获取) ----
DATABASE_URL: Optional[MySQLDsn] = None
DB_MIN_SIZE: int = 5
DB_MAX_SIZE: int = 20
DB_ECHO: bool = False
# ---- Redis配置 (从Apollo获取) ----
REDIS_URL: Optional[RedisDsn] = None
# ---- 日志配置 ----
LOG_LEVEL: str = "INFO"
LOG_FORMAT: Optional[str] = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
LOG_FILE: Optional[str] = None
# pydantic-settings的模型配置
# 不再需要指定 env_file,因为配置由 Apollo 设置到环境变量
model_config = SettingsConfigDict(
case_sensitive=False, # 环境变量不区分大小写
env_prefix="", # 环境变量没有前缀
extra="allow" # 允许额外的字段
)
代码解析:
ApolloConnectionSettings: 专门用于存储连接Apollo所需的信息。APOLLO_ENABLE作为一个开关,可以方便地决定是否启用Apollo。AppSettings: 定义了应用的所有配置,包括数据库URL、Redis URL等。注意DATABASE_URL和REDIS_URL的类型是MySQLDsn和RedisDsn,这利用了Pydantic强大的类型验证功能,确保URL格式正确。model_config:case_sensitive=False使得我们可以用debug=True或DEBUG=True这样的环境变量。
3. 实现配置加载逻辑
接下来是整个流程的核心:一个函数负责按需初始化Apollo,另一个函数作为单例工厂来提供最终的配置对象。
config/config.py (续):
def init_apollo_if_needed() -> None:
"""
如果配置启用Apollo,则初始化连接并拉取配置。
Apollo客户端会把拉取到的配置设置为当前进程的环境变量。
"""
# 1. 首先加载本地.env文件,为Apollo连接参数提供可能的值
load_dotenv(ENV_PATH)
# 2. 加载Apollo连接配置
apollo_conn = ApolloConnectionSettings()
# 3. 检查是否启用Apollo并且关键参数已提供
if apollo_conn.APOLLO_ENABLE and apollo_conn.APOLLO_URL and apollo_conn.APOLLO_APP_ID:
print("[CONFIG] Apollo is enabled. Attempting to connect...")
try:
# pip install pyapollo
from pyapollo import ApolloClient
client = ApolloClient(
app_id=apollo_conn.APOLLO_APP_ID,
cluster=apollo_conn.APOLLO_CLUSTER,
config_server_url=apollo_conn.APOLLO_URL,
secret=apollo_conn.APOLLO_SECRET
)
# 启动客户端,假设在后台拉取配置并会更新到os.environ
client.start(catch_signals=False)
print("[CONFIG] Apollo client started and configurations are being loaded into environment variables.")
except ImportError:
print("[CONFIG] WARNING: `pyapollo` library not found. Skipping Apollo initialization.")
except Exception as e:
print(f"[CONFIG] ERROR: Failed to connect to Apollo: {e}")
# 根据策略,连接失败可以选择退出或继续使用现有环境配置
# raise e
@cachetools.cached(cache=cachetools.TTLCache(maxsize=1, ttl=600))
def get_settings() -> AppSettings:
"""
获取应用配置的单例函数。
整个应用都应该通过这个函数来获取配置。
"""
# 1. 尝试从Apollo加载配置并设置到环境变量
init_apollo_if_needed()
# 2. 实例化AppSettings
# pydantic-settings会自动从环境变量中读取所有配置
# 如果Apollo成功运行,此时的环境变量已经被Apollo更新
settings = AppSettings()
# 3. 在开发环境下打印配置以供调试
if settings.ENVIRONMENT.lower() == "dev":
print("\n[CONFIG] Application settings loaded:")
for field, value in settings.model_dump().items():
# 对敏感字段进行脱敏
if any(sensitive in field.lower() for sensitive in ["secret", "password", "token", "key"]):
value = "******"
print(f"[CONFIG] - {field}: {value}")
print("-" * 30)
return settings
# 在模块加载时直接调用,生成全局可用的配置实例
settings = get_settings()
代码解析:
init_apollo_if_needed(): 这个函数首先加载本地的.env文件,这样APOLLO_URL等参数就可以在.env中配置。然后,它检查是否启用了Apollo,如果启用,就初始化Apollo客户端。关键点在于:Apollo客户端库(如pyapollo)在获取到配置后,会将其写入os.environ,即当前进程的环境变量中。@lru_cache(maxsize=1): 这是一个非常巧妙的装饰器,它将get_settings函数变成了一个单例工厂。第一次调用时,函数体会被执行,配置被加载和初始化;后续所有调用都会立即返回第一次缓存的结果,避免了重复加载,并确保了全局配置的一致性。settings = get_settings(): 在模块的末尾执行一次,这样其他任何模块只需from config.config import settings就可以直接使用已经初始化好的配置实例。
另外这里使用load_dotenv(ENV_PATH)手动加载配置文件,如果改成在ApolloConnectionSettings类里新增如下代码,这样虽然实例化的类里有加载到配置,但是os.env里是没有的,可能导致后续apollo里加载不到配置:
model_config = SettingsConfigDict(
# 指定 env_file 为项目根目录下的 .env 文件
env_file=ENV_PATH,
env_file_encoding="utf-8",
case_sensitive=False, # 环境变量不区分大小写
extra="allow" # 允许额外字段
)
4. 在FastAPI应用中使用
现在,在你的FastAPI应用中使用配置就变得非常简单了。
main.py:
from fastapi import FastAPI
from config.config import settings # 导入配置实例
# 使用配置来初始化应用
app = FastAPI(
title=settings.PROJECT_NAME,
description=settings.PROJECT_DESC,
version=settings.PROJECT_VERSION,
debug=settings.DEBUG
)
@app.get("/")
def read_root():
return {
"message": f"Welcome to {settings.PROJECT_NAME}",
"environment": settings.ENVIRONMENT,
"database_url_is_set": settings.DATABASE_URL is not None
}
# 假设你有一个数据库初始化函数
def init_database():
db_url = str(settings.DATABASE_URL)
print(f"Initializing database with URL: {db_url[:db_url.find('@')]}@... (credentials masked)")
# ... 数据库连接池创建逻辑 ...
init_database()
完整的配置流程总结
- 应用启动: main.py被执行,导入config.config模块。
- 配置模块加载: config.py被加载,代码执行到
settings = get_settings()。 - 首次获取配置:
get_settings()被调用。 - Apollo初始化:
init_apollo_if_needed()被调用。load_dotenv()加载.env文件。ApolloConnectionSettings读取到Apollo的连接信息。- 如果
APOLLO_ENABLE=True,Apollo客户端启动,从远端拉取DATABASE_URL, REDIS_URL等配置,并设置到环境变量中。
- 最终配置生成:
AppSettings()被实例化。pydantic-settings从环境变量中读取配置。此时,如果Apollo已成功加载,它读取到的是Apollo设置的值;否则,它读取到的是.env或启动时传入的环境变量的值。 - 配置单例缓存: get_settings的结果被
@lru_cache缓存。 - 全局使用: settings实例在
config.config模块中可用,任何地方导入即可使用。
结论
通过结合pydantic-settings的类型安全和Apollo的集中管理能力,我们为FastAPI应用构建了一个既灵活又强大的配置系统。该方案优雅地处理了不同环境下的配置差异,简化了开发和部署流程,并极大地提升了生产环境配置的安全性和可维护性,是值得在实际项目中采纳的优秀实践。
参考
- kinit-api/application/settings.py · ktianc/kinit
https://gitee.com/ktianc/kinit/blob/master/kinit-api/application/settings.py - ruoyi-fastapi-backend/config/env.py · insistence/RuoYi-Vue3-FastAPI
https://gitee.com/insistence2022/RuoYi-Vue3-FastAPI/blob/master/ruoyi-fastapi-backend/config/env.py
欢迎关注公众号"飞鸿影记(fhyblog)",探寻物件背后的逻辑,记录生活真实的影子。

作者:飞鸿影
出处:http://52fhy.cnblogs.com/
版权申明:没有标明转载或特殊申明均为作者原创。本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处。


浙公网安备 33010602011771号