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