配置管理Skill configuration-management

本指南提供了一套全面的应用程序配置管理方法,涵盖环境变量、配置文件、秘密管理、特性标志等,遵循12因素应用原则,适用于多环境配置管理。

后端开发 0 次安装 0 次浏览 更新于 3/3/2026

配置管理

概览

全面的指南,用于管理跨环境的应用程序配置,包括环境变量、配置文件、秘密、特性标志以及遵循12因素应用程序方法论。

何时使用

  • 为不同环境设置配置
  • 管理秘密和凭证
  • 实施特性标志
  • 创建配置层级
  • 遵循12因素应用程序原则
  • 将配置迁移到云服务
  • 实施动态配置
  • 管理多租户配置

指令

1. 环境变量

基本设置(.env文件)

# .env.development
NODE_ENV=development
PORT=3000
DATABASE_URL=postgresql://localhost:5432/myapp_dev
REDIS_URL=redis://localhost:6379
LOG_LEVEL=debug
API_KEY=dev-api-key-12345

# .env.production
NODE_ENV=production
PORT=8080
DATABASE_URL=${DATABASE_URL}  # 来自环境
REDIS_URL=${REDIS_URL}
LOG_LEVEL=info
API_KEY=${API_KEY}  # 来自秘密管理器

# .env.test
NODE_ENV=test
DATABASE_URL=postgresql://localhost:5432/myapp_test
LOG_LEVEL=error

加载环境变量

// config/env.ts
import dotenv from 'dotenv';
import path from 'path';

// 加载特定于环境的.env文件
const envFile = `.env.${process.env.NODE_ENV || 'development'}`;
dotenv.config({ path: path.resolve(process.cwd(), envFile) });

// 验证必需的变量
const required = ['DATABASE_URL', 'PORT', 'API_KEY'];
const missing = required.filter(key => !process.env[key]);

if (missing.length > 0) {
  throw new Error(`缺少必需的环境变量:${missing.join(', ')}`);
}

// 导出类型化的配置
export const config = {
  env: process.env.NODE_ENV || 'development',
  port: parseInt(process.env.PORT || '3000', 10),
  database: {
    url: process.env.DATABASE_URL!,
    poolSize: parseInt(process.env.DB_POOL_SIZE || '10', 10)
  },
  redis: {
    url: process.env.REDIS_URL || 'redis://localhost:6379'
  },
  logging: {
    level: process.env.LOG_LEVEL || 'info'
  },
  api: {
    key: process.env.API_KEY!,
    timeout: parseInt(process.env.API_TIMEOUT || '5000', 10)
  }
} as const;

Python配置

# config/settings.py
import os
from pathlib import Path
from dotenv import load_dotenv

# 加载.env文件
env_file = f'.env.{os.getenv("ENVIRONMENT", "development")}'
load_dotenv(Path(__file__).parent.parent / env_file)

class Config:
    """基础配置"""
    ENV = os.getenv('ENVIRONMENT', 'development')
    DEBUG = os.getenv('DEBUG', 'False').lower() == 'true'
    SECRET_KEY = os.getenv('SECRET_KEY')

    # 数据库
    DATABASE_URL = os.getenv('DATABASE_URL')
    DB_POOL_SIZE = int(os.getenv('DB_POOL_SIZE', 10))

    # Redis
    REDIS_URL = os.getenv('REDIS_URL', 'redis://localhost:6379')

    # API
    API_KEY = os.getenv('API_KEY')
    API_TIMEOUT = int(os.getenv('API_TIMEOUT', 5000))

class DevelopmentConfig(Config):
    """开发配置"""
    DEBUG = True
    LOG_LEVEL = 'DEBUG'

class ProductionConfig(Config):
    """生产配置"""
    DEBUG = False
    LOG_LEVEL = 'INFO'

class TestConfig(Config):
    """测试配置"""
    TESTING = True
    DATABASE_URL = 'sqlite:///:memory:'

# 配置字典
config_by_name = {
    'development': DevelopmentConfig,
    'production': ProductionConfig,
    'test': TestConfig
}

# 获取活动配置
config = config_by_name[Config.ENV]()

2. 配置层级

// config/config.ts
import deepmerge from 'deepmerge';

// 基础配置(所有环境共享)
const baseConfig = {
  app: {
    name: 'MyApp',
    version: '1.0.0'
  },
  server: {
    timeout: 30000,
    bodyLimit: '100kb'
  },
  database: {
    poolSize: 10,
    idleTimeout: 30000
  },
  logging: {
    format: 'json',
    destination: 'stdout'
  }
};

// 环境特定的覆盖
const developmentConfig = {
  server: {
    port: 3000
  },
  database: {
    url: 'postgresql://localhost:5432/myapp_dev',
    logging: true
  },
  logging: {
    level: 'debug',
    prettyPrint: true
  }
};

const productionConfig = {
  server: {
    port: 8080,
    trustProxy: true
  },
  database: {
    url: process.env.DATABASE_URL,
    ssl: true,
    logging: false
  },
  logging: {
    level: 'info',
    prettyPrint: false
  }
};

// 合并配置
const configs = {
  development: deepmerge(baseConfig, developmentConfig),
  production: deepmerge(baseConfig, productionConfig),
  test: deepmerge(baseConfig, {
    database: { url: 'postgresql://localhost:5432/myapp_test' }
  })
};

export const config = configs[process.env.NODE_ENV || 'development'];

YAML配置文件

# config/default.yml
app:
  name: MyApp
  version: 1.0.0

server:
  timeout: 30000
  bodyLimit: 100kb

database:
  poolSize: 10
  idleTimeout: 30000

# config/development.yml
server:
  port: 3000

database:
  url: postgresql://localhost:5432/myapp_dev
  logging: true

logging:
  level: debug
  prettyPrint: true

# config/production.yml
server:
  port: 8080
  trustProxy: true

database:
  url: ${DATABASE_URL}
  ssl: true
  logging: false

logging:
  level: info
  prettyPrint: false
// 加载YAML配置
import yaml from 'js-yaml';
import fs from 'fs';
import path from 'path';

function loadYamlConfig(env: string) {
  const defaultConfig = yaml.load(
    fs.readFileSync(path.join(__dirname, 'config/default.yml'), 'utf8')
  );

  const envConfig = yaml.load(
    fs.readFileSync(path.join(__dirname, `config/${env}.yml`), 'utf8')
  );

  return deepmerge(defaultConfig, envConfig);
}

export const config = loadYamlConfig(process.env.NODE_ENV || 'development');

3. 秘密管理

AWS Secrets Manager

// secrets/aws-secrets-manager.ts
import { SecretsManagerClient, GetSecretValueCommand } from '@aws-sdk/client-secrets-manager';

export class SecretManager {
  private client: SecretsManagerClient;
  private cache = new Map<string, { value: any; expiry: number }>();
  private cacheTtl = 300000; // 5分钟

  constructor() {
    this.client = new SecretsManagerClient({ region: process.env.AWS_REGION });
  }

  async getSecret(secretName: string): Promise<any> {
    // 检查缓存
    const cached = this.cache.get(secretName);
    if (cached && cached.expiry > Date.now()) {
      return cached.value;
    }

    try {
      const command = new GetSecretValueCommand({ SecretId: secretName });
      const response = await this.client.send(command);

      const secret = JSON.parse(response.SecretString || '{}');

      // 缓存秘密
      this.cache.set(secretName, {
        value: secret,
        expiry: Date.now() + this.cacheTtl
      });

      return secret;
    } catch (error) {
      throw new Error(`无法检索秘密${secretName}:${error.message}`);
    }
  }

  async getDatabaseCredentials(): Promise<DatabaseCredentials> {
    return this.getSecret('prod/database/credentials');
  }

  async getApiKey(service: string): Promise<string> {
    const secrets = await this.getSecret('prod/api-keys');
    return secrets[service];
  }
}

// 使用
const secretManager = new SecretManager();

async function connectDatabase() {
  const credentials = await secretManager.getDatabaseCredentials();

  return createConnection({
    host: credentials.host,
    port: credentials.port,
    username: credentials.username,
    password: credentials.password,
    database: credentials.database
  });
}

HashiCorp Vault

// secrets/vault.ts
import vault from 'node-vault';

export class VaultClient {
  private client: any;

  constructor() {
    this.client = vault({
      apiVersion: 'v1',
      endpoint: process.env.VAULT_ADDR || 'http://localhost:8200',
      token: process.env.VAULT_TOKEN
    });
  }

  async getSecret(path: string): Promise<any> {
    try {
      const result = await this.client.read(path);
      return result.data.data;
    } catch (error) {
      throw new Error(`无法从${path}读取秘密:${error.message}`);
    }
  }

  async getDatabaseConfig(): Promise<DatabaseConfig> {
    return this.getSecret('secret/data/database');
  }

  async getApiKeys(): Promise<Record<string, string>> {
    return this.getSecret('secret/data/api-keys');
  }

  // 动态数据库凭证(自动旋转)
  async getDynamicDBCredentials(): Promise<Credentials> {
    const result = await this.client.read('database/creds/readonly');
    return {
      username: result.data.username,
      password: result.data.password,
      leaseId: result.lease_id,
      leaseDuration: result.lease_duration
    };
  }
}

环境特定的秘密

// secrets/secret-provider.ts
export interface SecretProvider {
  getSecret(key: string): Promise<string>;
}

// 开发:使用.env文件
export class EnvFileSecretProvider implements SecretProvider {
  async getSecret(key: string): Promise<string> {
    const value = process.env[key];
    if (!value) {
      throw new Error(`秘密${key}在环境中未找到`);
    }
    return value;
  }
}

// 生产:使用AWS Secrets Manager
export class AWSSecretProvider implements SecretProvider {
  private secretManager: SecretManager;

  constructor() {
    this.secretManager = new SecretManager();
  }

  async getSecret(key: string): Promise<string> {
    const secrets = await this.secretManager.getSecret('prod/secrets');
    return secrets[key];
  }
}

// 工厂
export function createSecretProvider(): SecretProvider {
  if (process.env.NODE_ENV === 'production') {
    return new AWSSecretProvider();
  }
  return new EnvFileSecretProvider();
}

4. 特性标志

简单的特性标志实现

// feature-flags/feature-flag.ts
export interface FeatureFlag {
  enabled: boolean;
  rolloutPercentage?: number;
  allowedUsers?: string[];
  allowedEnvironments?: string[];
}

export class FeatureFlagManager {
  private flags: Map<string, FeatureFlag>;

  constructor(flags: Record<string, FeatureFlag>) {
    this.flags = new Map(Object.entries(flags));
  }

  isEnabled(
    flagName: string,
    context?: { userId?: string; environment?: string }
  ): boolean {
    const flag = this.flags.get(flagName);
    if (!flag) return false;

    // 检查是否全局禁用
    if (!flag.enabled) return false;

    // 检查环境限制
    if (flag.allowedEnvironments && context?.environment) {
      if (!flag.allowedEnvironments.includes(context.environment)) {
        return false;
      }
    }

    // 检查用户白名单
    if (flag.allowedUsers && context?.userId) {
      if (flag.allowedUsers.includes(context.userId)) {
        return true;
      }
    }

    // 检查推出百分比
    if (flag.rolloutPercentage !== undefined && context?.userId) {
      const hash = this.hashUserId(context.userId);
      return (hash % 100) < flag.rolloutPercentage;
    }

    return true;
  }

  private hashUserId(userId: string): number {
    let hash = 0;
    for (let i = 0; i < userId.length; i++) {
      hash = ((hash << 5) - hash) + userId.charCodeAt(i);
      hash |= 0;
    }
    return Math.abs(hash);
  }
}

// 配置
const featureFlags = {
  'new-dashboard': {
    enabled: true,
    rolloutPercentage: 50 // 50%的用户
  },
  'experimental-feature': {
    enabled: true,
    allowedUsers: ['user-123', 'user-456'],
    allowedEnvironments: ['development', 'staging']
  },
  'beta-api': {
    enabled: true,
    rolloutPercentage: 10
  }
};

const flagManager = new FeatureFlagManager(featureFlags);

// 使用
app.get('/api/dashboard', (req, res) => {
  if (flagManager.isEnabled('new-dashboard', {
    userId: req.user.id,
    environment: process.env.NODE_ENV
  })) {
    return res.json(getNewDashboard());
  }

  return res.json(getOldDashboard());
});

LaunchDarkly集成

// feature-flags/launchdarkly.ts
import LaunchDarkly from 'launchdarkly-node-server-sdk';

export class LaunchDarklyClient {
  private client: LaunchDarkly.LDClient;

  async initialize() {
    this.client = LaunchDarkly.init(process.env.LAUNCHDARKLY_SDK_KEY!);
    await this.client.waitForInitialization();
  }

  async isEnabled(flagKey: string, user: LaunchDarkly.LDUser): Promise<boolean> {
    return this.client.variation(flagKey, user, false);
  }

  async getVariation<T>(
    flagKey: string,
    user: LaunchDarkly.LDUser,
    defaultValue: T
  ): Promise<T> {
    return this.client.variation(flagKey, user, defaultValue);
  }

  close() {
    this.client.close();
  }
}

// 使用
const ldClient = new LaunchDarklyClient();
await ldClient.initialize();

app.get('/api/dashboard', async (req, res) => {
  const user = {
    key: req.user.id,
    email: req.user.email,
    custom: {
      groups: req.user.groups
    }
  };

  const showNewDashboard = await ldClient.isEnabled('new-dashboard', user);

  if (showNewDashboard) {
    return res.json(getNewDashboard());
  }

  return res.json(getOldDashboard());
});

5. 12-Factor App配置

// config/twelve-factor.ts

/**
 * 12-Factor App配置原则
 *
 * III. 配置 - 在环境中存储配置
 * - 配置与代码严格分离
 * - 配置在部署之间变化,代码不变化
 * - 存储在环境变量中
 */

// ✅ 好的:从环境配置
export const config = {
  database: {
    url: process.env.DATABASE_URL!,
    poolMin: parseInt(process.env.DB_POOL_MIN || '2', 10),
    poolMax: parseInt(process.env.DB_POOL_MAX || '10', 10)
  },
  redis: {
    url: process.env.REDIS_URL!
  },
  s3: {
    bucket: process.env.S3_BUCKET!,
    region: process.env.AWS_REGION!
  },
  sendgrid: {
    apiKey: process.env.SENDGRID_API_KEY!
  }
};

// ❌ 坏的:硬编码配置
const badConfig = {
  database: {
    host: 'prod-db.example.com',  // 硬编码!
    password: 'secretpassword'     // 代码中的机密!
  }
};

/**
 * 后端服务 - 将后端服务视为附加资源
 * - 数据库、缓存、消息队列等通过URL访问
 * - 应该可以在不更改代码的情况下互换
 */

// ✅ 好的:后端服务作为URL
const db = createConnection(process.env.DATABASE_URL);
const cache = createClient(process.env.REDIS_URL);

// 可以通过更改环境变量来交换服务
// DATABASE_URL=postgresql://localhost/dev  (本地开发)
// DATABASE_URL=postgresql://prod-db/app     (生产)

/**
 * 可处理性 - 快速启动和优雅关闭
 */
function startServer() {
  const server = app.listen(config.port, () => {
    console.log(`服务器在端口${config.port}上启动`);
  });

  // 优雅关闭
  process.on('SIGTERM', async () => {
    console.log('收到SIGTERM,优雅地关闭');

    server.close(() => {
      console.log('HTTP服务器已关闭');
    });

    await db.close();
    await cache.quit();

    process.exit(0);
  });
}

6. 配置验证

// config/validation.ts
import Joi from 'joi';

const configSchema = Joi.object({
  NODE_ENV: Joi.string()
    .valid('development', 'production', 'test')
    .default('development'),

  PORT: Joi.number()
    .port()
    .default(3000),

  DATABASE_URL: Joi.string()
    .uri()
    .required(),

  REDIS_URL: Joi.string()
    .uri()
    .default('redis://localhost:6379'),

  LOG_LEVEL: Joi.string()
    .valid('debug', 'info', 'warn', 'error')
    .default('info'),

  API_KEY: Joi.string()
    .min(32)
    .required(),

  API_TIMEOUT: Joi.number()
    .min(1000)
    .max(30000)
    .default(5000),

  ENABLE_METRICS: Joi.boolean()
    .default(false)
});

export function validateConfig() {
  const { error, value } = configSchema.validate(process.env, {
    allowUnknown: true,  // 允许其他环境变量
    stripUnknown: true   // 移除未知变量
  });

  if (error) {
    throw new Error(`配置验证错误:${error.message}`);
  }

  return value;
}

// 使用
const validatedConfig = validateConfig();

7. 动态配置(远程配置)

// config/remote-config.ts
export class RemoteConfigService {
  private config: Map<string, any> = new Map();
  private pollInterval: NodeJS.Timeout | null = null;

  constructor(private configServiceUrl: string) {}

  async initialize() {
    await this.fetchConfig();
    this.startPolling();
  }

  private async fetchConfig() {
    try {
      const response = await fetch(`${this.configServiceUrl}/config`);
      const config = await response.json();

      for (const [key, value] of Object.entries(config)) {
        const oldValue = this.config.get(key);
        if (oldValue !== value) {
          console.log(`配置已更改:${key} = ${value}`);
          this.config.set(key, value);
        }
      }
    } catch (error) {
      console.error('无法获取远程配置:', error);
    }
  }

  private startPolling() {
    // 每60秒轮询一次
    this.pollInterval = setInterval(() => {
      this.fetchConfig();
    }, 60000);
  }

  get(key: string, defaultValue?: any): any {
    return this.config.get(key) ?? defaultValue;
  }

  stop() {
    if (this.pollInterval) {
      clearInterval(this.pollInterval);
    }
  }
}

// 使用
const remoteConfig = new RemoteConfigService('https://config-service.example.com');
await remoteConfig.initialize();

app.get('/api/users', (req, res) => {
  const pageSize = remoteConfig.get('api.users.pageSize', 20);
  const enableCache = remoteConfig.get('api.users.enableCache', false);

  // 使用动态配置值
});

最佳实践

✅ 做

  • 在环境变量中存储配置
  • 为每个环境使用不同的配置文件
  • 在启动时验证配置
  • 使用秘密管理器处理敏感数据
  • 永远不要将秘密提交到版本控制
  • 提供合理的默认值
  • 记录所有配置选项
  • 使用类型安全的配置对象
  • 实施配置层级(基础+覆盖)
  • 使用特性标志进行逐步推出
  • 遵循12因素应用程序原则
  • 实施对缺失配置的优雅降级
  • 缓存秘密以减少API调用

❌ 不做

  • 在源代码中硬编码配置
  • 提交带有真实秘密的.env文件
  • 在服务之间使用不同的配置格式
  • 以纯文本形式存储秘密
  • 通过API暴露配置
  • 在开发中使用生产凭证
  • 忽略配置验证错误
  • 到处直接访问process.env
  • 在数据库中存储配置(循环依赖)
  • 将配置与业务逻辑混合

常见模式

模式1:配置服务

export class ConfigService {
  private static instance: ConfigService;
  private config: Config;

  private constructor() {
    this.config = loadAndValidateConfig();
  }

  static getInstance(): ConfigService {
    if (!ConfigService.instance) {
      ConfigService.instance = new ConfigService();
    }
    return ConfigService.instance;
  }

  get<K extends keyof Config>(key: K): Config[K] {
    return this.config[key];
  }
}

模式2:配置构建器

export class ConfigBuilder {
  private config: Partial<Config> = {};

  withDatabase(url: string): this {
    this.config.database = { url };
    return this;
  }

  withRedis(url: string): this {
    this.config.redis = { url };
    return this;
  }

  build(): Config {
    return this.config as Config;
  }
}

工具和资源

  • dotenv:从.env文件中加载环境变量
  • convict:带验证的配置管理
  • config:Node.js的分层配置
  • AWS Secrets Manager:基于云的秘密存储
  • HashiCorp Vault:秘密和加密管理
  • LaunchDarkly:特性标志管理
  • ConfigCat:特性标志和配置服务
  • Consul:服务配置和发现