最直接读取环境变量的方式是os.Getenv,需校验空值并设默认值;应统一用GO_ENV区分环境,避免硬编码和多配置文件,推荐动态初始化Config结构体,慎用viper默认行为。

用 os.Getenv 读取环境变量是最直接的方式
Go 本身不内置环境配置分层机制,所以区分开发与生产环境的核心是依赖外部传入的环境变量,最常用的是 GO_ENV 或 ENV。项目启动时通过 os.Getenv("GO_ENV") 获取值,再决定加载哪套配置。
常见错误是硬编码判断逻辑(比如写死 if env == "dev" 却没做空值检查),导致本地没设环境变量时程序 panic 或静默走错分支。
- 务必在读取后做非空校验:
env := os.Getenv("GO_ENV"); if env == "" { env = "development" } - 推荐统一约定变量名,如
GO_ENV(避免和GOPATH等 Go 自身变量冲突) - 测试时可通过
GO_ENV=test go test ./...快速切换
配置结构体按环境字段合并,而非拆成多个文件
不要为每个环境单独建 config_dev.go、config_prod.go,容易重复、难维护。更合理的方式是定义一个统一的 Config 结构体,在初始化时按环境填充字段。
例如数据库地址:开发用 localhost:5432,生产从 DATABASE_URL 环境变量读取;日志级别:开发设为 debug,生产设为 info —— 这些都应在同一结构体中动态赋值,而不是靠文件名区分。
立即学习“go语言免费学习笔记(深入)”;
- 避免使用
build tag分环境(如//go:build prod),会导致编译产物不一致,CI/CD 难以复现 - 敏感字段(如密钥)必须从环境变量读,绝不写进代码或 JSON/YAML 配置文件
- 可封装一个
NewConfig()函数,内部根据GO_ENV调用不同初始化逻辑
viper 支持多环境配置但默认行为易踩坑
很多人选 viper 是因为它支持自动加载 config.{env}.yaml,但默认不会主动读取 GO_ENV,也不会 fallback。若没显式调用 viper.SetEnvKeyReplacer 和 viper.AutomaticEnv(),环境变量根本不会生效。
典型错误是只写 viper.SetConfigName("config"),然后期待它自动加载 config.production.yaml —— 实际上它只找 config.yaml,除非你手动 viper.SetConfigName("config." + env)。
- 正确做法:先读
GO_ENV,再拼接配置名,再viper.ReadInConfig() - 必须调用
viper.SetEnvPrefix("APP")并配合viper.AutomaticEnv()才能让APP_PORT=8080覆盖配置文件中的port -
viper的Unmarshal对嵌套结构体支持较弱,复杂配置建议手写解析逻辑
package main
import (
"log"
"os"
"github.com/spf13/viper"
)
func loadConfig() {
env := os.Getenv("GO_ENV")
if env == "" {
env = "development"
}
viper.SetConfigName("config." + env)
viper.AddConfigPath(".")
viper.SetEnvPrefix("APP")
viper.AutomaticEnv()
if err := viper.ReadInConfig(); err != nil {
log.Fatal("failed to read config:", err)
}
}
部署时确保环境变量真正注入到进程上下文
本地 GO_ENV=production go run main.go 没问题,但 Docker 或 Kubernetes 中常出问题:环境变量设在容器启动命令里,却没透传给 Go 进程;或者用了 docker-compose.yml 的 environment 字段,但忘了在 deploy 下加 labels 或健康检查干扰了变量加载顺序。
最容易被忽略的是 systemd 服务:如果用 systemctl start myapp,默认继承的是 root 用户 shell 的环境,不是你 export GO_ENV=production 那个终端的环境。
- Dockerfile 中避免
ENV GO_ENV=production—— 这会让镜像失去环境灵活性,改用运行时docker run -e GO_ENV=production - Kubernetes 中优先用
envFrom: configMapRef或secretRef,比硬写env:更安全 - 上线前加一行日志:
log.Printf("running in %s mode", os.Getenv("GO_ENV")),确认变量真被读到了
main() 之后才设置的 os.Setenv 都不会影响已初始化的配置,这点在热重载或测试 mock 场景下特别容易误判。










