环境变量与配置管理
同一份代码需要在开发、测试、生产等不同环境运行。配置管理让我们能够灵活切换环境,而不需要修改代码。
1. 环境变量基础
1.1 读取环境变量
1import (
2 "fmt"
3 "os"
4)
5
6func main() {
7 // 读取环境变量
8 dbHost := os.Getenv("DB_HOST")
9 if dbHost == "" {
10 dbHost = "localhost" // 默认值
11 }
12
13 fmt.Println("DB Host:", dbHost)
14
15 // 检查环境变量是否存在
16 port, exists := os.LookupEnv("PORT")
17 if !exists {
18 port = "8080"
19 }
20}
1.2 设置环境变量
1// 在程序中设置(仅影响当前进程)
2os.Setenv("API_KEY", "secret123")
3
4// 在 shell 中设置
5// export DB_HOST=localhost
6// export DB_PORT=3306
2. godotenv:.env 文件
2.1 安装
1go get -u github.com/joho/godotenv
2.2 使用
创建 .env 文件:
1DB_HOST=localhost
2DB_PORT=3306
3DB_USER=root
4DB_PASSWORD=secret
5API_KEY=your-api-key
6DEBUG=true
加载配置:
1import (
2 "github.com/joho/godotenv"
3 "log"
4 "os"
5)
6
7func main() {
8 // 加载 .env 文件
9 err := godotenv.Load()
10 if err != nil {
11 log.Println("No .env file found")
12 }
13
14 dbHost := os.Getenv("DB_HOST")
15 dbPort := os.Getenv("DB_PORT")
16
17 fmt.Printf("Connecting to %s:%s\n", dbHost, dbPort)
18}
2.3 多环境配置
1func main() {
2 env := os.Getenv("GO_ENV")
3 if env == "" {
4 env = "development"
5 }
6
7 // 根据环境加载不同的配置文件
8 godotenv.Load(".env." + env)
9
10 // .env.development
11 // .env.production
12 // .env.test
13}
3. Viper:强大的配置库
3.1 安装
1go get -u github.com/spf13/viper
3.2 基础使用
创建 config.yaml:
1server:
2 port: 8080
3 host: localhost
4
5database:
6 host: localhost
7 port: 3306
8 user: root
9 password: secret
10 dbname: myapp
11
12redis:
13 host: localhost
14 port: 6379
读取配置:
1import (
2 "fmt"
3 "github.com/spf13/viper"
4)
5
6func main() {
7 // 设置配置文件名和路径
8 viper.SetConfigName("config")
9 viper.SetConfigType("yaml")
10 viper.AddConfigPath(".")
11 viper.AddConfigPath("./config")
12
13 // 读取配置
14 if err := viper.ReadInConfig(); err != nil {
15 panic(err)
16 }
17
18 // 获取配置值
19 port := viper.GetInt("server.port")
20 dbHost := viper.GetString("database.host")
21
22 fmt.Printf("Server port: %d\n", port)
23 fmt.Printf("DB host: %s\n", dbHost)
24}
3.3 结构体绑定
1type Config struct {
2 Server struct {
3 Port int `mapstructure:"port"`
4 Host string `mapstructure:"host"`
5 } `mapstructure:"server"`
6
7 Database struct {
8 Host string `mapstructure:"host"`
9 Port int `mapstructure:"port"`
10 User string `mapstructure:"user"`
11 Password string `mapstructure:"password"`
12 DBName string `mapstructure:"dbname"`
13 } `mapstructure:"database"`
14}
15
16func main() {
17 viper.SetConfigName("config")
18 viper.AddConfigPath(".")
19 viper.ReadInConfig()
20
21 var config Config
22 if err := viper.Unmarshal(&config); err != nil {
23 panic(err)
24 }
25
26 fmt.Printf("Server: %s:%d\n", config.Server.Host, config.Server.Port)
27}
3.4 环境变量覆盖
1func main() {
2 viper.SetConfigName("config")
3 viper.AddConfigPath(".")
4 viper.ReadInConfig()
5
6 // 自动读取环境变量
7 viper.AutomaticEnv()
8
9 // 设置环境变量前缀
10 viper.SetEnvPrefix("MYAPP")
11
12 // 现在可以通过环境变量覆盖配置
13 // export MYAPP_SERVER_PORT=9000
14
15 port := viper.GetInt("server.port") // 如果设置了环境变量,会使用环境变量的值
16}
3.5 监听配置变化
1func main() {
2 viper.SetConfigName("config")
3 viper.AddConfigPath(".")
4 viper.ReadInConfig()
5
6 // 监听配置文件变化
7 viper.WatchConfig()
8 viper.OnConfigChange(func(e fsnotify.Event) {
9 fmt.Println("Config file changed:", e.Name)
10 // 重新加载配置
11 })
12
13 // 程序继续运行...
14}
4. 最佳实践
4.1 配置优先级
通常的优先级(从高到低):
- 命令行参数
- 环境变量
- 配置文件
- 默认值
1func main() {
2 // 1. 设置默认值
3 viper.SetDefault("server.port", 8080)
4
5 // 2. 读取配置文件
6 viper.SetConfigName("config")
7 viper.AddConfigPath(".")
8 viper.ReadInConfig()
9
10 // 3. 读取环境变量
11 viper.AutomaticEnv()
12
13 // 4. 命令行参数(使用 pflag 库)
14 // pflag.Int("port", 8080, "Server port")
15 // pflag.Parse()
16 // viper.BindPFlags(pflag.CommandLine)
17
18 port := viper.GetInt("server.port")
19}
4.2 敏感信息管理
不要将敏感信息提交到版本控制:
# .gitignore
.env
.env.local
.env.*.local
config/production.yaml
使用环境变量或密钥管理服务:
1// 生产环境从环境变量读取
2dbPassword := os.Getenv("DB_PASSWORD")
3if dbPassword == "" {
4 // 或从 AWS Secrets Manager / HashiCorp Vault 读取
5}
4.3 配置验证
1type Config struct {
2 Server struct {
3 Port int `mapstructure:"port" validate:"required,min=1,max=65535"`
4 }
5}
6
7func main() {
8 var config Config
9 viper.Unmarshal(&config)
10
11 // 使用 validator 库验证
12 validate := validator.New()
13 if err := validate.Struct(config); err != nil {
14 log.Fatal("Invalid config:", err)
15 }
16}
5. 总结
- 简单场景:使用
os.Getenv+godotenv - 复杂场景:使用 Viper
- 敏感信息:使用环境变量,不要提交到代码仓库
- 多环境:使用不同的配置文件或环境变量
思考题: 为什么敏感信息(如数据库密码)不应该写在配置文件中?有哪些更安全的方案?