配置管理:从 YAML 到运行时的配置链路
在一个由三个进程组成的平台中,配置管理看似简单,实则是运维痛点的重灾区。一个错误的数据库连接字符串可以让整个平台瘫痪,一个遗漏的环境变量可以让功能神秘消失。
配置管理:从 YAML 到运行时的配置链路
“系列:S2 架构全景 · 第 12 篇 | 难度:中级 | 阅读时间:18 分钟
#TL;DR
- 智策平台采用四层配置模型:代码默认值 → YAML 文件 → 环境变量 → 运行时动态配置,后者覆盖前者,实现从开发到生产的配置无缝过渡。
- 三个技术栈(Spring Boot / Quarkus / FastAPI)各有配置惯例,平台通过 Docker Compose 环境变量注入统一管理,单一
.env文件控制整个集群配置。 - 敏感配置(数据库密码、API Key)使用 Docker Secrets + 环境变量分离,绝不硬编码在 YAML 中;Feature Flag 使用 Ontology 元数据驱动,支持按 World 粒度开关功能。
#引言:配置是分布式系统的神经系统
在一个由三个进程组成的平台中,配置管理看似简单,实则是运维痛点的重灾区。一个错误的数据库连接字符串可以让整个平台瘫痪,一个遗漏的环境变量可以让功能神秘消失。
配置管理的挑战:
1. 多技术栈统一
onto-control: Spring Boot (application.yml)
onto-data: Quarkus (application.properties)
onto-intelligence: FastAPI (Python .env / pydantic-settings)
2. 多环境适配
dev → 本地开发(SQLite / 内存存储)
test → CI/CD 测试(Testcontainers)
staging → 预发布(真实基础设施,小规模)
prod → 生产环境(高可用,多副本)
3. 安全性要求
数据库密码、Kafka 认证、gRPC TLS 证书
→ 不能出现在代码仓库中
→ 不能出现在 Docker 镜像中
4. 动态更新
限流阈值、降级开关、Feature Flag
→ 需要不重启就能修改
本文将完整讲解智策平台的配置架构——从静态 YAML 到 Docker Compose 环境变量,从 Secret 管理到运行时动态配置。
#1. 四层配置模型
#1.1 配置优先级
智策平台遵循"约定优于配置"原则,定义了四层配置,优先级从低到高:
配置优先级(从低到高):
Layer 1: 代码默认值(Code Defaults)
位置:源代码中的常量 / 默认参数
用途:确保系统在零配置时仍能启动(开发模式)
示例:server.port = 8080
Layer 2: YAML / Properties 文件(Static Config)
位置:src/main/resources/application.yml
用途:各环境的基础配置
示例:spring.datasource.url = jdbc:postgresql://...
Layer 3: 环境变量(Environment Variables)
位置:Docker Compose .env 文件 / K8s ConfigMap
用途:覆盖特定环境的配置
示例:SPRING_DATASOURCE_URL=jdbc:postgresql://prod-db:5432/onto
Layer 4: 运行时动态配置(Runtime Config)
位置:数据库 / 配置中心
用途:无需重启的动态调整
示例:rate_limit.max_requests_per_second = 500
覆盖关系:Layer 4 > Layer 3 > Layer 2 > Layer 1
#1.2 各层的适用场景
配置分类决策树:
这个配置值...
│
├─ 几乎不变?(端口、协议版本)
│ └─ → Layer 1: 代码默认值
│
├─ 按环境不同?(数据库地址、Kafka 地址)
│ └─ → Layer 2 + Layer 3: YAML + 环境变量覆盖
│
├─ 是敏感信息?(密码、密钥)
│ └─ → Layer 3: 环境变量(从 Secret 注入)
│
└─ 需要动态调整?(限流、开关)
└─ → Layer 4: 运行时配置
#2. 各进程的配置实现
#2.1 onto-control:Spring Boot 配置
Spring Boot 是三个框架中配置能力最强的,支持 Profile、YAML 多文档、属性绑定等:
# onto-control/src/main/resources/application.yml
# ── 默认配置(所有环境通用)──
server:
port: 8080
spring:
application:
name: onto-control
datasource:
url: jdbc:postgresql://localhost:5432/onto_control
username: onto
password: onto_dev
hikari:
maximum-pool-size: 20
minimum-idle: 5
connection-timeout: 10000
kafka:
bootstrap-servers: localhost:9092
consumer:
group-id: onto-control
auto-offset-reset: earliest
producer:
acks: all
grpc:
server:
port: 9090
client:
onto-data:
address: static://localhost:9091
negotiation-type: plaintext
onto-intelligence:
address: static://localhost:9092
negotiation-type: plaintext
# Resilience4j 配置
resilience4j:
circuitbreaker:
instances:
data-service:
sliding-window-size: 20
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
intelligence-service:
sliding-window-size: 10
failure-rate-threshold: 50
wait-duration-in-open-state: 30s
---
# ── 生产配置 ──
spring:
config:
activate:
on-profile: prod
datasource:
hikari:
maximum-pool-size: 50
minimum-idle: 10
grpc:
server:
security:
enabled: true
certificate-chain: file:/etc/certs/server.crt
private-key: file:/etc/certs/server.key
#2.2 onto-data:Quarkus 配置
Quarkus 使用 MicroProfile Config,支持 properties 和 YAML 格式:
# onto-data/src/main/resources/application.properties
# ── 默认配置 ──
quarkus.application.name=onto-data
quarkus.http.port=8081
quarkus.grpc.server.port=9091
# Doris 数据源
quarkus.datasource.db-kind=mysql
quarkus.datasource.jdbc.url=jdbc:mysql://localhost:9030/onto_data
quarkus.datasource.username=root
quarkus.datasource.password=
quarkus.datasource.jdbc.max-size=30
quarkus.datasource.jdbc.min-size=5
# Nessie 配置
nessie.server.uri=http://localhost:19120/api/v2
nessie.default-branch=main
# Iceberg 配置
iceberg.catalog.type=nessie
iceberg.catalog.uri=http://localhost:19120/api/v2
iceberg.catalog.warehouse=s3://onto-warehouse/
# Kafka
kafka.bootstrap.servers=localhost:9092
mp.messaging.incoming.ontology-events.connector=smallrye-kafka
mp.messaging.incoming.ontology-events.topic=platform.ontology.events
mp.messaging.incoming.ontology-events.group.id=onto-data
# ── 生产配置(通过环境变量覆盖或 profile)──
%prod.quarkus.datasource.jdbc.url=jdbc:mysql://doris-prod:9030/onto_data
%prod.quarkus.datasource.jdbc.max-size=100
%prod.quarkus.datasource.jdbc.min-size=20
#2.3 onto-intelligence:FastAPI + Pydantic Settings
Python 生态使用 pydantic-settings 实现类型安全的配置:
# onto-intelligence/config.py
from pydantic_settings import BaseSettings, SettingsConfigDict
from pydantic import Field
from typing import Optional
class DatabaseSettings(BaseSettings):
"""数据库配置"""
model_config = SettingsConfigDict(env_prefix="DB_")
host: str = "localhost"
port: int = 5432
name: str = "onto_intelligence"
user: str = "onto"
password: str = "onto_dev"
pool_size: int = 10
pool_max_overflow: int = 20
@property
def url(self) -> str:
return f"postgresql+asyncpg://{self.user}:{self.password}@{self.host}:{self.port}/{self.name}"
class GrpcSettings(BaseSettings):
"""gRPC 配置"""
model_config = SettingsConfigDict(env_prefix="GRPC_")
server_port: int = 9092
control_address: str = "localhost:9090"
data_address: str = "localhost:9091"
max_message_size: int = 50 * 1024 * 1024 # 50MB
keepalive_time_ms: int = 30000
keepalive_timeout_ms: int = 10000
class KafkaSettings(BaseSettings):
"""Kafka 配置"""
model_config = SettingsConfigDict(env_prefix="KAFKA_")
bootstrap_servers: str = "localhost:9092"
group_id: str = "onto-intelligence"
auto_offset_reset: str = "earliest"
enable_auto_commit: bool = False
class TemporalSettings(BaseSettings):
"""Temporal 配置"""
model_config = SettingsConfigDict(env_prefix="TEMPORAL_")
server_address: str = "localhost:7233"
namespace: str = "onto-intelligence"
task_queue: str = "reasoning-tasks"
max_concurrent_activities: int = 50
max_concurrent_workflows: int = 100
class AppSettings(BaseSettings):
"""应用顶层配置"""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
env_nested_delimiter="__",
)
environment: str = "dev"
debug: bool = False
log_level: str = "INFO"
db: DatabaseSettings = Field(default_factory=DatabaseSettings)
grpc: GrpcSettings = Field(default_factory=GrpcSettings)
kafka: KafkaSettings = Field(default_factory=KafkaSettings)
temporal: TemporalSettings = Field(default_factory=TemporalSettings)
# 全局配置单例
settings = AppSettings()
#3. Docker Compose 统一配置管理
#3.1 环境变量文件
所有环境变量集中在 .env 文件中,Docker Compose 自动加载:
# deployment-Layer/.env
# ══════════════════════════════════════════
# 智策平台统一环境变量配置
# ══════════════════════════════════════════
# ── 通用配置 ──
PLATFORM_ENV=dev
PLATFORM_LOG_LEVEL=INFO
# ── PostgreSQL ──
POSTGRES_HOST=postgres
POSTGRES_PORT=5432
POSTGRES_DB=onto_control
POSTGRES_USER=onto
POSTGRES_PASSWORD=change_me_in_production
# ── Doris ──
DORIS_HOST=doris-fe
DORIS_PORT=9030
DORIS_USER=root
DORIS_PASSWORD=
# ── Kafka ──
KAFKA_BOOTSTRAP_SERVERS=kafka:9092
KAFKA_NUM_PARTITIONS=6
# ── Nessie ──
NESSIE_URI=http://nessie:19120/api/v2
# ── MinIO (S3 兼容存储) ──
MINIO_ENDPOINT=http://minio:9000
MINIO_ACCESS_KEY=minioadmin
MINIO_SECRET_KEY=minioadmin
MINIO_BUCKET=onto-warehouse
# ── Temporal ──
TEMPORAL_ADDRESS=temporal:7233
TEMPORAL_NAMESPACE=onto-intelligence
# ── gRPC 端口 ──
GRPC_CONTROL_PORT=9090
GRPC_DATA_PORT=9091
GRPC_INTELLIGENCE_PORT=9092
# ── onto-control 特有 ──
SPRING_PROFILES_ACTIVE=docker
SPRING_DATASOURCE_URL=jdbc:postgresql://${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
SPRING_DATASOURCE_USERNAME=${POSTGRES_USER}
SPRING_DATASOURCE_PASSWORD=${POSTGRES_PASSWORD}
# ── onto-data 特有 ──
QUARKUS_PROFILE=docker
QUARKUS_DATASOURCE_JDBC_URL=jdbc:mysql://${DORIS_HOST}:${DORIS_PORT}/onto_data
# ── onto-intelligence 特有 ──
DB_HOST=${POSTGRES_HOST}
DB_PORT=${POSTGRES_PORT}
DB_NAME=onto_intelligence
DB_USER=${POSTGRES_USER}
DB_PASSWORD=${POSTGRES_PASSWORD}
GRPC_CONTROL_ADDRESS=onto-control:${GRPC_CONTROL_PORT}
GRPC_DATA_ADDRESS=onto-data:${GRPC_DATA_PORT}
#3.2 Docker Compose 配置注入
# deployment-Layer/docker-compose.yml (配置相关部分)
services:
onto-control:
image: coomia-dip/control:latest
environment:
- SPRING_PROFILES_ACTIVE=${SPRING_PROFILES_ACTIVE}
- SPRING_DATASOURCE_URL=${SPRING_DATASOURCE_URL}
- SPRING_DATASOURCE_USERNAME=${SPRING_DATASOURCE_USERNAME}
- SPRING_DATASOURCE_PASSWORD=${SPRING_DATASOURCE_PASSWORD}
- SPRING_KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS}
- GRPC_SERVER_PORT=${GRPC_CONTROL_PORT}
- GRPC_CLIENT_ONTO_DATA_ADDRESS=static://onto-data:${GRPC_DATA_PORT}
- GRPC_CLIENT_ONTO_INTELLIGENCE_ADDRESS=static://onto-intelligence:${GRPC_INTELLIGENCE_PORT}
- PLATFORM_LOG_LEVEL=${PLATFORM_LOG_LEVEL}
ports:
- "8080:8080"
- "${GRPC_CONTROL_PORT}:${GRPC_CONTROL_PORT}"
depends_on:
postgres:
condition: service_healthy
kafka:
condition: service_healthy
onto-data:
image: coomia-dip/data:latest
environment:
- QUARKUS_PROFILE=${QUARKUS_PROFILE}
- QUARKUS_DATASOURCE_JDBC_URL=${QUARKUS_DATASOURCE_JDBC_URL}
- QUARKUS_DATASOURCE_USERNAME=${DORIS_USER}
- QUARKUS_DATASOURCE_PASSWORD=${DORIS_PASSWORD}
- NESSIE_URI=${NESSIE_URI}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS}
- GRPC_SERVER_PORT=${GRPC_DATA_PORT}
- MINIO_ENDPOINT=${MINIO_ENDPOINT}
- MINIO_ACCESS_KEY=${MINIO_ACCESS_KEY}
- MINIO_SECRET_KEY=${MINIO_SECRET_KEY}
ports:
- "8081:8081"
- "${GRPC_DATA_PORT}:${GRPC_DATA_PORT}"
depends_on:
doris-fe:
condition: service_healthy
nessie:
condition: service_started
onto-intelligence:
image: coomia-dip/intelligence:latest
environment:
- ENVIRONMENT=${PLATFORM_ENV}
- LOG_LEVEL=${PLATFORM_LOG_LEVEL}
- DB_HOST=${POSTGRES_HOST}
- DB_PORT=${POSTGRES_PORT}
- DB_NAME=${DB_NAME}
- DB_USER=${DB_USER}
- DB_PASSWORD=${DB_PASSWORD}
- GRPC_SERVER_PORT=${GRPC_INTELLIGENCE_PORT}
- GRPC_CONTROL_ADDRESS=${GRPC_CONTROL_ADDRESS}
- GRPC_DATA_ADDRESS=${GRPC_DATA_ADDRESS}
- KAFKA_BOOTSTRAP_SERVERS=${KAFKA_BOOTSTRAP_SERVERS}
- TEMPORAL_SERVER_ADDRESS=${TEMPORAL_ADDRESS}
- TEMPORAL_NAMESPACE=${TEMPORAL_NAMESPACE}
ports:
- "8082:8082"
- "${GRPC_INTELLIGENCE_PORT}:${GRPC_INTELLIGENCE_PORT}"
depends_on:
postgres:
condition: service_healthy
temporal:
condition: service_healthy
#3.3 多环境覆盖
多环境配置策略:
deployment-Layer/
├── .env # 默认环境变量(dev)
├── .env.staging # 预发布环境覆盖
├── .env.prod # 生产环境覆盖
├── docker-compose.yml # 基础 Compose 文件
├── docker-compose.staging.yml # 预发布覆盖
└── docker-compose.prod.yml # 生产覆盖
启动命令:
dev: docker compose up
staging: docker compose --env-file .env.staging -f docker-compose.yml -f docker-compose.staging.yml up
prod: docker compose --env-file .env.prod -f docker-compose.yml -f docker-compose.prod.yml up
生产覆盖示例(docker-compose.prod.yml):
services:
onto-control:
deploy:
replicas: 2
resources:
limits:
cpus: '4'
memory: 8G
environment:
- SPRING_DATASOURCE_HIKARI_MAXIMUM_POOL_SIZE=50
- JAVA_OPTS=-Xmx6g -Xms4g
#4. 敏感配置(Secrets)管理
#4.1 Secret 分离原则
Secret 管理规则:
❌ 绝对禁止:
- 密码硬编码在源代码中
- 密码写在 YAML 配置文件并提交到 Git
- 密码写在 Dockerfile 中
- 密码写在 docker-compose.yml 中
✅ 正确做法:
- .env 文件设置弱密码,仅用于本地开发
- .env 文件在 .gitignore 中(不提交到 Git)
- 提供 .env.example 模板(密码用 change_me 占位)
- 生产环境使用 Docker Secrets 或 K8s Secrets
#4.2 Docker Secrets
# docker-compose.prod.yml 使用 Docker Secrets
version: '3.8'
secrets:
postgres_password:
file: /run/secrets/postgres_password
doris_password:
file: /run/secrets/doris_password
kafka_sasl_password:
file: /run/secrets/kafka_sasl_password
services:
onto-control:
secrets:
- postgres_password
environment:
- SPRING_DATASOURCE_PASSWORD_FILE=/run/secrets/postgres_password
# Spring Boot 可以通过自定义 EnvironmentPostProcessor 读取 _FILE 后缀的环境变量
onto-data:
secrets:
- doris_password
environment:
- QUARKUS_DATASOURCE_PASSWORD_FILE=/run/secrets/doris_password
onto-intelligence:
secrets:
- postgres_password
environment:
- DB_PASSWORD_FILE=/run/secrets/postgres_password
#4.3 Python 端的 Secret 文件读取
# onto-intelligence/config.py 中的 Secret 文件支持
from pydantic_settings import BaseSettings
from pydantic import model_validator
from pathlib import Path
from typing import Optional
class DatabaseSettings(BaseSettings):
host: str = "localhost"
port: int = 5432
name: str = "onto_intelligence"
user: str = "onto"
password: str = "onto_dev"
password_file: Optional[str] = None # Docker Secret 文件路径
@model_validator(mode="after")
def read_password_from_file(self) -> "DatabaseSettings":
"""如果提供了 password_file,从文件读取密码"""
if self.password_file:
secret_path = Path(self.password_file)
if secret_path.exists():
self.password = secret_path.read_text().strip()
return self
#5. Feature Flag 系统
#5.1 Ontology 驱动的 Feature Flag
智策平台的 Feature Flag 不使用第三方服务(如 LaunchDarkly),而是利用自身的 Ontology 模型来管理:
Feature Flag 存储模型:
ObjectType: FeatureFlag
Properties:
├── flag_key: String (唯一标识,如 "enable_derived_properties")
├── flag_type: Enum (BOOLEAN, STRING, NUMBER, JSON)
├── default_value: String (默认值)
├── description: String (功能描述)
├── enabled: Boolean (全局开关)
├── created_at: Timestamp
└── updated_at: Timestamp
ObjectType: FeatureFlagOverride
Properties:
├── flag_key: String (关联的 Flag)
├── scope_type: Enum (WORLD, USER, ROLE)
├── scope_value: String (World ID / User ID / Role name)
├── override_value: String (覆盖值)
└── priority: Integer (优先级,越高越优先)
#5.2 Feature Flag 评估逻辑
Feature Flag 评估流程:
evaluate(flag_key, context) {
1. 查找 FeatureFlag 对象
├─ 不存在 → 返回 null(功能未定义)
└─ 存在 → 继续
2. 检查全局开关
├─ enabled = false → 返回 default_value
└─ enabled = true → 继续
3. 查找 Override(按优先级降序)
├─ 匹配 USER scope → 返回 override_value
├─ 匹配 ROLE scope → 返回 override_value
├─ 匹配 WORLD scope → 返回 override_value
└─ 无匹配 → 返回 default_value
}
示例:
flag_key: "enable_derived_properties"
default_value: "false"
overrides:
- scope: WORLD, value: "world-staging", override: "true" (staging World 开启)
- scope: USER, value: "admin-001", override: "true" (管理员开启)
evaluate("enable_derived_properties", {world: "world-prod", user: "user-001"})
→ "false"(默认值)
evaluate("enable_derived_properties", {world: "world-staging", user: "user-001"})
→ "true"(World 覆盖)
#5.3 各进程的 Feature Flag 客户端
// onto-control 中的 Feature Flag 使用
@Service
public class FeatureFlagService {
private final FeatureFlagRepository flagRepo;
private final LoadingCache<String, FeatureFlag> cache;
public FeatureFlagService(FeatureFlagRepository flagRepo) {
this.flagRepo = flagRepo;
this.cache = Caffeine.newBuilder()
.maximumSize(200)
.expireAfterWrite(Duration.ofMinutes(5))
.build(flagRepo::findByKey);
}
public boolean isEnabled(String flagKey, EvaluationContext context) {
var flag = cache.get(flagKey);
if (flag == null) return false;
return flag.evaluate(context);
}
}
// 使用示例
public class ActionServiceImpl {
public ActionResult executeAction(ActionRequest request) {
if (!featureFlags.isEnabled("enable_action_v2", context)) {
return executeActionV1(request); // 旧逻辑
}
return executeActionV2(request); // 新逻辑
}
}
# onto-intelligence 中的 Feature Flag 使用
class FeatureFlagClient:
"""Feature Flag 客户端 — 从 onto-control 获取 Flag 状态"""
def __init__(self, control_stub: ControlServiceStub):
self.stub = control_stub
self._cache: dict[str, CachedFlag] = {}
self._cache_ttl = timedelta(minutes=5)
async def is_enabled(
self, flag_key: str, context: EvaluationContext | None = None
) -> bool:
cached = self._cache.get(flag_key)
if cached and not cached.is_expired:
return cached.evaluate(context)
# 从 onto-control 拉取最新值
response = await self.stub.GetFeatureFlag(
GetFeatureFlagRequest(key=flag_key)
)
flag = CachedFlag.from_response(response, self._cache_ttl)
self._cache[flag_key] = flag
return flag.evaluate(context)
# 使用示例
async def evaluate_rule(rule_id: str, context: dict):
if await feature_flags.is_enabled("enable_rule_v2_engine", eval_ctx):
return await rule_engine_v2.evaluate(rule_id, context)
return await rule_engine_v1.evaluate(rule_id, context)
#6. 配置热更新
#6.1 哪些配置支持热更新
配置热更新能力矩阵:
配置类型 支持热更新? 更新方式
────────────────────────────────────────────────
Feature Flag ✅ Ontology 修改 → 事件通知
日志级别 ✅ Admin API / Actuator
限流阈值 ✅ Resilience4j 运行时修改
断路器参数 ✅ Resilience4j 运行时修改
gRPC 超时 ⚠️ 部分 需要重建 Channel
数据库连接池大小 ⚠️ 部分 HikariCP 支持运行时调整
服务端口 ❌ 需要重启
gRPC TLS 证书 ❌ 需要重启
Kafka Consumer Group ❌ 需要重启
#6.2 日志级别动态调整
onto-control(Spring Boot Actuator):
POST /actuator/loggers/com.onto.control
Body: {"configuredLevel": "DEBUG"}
→ 立即生效,无需重启
onto-data(Quarkus):
# Quarkus 不支持运行时日志级别调整
# 需要通过配置变更 + 重启
# 但可以通过自定义端点实现
onto-intelligence(Python logging):
POST /admin/log-level
Body: {"logger": "onto_intelligence", "level": "DEBUG"}
→ 调用 logging.getLogger(name).setLevel(level)
→ 立即生效
#6.3 配置变更事件
配置变更通过 Kafka 事件通知所有进程:
配置变更事件流:
管理员修改 Feature Flag
│
├─ onto-control 更新 PostgreSQL
│
├─ 发布 Kafka 事件
│ topic: platform.config.events
│ {
│ "type": "FEATURE_FLAG_CHANGED",
│ "key": "enable_derived_properties",
│ "old_value": "false",
│ "new_value": "true",
│ "scope": "WORLD:world-staging",
│ "changed_by": "admin-001",
│ "timestamp": "2026-03-24T14:30:00Z"
│ }
│
├─ onto-data 收到事件
│ → 清除本地 Feature Flag 缓存
│ → 下次请求时重新拉取
│
└─ onto-intelligence 收到事件
→ 清除本地 Feature Flag 缓存
→ 下次请求时重新拉取
#7. 配置校验与启动检查
#7.1 启动时配置校验
每个进程在启动时都会执行配置校验,确保必要配置存在且有效:
# onto-intelligence 启动时配置校验
class ConfigValidator:
"""配置校验器 — 启动时运行"""
def validate(self, settings: AppSettings) -> list[str]:
errors: list[str] = []
# 必填项检查
if not settings.db.host:
errors.append("DB_HOST is required")
if not settings.grpc.control_address:
errors.append("GRPC_CONTROL_ADDRESS is required")
if not settings.kafka.bootstrap_servers:
errors.append("KAFKA_BOOTSTRAP_SERVERS is required")
# 连通性检查
if not self._check_db_connection(settings.db):
errors.append(f"Cannot connect to database at {settings.db.host}:{settings.db.port}")
if not self._check_grpc_connection(settings.grpc.control_address):
errors.append(f"Cannot connect to onto-control at {settings.grpc.control_address}")
# 值域检查
if settings.db.pool_size < 1 or settings.db.pool_size > 100:
errors.append(f"DB_POOL_SIZE must be 1-100, got {settings.db.pool_size}")
if settings.grpc.max_message_size > 100 * 1024 * 1024:
errors.append("GRPC_MAX_MESSAGE_SIZE exceeds 100MB limit")
return errors
# main.py
async def startup():
validator = ConfigValidator()
errors = validator.validate(settings)
if errors:
for error in errors:
logger.error(f"Configuration error: {error}")
sys.exit(1)
logger.info("Configuration validated successfully")
#7.2 配置诊断端点
每个进程都暴露配置诊断端点,方便运维排查:
配置诊断 API:
onto-control:
GET /actuator/env — 查看所有环境变量(敏感值脱敏)
GET /actuator/configprops — 查看所有配置属性及其来源
GET /actuator/info — 查看应用信息(版本、构建时间)
onto-data:
GET /q/config — 查看 Quarkus 配置
onto-intelligence:
GET /admin/config — 查看当前生效配置(密码脱敏)
GET /admin/config/sources — 查看各配置项的来源(代码默认/env/文件)
#7.3 配置漂移检测
配置漂移检测(每小时运行):
1. 从各进程收集当前生效配置
2. 与期望配置(.env 文件)对比
3. 检测是否有意外差异
检测项目:
├─ 环境变量是否被运行时修改覆盖
├─ Feature Flag 是否与预期一致
├─ 连接池参数是否匹配
└─ 日志级别是否被临时修改
漂移报告:
┌─────────────────────────────────────────────────────┐
│ Configuration Drift Report - 2026-03-24 15:00 │
├─────────────────────────────────────────────────────┤
│ onto-control: │
│ ⚠️ log.level: expected=INFO, actual=DEBUG │
│ (manually changed via Actuator at 14:30) │
│ ✅ datasource.pool-size: expected=20, actual=20 │
│ │
│ onto-intelligence: │
│ ✅ All configs match expected values │
│ │
│ Feature Flags: │
│ ⚠️ enable_derived_properties: expected=false │
│ actual=true (override for WORLD:world-staging) │
└─────────────────────────────────────────────────────┘
#8. 配置最佳实践
#8.1 命名规范
配置命名规范:
环境变量命名:
格式:{NAMESPACE}_{COMPONENT}_{KEY}
示例:
SPRING_DATASOURCE_URL (Spring Boot 标准)
QUARKUS_DATASOURCE_JDBC_URL (Quarkus 标准)
DB_HOST (Python 自定义)
GRPC_CONTROL_ADDRESS (跨语言通用)
KAFKA_BOOTSTRAP_SERVERS (跨语言通用)
YAML Key 命名:
格式:kebab-case(Spring Boot)/ dot-separated(Quarkus)
示例:
spring.datasource.hikari.maximum-pool-size
quarkus.datasource.jdbc.max-size
Python 配置命名:
格式:snake_case(Pydantic 字段)
示例:
db.pool_size
grpc.control_address
#8.2 环境变量覆盖规则
环境变量到配置的映射规则:
Spring Boot:
SPRING_DATASOURCE_URL → spring.datasource.url
规则:_ 替换为 . ,全部小写
Quarkus:
QUARKUS_DATASOURCE_JDBC_URL → quarkus.datasource.jdbc.url
规则:同 Spring Boot
Pydantic Settings:
DB_HOST → DatabaseSettings.host (通过 env_prefix="DB_" 匹配)
GRPC__CONTROL_ADDRESS → GrpcSettings.control_address (通过 env_nested_delimiter="__")
#8.3 新增配置 Checklist
添加新配置项的检查清单:
□ 在代码中定义合理的默认值(Layer 1)
□ 在 YAML/properties 中添加带注释的示例(Layer 2)
□ 在 .env.example 中添加环境变量(Layer 3)
□ 如果是敏感信息,确保只通过环境变量/Secret 注入
□ 添加配置校验(启动时检查)
□ 更新配置诊断端点的脱敏规则
□ 在文档中记录新配置项
□ 测试:无配置时使用默认值正常启动
□ 测试:环境变量覆盖生效
□ 测试:无效值被校验拦截
#Key Takeaways
- 四层模型:代码默认值 → YAML → 环境变量 → 运行时配置,后者覆盖前者,兼顾开发便利和生产安全。
- Docker Compose 统一管理:单一
.env文件控制三个进程的配置,消除配置散落在多处的问题。 - Secret 分离:敏感配置通过 Docker Secrets 注入,绝不出现在代码仓库或镜像中。
- Feature Flag 自举:利用 Ontology 模型存储 Flag 定义,支持按 World/User/Role 粒度控制。
- 配置校验:启动时自动校验必填项、连通性和值域,快速失败而不是运行中莫名出错。
- 热更新边界:清晰区分哪些配置可以热更新、哪些需要重启,避免运维误操作。
- 漂移检测:定期对比实际配置与期望配置,发现意外差异及时告警。
#Next Article
下一篇 S2-13 AI + 人类协作开发模式:Claude 实现 10x 效率 将分享智策平台的开发方法论——如何利用 AI 编程助手(Claude)完成 70-80% 的代码编写,2-4 小时交付一个完整的 gRPC Service,以及 CLAUDE.md 守护规则如何确保 AI 生成代码的质量。
tags: configuration, YAML, Docker-Compose, environment-variables, secrets, feature-flags, hot-reload, pydantic-settings, Spring-Boot, Quarkus