返回博客

配置管理:从 YAML 到运行时的配置链路

在一个由三个进程组成的平台中,配置管理看似简单,实则是运维痛点的重灾区。一个错误的数据库连接字符串可以让整个平台瘫痪,一个遗漏的环境变量可以让功能神秘消失。

Coomia发布于 2025年7月5日17 分钟阅读
分享本文Twitter / X

配置管理:从 YAML 到运行时的配置链路

系列:S2 架构全景 · 第 12 篇 | 难度:中级 | 阅读时间:18 分钟

#TL;DR

  • 智策平台采用四层配置模型:代码默认值 → YAML 文件 → 环境变量 → 运行时动态配置,后者覆盖前者,实现从开发到生产的配置无缝过渡。
  • 三个技术栈(Spring Boot / Quarkus / FastAPI)各有配置惯例,平台通过 Docker Compose 环境变量注入统一管理,单一 .env 文件控制整个集群配置。
  • 敏感配置(数据库密码、API Key)使用 Docker Secrets + 环境变量分离,绝不硬编码在 YAML 中;Feature Flag 使用 Ontology 元数据驱动,支持按 World 粒度开关功能。

#引言:配置是分布式系统的神经系统

在一个由三个进程组成的平台中,配置管理看似简单,实则是运维痛点的重灾区。一个错误的数据库连接字符串可以让整个平台瘫痪,一个遗漏的环境变量可以让功能神秘消失。

Code
配置管理的挑战:

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 配置优先级

智策平台遵循"约定优于配置"原则,定义了四层配置,优先级从低到高:

Code
配置优先级(从低到高):

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 各层的适用场景

Code
配置分类决策树:

这个配置值...
  │
  ├─ 几乎不变?(端口、协议版本)
  │   └─ → Layer 1: 代码默认值
  │
  ├─ 按环境不同?(数据库地址、Kafka 地址)
  │   └─ → Layer 2 + Layer 3: YAML + 环境变量覆盖
  │
  ├─ 是敏感信息?(密码、密钥)
  │   └─ → Layer 3: 环境变量(从 Secret 注入)
  │
  └─ 需要动态调整?(限流、开关)
      └─ → Layer 4: 运行时配置

#2. 各进程的配置实现

#2.1 onto-control:Spring Boot 配置

Spring Boot 是三个框架中配置能力最强的,支持 Profile、YAML 多文档、属性绑定等:

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 格式:

PROPERTIES
# 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 实现类型安全的配置:

Python
# 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 自动加载:

Bash
# 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 配置注入

YAML
# 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 多环境覆盖

Code
多环境配置策略:

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 分离原则

Code
Secret 管理规则:

❌ 绝对禁止:
  - 密码硬编码在源代码中
  - 密码写在 YAML 配置文件并提交到 Git
  - 密码写在 Dockerfile 中
  - 密码写在 docker-compose.yml 中

✅ 正确做法:
  - .env 文件设置弱密码,仅用于本地开发
  - .env 文件在 .gitignore 中(不提交到 Git)
  - 提供 .env.example 模板(密码用 change_me 占位)
  - 生产环境使用 Docker Secrets 或 K8s Secrets

#4.2 Docker Secrets

YAML
# 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 文件读取

Python
# 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 模型来管理:

Code
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 评估逻辑

Code
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 客户端

Java
// 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);  // 新逻辑
    }
}
Python
# 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 哪些配置支持热更新

Code
配置热更新能力矩阵:

配置类型                支持热更新?   更新方式
────────────────────────────────────────────────
Feature Flag            ✅          Ontology 修改 → 事件通知
日志级别                ✅          Admin API / Actuator
限流阈值                ✅          Resilience4j 运行时修改
断路器参数              ✅          Resilience4j 运行时修改
gRPC 超时               ⚠️ 部分     需要重建 Channel
数据库连接池大小        ⚠️ 部分     HikariCP 支持运行时调整
服务端口                ❌          需要重启
gRPC TLS 证书           ❌          需要重启
Kafka Consumer Group    ❌          需要重启

#6.2 日志级别动态调整

Code
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 事件通知所有进程:

Code
配置变更事件流:

管理员修改 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 启动时配置校验

每个进程在启动时都会执行配置校验,确保必要配置存在且有效:

Python
# 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 配置诊断端点

每个进程都暴露配置诊断端点,方便运维排查:

Code
配置诊断 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 配置漂移检测

Code
配置漂移检测(每小时运行):

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 命名规范

Code
配置命名规范:

环境变量命名:
  格式:{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 环境变量覆盖规则

Code
环境变量到配置的映射规则:

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

Code
添加新配置项的检查清单:

□ 在代码中定义合理的默认值(Layer 1)
□ 在 YAML/properties 中添加带注释的示例(Layer 2)
□ 在 .env.example 中添加环境变量(Layer 3)
□ 如果是敏感信息,确保只通过环境变量/Secret 注入
□ 添加配置校验(启动时检查)
□ 更新配置诊断端点的脱敏规则
□ 在文档中记录新配置项
□ 测试:无配置时使用默认值正常启动
□ 测试:环境变量覆盖生效
□ 测试:无效值被校验拦截

#Key Takeaways

  1. 四层模型:代码默认值 → YAML → 环境变量 → 运行时配置,后者覆盖前者,兼顾开发便利和生产安全。
  2. Docker Compose 统一管理:单一 .env 文件控制三个进程的配置,消除配置散落在多处的问题。
  3. Secret 分离:敏感配置通过 Docker Secrets 注入,绝不出现在代码仓库或镜像中。
  4. Feature Flag 自举:利用 Ontology 模型存储 Flag 定义,支持按 World/User/Role 粒度控制。
  5. 配置校验:启动时自动校验必填项、连通性和值域,快速失败而不是运行中莫名出错。
  6. 热更新边界:清晰区分哪些配置可以热更新、哪些需要重启,避免运维误操作。
  7. 漂移检测:定期对比实际配置与期望配置,发现意外差异及时告警。

#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