文章

配置管理

配置管理

配置来源优先级

命令行参数 > 环境变量 > 配置文件 > 默认值
来源优点缺点适用
环境变量12-Factor 标准,容器友好不适合复杂结构敏感信息、环境标识
配置文件支持复杂结构需要文件管理业务配置
命令行参数灵活不适合大量配置覆盖个别配置
远程配置中心动态更新依赖外部服务运行时动态配置

配置结构定义

// internal/config/config.go
package config

type Config struct {
    App      AppConfig      `yaml:"app"      mapstructure:"app"`
    Server   ServerConfig   `yaml:"server"   mapstructure:"server"`
    Database DatabaseConfig `yaml:"database" mapstructure:"database"`
    Redis    RedisConfig    `yaml:"redis"    mapstructure:"redis"`
    Log      LogConfig      `yaml:"log"      mapstructure:"log"`
    SkyWalking SkyWalkingConfig `yaml:"skywalking" mapstructure:"skywalking"`
}

type AppConfig struct {
    Name        string `yaml:"name"         mapstructure:"name"`
    Version     string `yaml:"version"      mapstructure:"version"`
    Environment string `yaml:"environment"  mapstructure:"environment"`
}

type ServerConfig struct {
    Port            int           `yaml:"port"              mapstructure:"port"`
    ReadTimeout     time.Duration `yaml:"read_timeout"      mapstructure:"read_timeout"`
    WriteTimeout    time.Duration `yaml:"write_timeout"     mapstructure:"write_timeout"`
    GracefulTimeout time.Duration `yaml:"graceful_timeout"  mapstructure:"graceful_timeout"`
}

type DatabaseConfig struct {
    Driver          string `yaml:"driver"           mapstructure:"driver"`
    Host            string `yaml:"host"             mapstructure:"host"`
    Port            int    `yaml:"port"             mapstructure:"port"`
    Username        string `yaml:"username"         mapstructure:"username"`
    Password        string `yaml:"password"         mapstructure:"password"`
    Database        string `yaml:"database"         mapstructure:"database"`
    MaxOpenConns    int    `yaml:"max_open_conns"   mapstructure:"max_open_conns"`
    MaxIdleConns    int    `yaml:"max_idle_conns"   mapstructure:"max_idle_conns"`
    ConnMaxLifetime time.Duration `yaml:"conn_max_lifetime" mapstructure:"conn_max_lifetime"`
}

func (d DatabaseConfig) DSN() string {
    return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=true&loc=Local",
        d.Username, d.Password, d.Host, d.Port, d.Database)
}

type RedisConfig struct {
    Addr         string `yaml:"addr"          mapstructure:"addr"`
    Password     string `yaml:"password"      mapstructure:"password"`
    DB           int    `yaml:"db"            mapstructure:"db"`
    PoolSize     int    `yaml:"pool_size"     mapstructure:"pool_size"`
    MinIdleConns int    `yaml:"min_idle_conns" mapstructure:"min_idle_conns"`
}

type LogConfig struct {
    Level  string `yaml:"level"  mapstructure:"level"`
    Format string `yaml:"format" mapstructure:"format"`
    Output string `yaml:"output" mapstructure:"output"`
}

type SkyWalkingConfig struct {
    OAPService  string `yaml:"oap_service"  mapstructure:"oap_service"`
    ServiceName string `yaml:"service_name" mapstructure:"service_name"`
    InstanceName string `yaml:"instance_name" mapstructure:"instance_name"`
}

配置文件示例

# configs/config.yaml
app:
  name: care-mate
  version: 1.0.0
  environment: production

server:
  port: 8080
  read_timeout: 10s
  write_timeout: 30s
  graceful_timeout: 30s

database:
  driver: mysql
  host: rm-xxx.mysql.rds.aliyuncs.com
  port: 3306
  username: care_mate
  password: ${DB_PASSWORD}  # 环境变量替换
  database: care_mate
  max_open_conns: 50
  max_idle_conns: 10
  conn_max_lifetime: 30m

redis:
  addr: r-xxx.redis.rds.aliyuncs.com:6379
  password: ${REDIS_PASSWORD}
  db: 0
  pool_size: 20
  min_idle_conns: 5

log:
  level: info
  format: json
  output: stdout

skywalking:
  oap_service: skywalking-oap.monitoring:11800
  service_name: care-mate
  instance_name: care-mate-prod-001

使用 Viper 加载配置

import "github.com/spf13/viper"

func Load() (*Config, error) {
    var cfg Config

    // 设置默认值
    viper.SetDefault("server.port", 8080)
    viper.SetDefault("server.read_timeout", "10s")
    viper.SetDefault("database.max_open_conns", 50)
    viper.SetDefault("log.level", "info")

    // 配置文件
    viper.SetConfigName("config")     // 文件名(不含扩展名)
    viper.SetConfigType("yaml")
    viper.AddConfigPath("./configs")  // 查找路径
    viper.AddConfigPath("/etc/care-mate")
    viper.AddConfigPath(".")

    // 环境变量
    viper.SetEnvPrefix("CARE_MATE")  // 前缀:CARE_MATE_
    viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
    viper.AutomaticEnv()

    // 读取配置文件
    if err := viper.ReadInConfig(); err != nil {
        if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
            return nil, fmt.Errorf("read config: %w", err)
        }
        // 配置文件不存在时用默认值 + 环境变量
    }

    // 反序列化到结构体
    if err := viper.Unmarshal(&cfg, viper.DecodeHook(
        viper.DecodeHook(mapstructure.StringToTimeDurationHookFunc()),
    )); err != nil {
        return nil, fmt.Errorf("unmarshal config: %w", err)
    }

    return &cfg, nil
}

环境变量映射规则

# viper 配置:SetEnvPrefix("CARE_MATE"), SetEnvKeyReplacer(".", "_")
# 配置文件中的 key → 环境变量名
# server.port         → CARE_MATE_SERVER_PORT
# database.host       → CARE_MATE_DATABASE_HOST
# database.password   → CARE_MATE_DATABASE_PASSWORD

纯标准库方案(不依赖 viper)

import (
    "os"
    "gopkg.in/yaml.v3"
)

func Load() (*Config, error) {
    cfg := &Config{
        // 默认值
        Server: ServerConfig{
            Port:            8080,
            ReadTimeout:     10 * time.Second,
            WriteTimeout:    30 * time.Second,
            GracefulTimeout: 30 * time.Second,
        },
        Database: DatabaseConfig{
            MaxOpenConns:    50,
            MaxIdleConns:    10,
            ConnMaxLifetime: 30 * time.Minute,
        },
        Log: LogConfig{
            Level:  "info",
            Format: "json",
            Output: "stdout",
        },
    }

    // 1. 读取配置文件
    configFile := os.Getenv("CONFIG_FILE")
    if configFile == "" {
        configFile = "configs/config.yaml"
    }

    if data, err := os.ReadFile(configFile); err == nil {
        if err := yaml.Unmarshal(data, cfg); err != nil {
            return nil, fmt.Errorf("parse config: %w", err)
        }
    }

    // 2. 环境变量覆盖
    if v := os.Getenv("SERVER_PORT"); v != "" {
        if port, err := strconv.Atoi(v); err == nil {
            cfg.Server.Port = port
        }
    }
    if v := os.Getenv("DB_HOST"); v != "" {
        cfg.Database.Host = v
    }
    if v := os.Getenv("DB_PASSWORD"); v != "" {
        cfg.Database.Password = v
    }
    if v := os.Getenv("REDIS_PASSWORD"); v != "" {
        cfg.Redis.Password = v
    }

    // 3. 验证配置
    if err := cfg.Validate(); err != nil {
        return nil, err
    }

    return cfg, nil
}

func (c *Config) Validate() error {
    if c.Database.Host == "" {
        return fmt.Errorf("database.host is required")
    }
    if c.Database.Port == 0 {
        return fmt.Errorf("database.port is required")
    }
    if c.Server.Port == 0 {
        return fmt.Errorf("server.port is required")
    }
    return nil
}

配置热更新

// 使用 fsnotify 监听配置文件变化
import "github.com/fsnotify/fsnotify"

func WatchConfig(configPath string, onChange func(*Config)) {
    viper.WatchConfig()
    viper.OnConfigChange(func(e fsnotify.Event) {
        slog.Info("config file changed", "file", e.Name)
        var cfg Config
        if err := viper.Unmarshal(&cfg); err != nil {
            slog.Error("failed to reload config", "error", err)
            return
        }
        onChange(&cfg)
    })
}

K8s ConfigMap/Secret 集成

# k8s-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: care-mate-config
data:
  config.yaml: |
    app:
      name: care-mate
      environment: production
    server:
      port: 8080
---
# k8s-secret.yaml
apiVersion: v1
kind: Secret
metadata:
  name: care-mate-secrets
type: Opaque
stringData:
  DB_PASSWORD: "encrypted_password"
  REDIS_PASSWORD: "encrypted_password"
---
# deployment.yaml
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    spec:
      containers:
        - name: care-mate
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: care-mate-secrets
                  key: DB_PASSWORD
          volumeMounts:
            - name: config
              mountPath: /etc/care-mate
      volumes:
        - name: config
          configMap:
            name: care-mate-config

配置管理检查清单

检查项说明
✅ 敏感信息用环境变量密码、密钥不放配置文件
✅ 配置文件不放密码用 ${VAR} 或环境变量覆盖
✅ 提供合理默认值减少必须配置的项
✅ 配置验证启动时检查必填项
✅ 多环境配置dev/test/prod 配置分离
✅ 配置版本化配置文件纳入 Git 管理