配置管理
配置来源优先级
命令行参数 > 环境变量 > 配置文件 > 默认值
| 来源 | 优点 | 缺点 | 适用 |
|---|
| 环境变量 | 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 管理 |