多环境与配置管理:viper/环境变量怎么组织
一句话结论:遵循 12-Factor 的思路——配置与代码分离,按“默认值 < 配置文件 < 环境变量 < 启动参数”的优先级分层覆盖。做法是:每个环境一份 config.{env}.yaml(dev/test/prod),敏感信息(密码、密钥)一律不进配置文件、只从环境变量或密钥管理服务注入;viper 负责读取文件并支持 AutomaticEnv 让环境变量按 APP_DB_HOST 这种前缀+点转下划线的规则覆盖配置项;启动早期把配置反序列化进一个带类型的 Config 结构体,再通过构造函数注入各层,避免代码里到处 os.Getenv。
要点
- 目录组织:configs/ 下放 config.dev.yaml、config.prod.yaml 等,或用 -config 参数显式指定;.gitignore 掉真实密钥。
- 环境变量命名约定:前缀 APP_ + 配置路径大写化,如 db.host → APP_DB_HOST;viper 用 SetEnvKeyReplacer 把 "." 替换成 "_" 即可自动映射。
- 覆盖顺序(优先级从高到低):命令行 flag > 环境变量 > 配置文件 > 内置默认值(viper.SetDefault)。
- 类型安全:启动时 viper.Unmarshal(&cfg) 进 Config 结构体(yaml/mapstructure tag),配置错、缺必填项应 fail-fast,不要运行到一半才 panic。
- 进程内传递:Config 只读共享(可用 atomic 指针快照,见热更新题),初始化 DB/Redis/Logger 后注入 service,禁止全局散读。
viper 初始化示例
viper.SetConfigName("config." + env) // env = dev/prod/...
viper.SetConfigType("yaml")
viper.AddConfigPath("./configs")
viper.SetEnvPrefix("APP") // APP_ 前缀
viper.SetEnvKeyReplacer(strings.NewReplacer(".", "_")) // db.host -> APP_DB_HOST
viper.AutomaticEnv() // 环境变量覆盖同名配置
if err := viper.ReadInConfig(); err != nil { log.Fatalf("读取配置失败: %v", err) }
var cfg Config
if err := viper.Unmarshal(&cfg); err != nil { log.Fatalf("解析配置失败: %v", err) }
# config.prod.yaml 只放非敏感项
server:
port: 8080
mode: release
db:
host: 10.0.0.5 # 用户名/密码由 APP_DB_USER、APP_DB_PASSWORD 注入
maxOpenConns: 100
追问记忆点
- 追问:为什么密码不进 yaml?——配置文件会进 Git、会被备份/镜像,泄漏面大;环境变量或 KMS 只在运行时注入,配合权限管理可审计。
- 追问:AutomaticEnv 与 BindEnv 区别?——AutomaticEnv 是“读不到配置时自动回落查环境变量”;BindEnv 是把某个 key 显式绑定到指定环境变量,优先级更可控。
- 记忆点:默认值 < 文件 < 环境变量 < flag;敏感信息只走环境变量/密钥服务;启动即 Unmarshal 成类型化 Config 并 fail-fast;代码不散读环境变量。