Go 配置管理
Go 服务配置管理方案,涵盖环境变量、Viper 多源配置、Config 结构体模式与热重载实践。
#type / concept
#status / growing
#tech / dev
#resource / go
[!info] related notes
- 所属 MOC: Go 服务工程
- 前置概念: Go 日志
- 并列概念: Go 认证与 JWT, Go 缓存策略
Go 配置管理
一句话定义
Go 配置管理是将应用运行参数(端口、数据库连接、特性开关等)从代码中分离,通过环境变量、配置文件、远程存储等多层来源统一加载和管理的工程实践。
核心机制 / 工作原理
12-Factor App 原则:配置严格与代码分离,通过环境变量注入。不同环境(dev/staging/prod)的差异仅在配置,不在代码。
环境变量(基础方案):
port := os.Getenv("APP_PORT")
if port == "" {
port = "8080" // 合理默认值
}
dbURL := os.Getenv("DATABASE_URL")
简单直接,但缺乏类型转换、校验和层级结构。
Config 结构体模式:
type Config struct {
Server ServerConfig `mapstructure:"server"`
Database DatabaseConfig `mapstructure:"database"`
Redis RedisConfig `mapstructure:"redis"`
}
type ServerConfig struct {
Port int `mapstructure:"port"`
ReadTimeout time.Duration `mapstructure:"read_timeout"`
WriteTimeout time.Duration `mapstructure:"write_timeout"`
}
// 加载后直接使用 cfg.Server.Port,类型安全
Viper 库(多源配置):
v := viper.New()
v.SetConfigName("config") // 配置文件名(不含扩展名)
v.SetConfigType("yaml") // 文件格式
v.AddConfigPath("./configs") // 搜索路径
v.AddConfigPath("/etc/myapp") // 多个搜索路径
v.AutomaticEnv() // 自动绑定环境变量
v.SetEnvPrefix("APP") // 环境变量前缀:APP_SERVER_PORT
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
// 读取配置文件
if err := v.ReadInConfig(); err != nil {
log.Printf("using defaults: %v", err)
}
// 解析到结构体
var cfg Config
if err := v.Unmarshal(&cfg); err != nil {
log.Fatal(err)
}
配置优先级(从低到高):默认值 < 配置文件 < 环境变量 < 命令行参数 < 远程配置中心。
热重载:
v.OnConfigChange(func(e fsnotify.Event) {
var newCfg Config
v.Unmarshal(&newCfg)
// 通知各组件更新配置
cfgChan <- newCfg
})
v.WatchConfig()
最小例子 / 最小场景
// configs/config.yaml
// server:
// port: 8080
// read_timeout: 5s
// database:
// host: localhost
// port: 5432
// name: myapp
package main
import (
"fmt"
"github.com/spf13/viper"
)
type Config struct {
Server struct {
Port int `mapstructure:"port"`
} `mapstructure:"server"`
}
func LoadConfig() (*Config, error) {
v := viper.New()
v.SetConfigFile("configs/config.yaml")
v.AutomaticEnv()
v.SetDefault("server.port", 8080)
if err := v.ReadInConfig(); err != nil {
return nil, err
}
var cfg Config
return &cfg, v.Unmarshal(&cfg)
}
为什么重要
- 环境一致性:同一份镜像/二进制通过不同配置部署到不同环境,消除”在我机器上能跑”问题。
- 安全性:密钥和敏感配置通过环境变量注入,不提交到代码仓库。
- 运维灵活性:特性开关、限流阈值等参数可在不重新部署的情况下调整。
- 可测试性:配置可注入意味着测试时可使用内存数据库、Mock 服务等替代。
边界与易混淆点
- Viper 不是唯一选择:小项目直接
os.Getenv+ Config 结构体足够。Viper 的复杂度只有在多格式、远程配置、热重载等场景才值得。 mapstructuretag:Viper 使用mapstructure而非jsontag 做结构体映射,两者不要混用。- 环境变量命名:推荐
APP_前缀 + 下划线分隔(APP_DATABASE_HOST),避免与系统变量冲突。 - 密钥管理:配置文件中的敏感值应使用占位符,实际值通过 Vault、AWS Secrets Manager 等注入。不要把生产密钥写进
config.yaml。 - 配置校验:加载后应立即校验(必填字段、端口范围、URL 格式),而非等到运行时才报错。可用
go-playground/validator做结构体校验。 - 时间解析:Viper 的
GetDuration()对 YAML 中的"5s"字符串能正确解析,但mapstructure解析time.Duration需要配合v.SetTypeByDefaultValue(true)或自定义解析 Hook。