「启动的时候怎么区分环境,让 dev、test、prod 用不同的配置?」—— 这篇讲清 go-zero 的多环境机制:多份配置文件 + -f 启动参数(官方首选)、环境变量 ${ENV} 占位、以及 config 里的 Mode 字段,并给出推荐落地组合与启动命令。
先把最关键的说在前面。
go-zero 没有 Spring 那种「profile」开关,它的多环境是「配置文件 + 启动参数」模式:为每个环境准备一份 etc/xxx-*.yaml,启动时用 -f 指定用哪份。再叠加两点增强:① 用 ${ENV} 占位符让敏感/部署相关的值走环境变量;② 在 config 里写 Mode: dev/test/pro 控制日志格式等框架行为。
同一份代码,不同环境「变」的是配置,不变的是逻辑。
-f 启动参数go-zero 原生支持,最干净、最常用。
-f 接收配置路径package main import ( "flag" "github.com/zeromicro/go-zero/core/conf" "github.com/zeromicro/go-zero/rest" "your_project/internal/config" "your_project/internal/handler" "your_project/internal/svc" ) // -f 指定配置文件,默认值指向 etc 目录下的 yaml var configFile = flag.String("f", "etc/user-api.yaml", "the config file") func main() { flag.Parse() var c config.Config // MustLoad 读 -f 指向的文件;多环境只换文件、不换代码 conf.MustLoad(*configFile, &c, conf.UseEnv()) server := rest.MustNewServer(c.RestConf) defer server.Stop() ctx := svc.NewServiceContext(c) handler.RegisterHandlers(server, ctx) server.Start() }
main.go 里天生就带这个 -f flag,你不用自己写——你只需要把多份 yaml 准备好、启动时候传对路径即可。# user-api.dev.yaml Name: user-api Mode: dev Host: 0.0.0.0 Port: 8888 Mysql: DataSource: root:123456@tcp(127.0.0.1:3306)/user_dev CacheRedis: - Host: 127.0.0.1:6379 Trace: Sampler: 1.0 # dev 全采样,方便排查 # user-api.prod.yaml Name: user-api Mode: pro Host: 0.0.0.0 Port: 8888 Mysql: DataSource: ${MYSQL_DSN} # 密钥/地址走环境变量,不进仓库 CacheRedis: - Host: ${REDIS_HOST} Trace: Sampler: 0.1 # prod 低采样,控成本
${ENV} 占位让「同一份模板」在部署时按环境变量填值。
conf.UseEnv()go-zero 的 conf 包支持在 yaml 里写 ${VAR},启动时替换为进程环境变量——但前提是 MustLoad 带上 conf.UseEnv() 选项(上面 main.go 已带上)。
# 一份 prod 模板 etc/user-api.prod.yaml Mysql: DataSource: ${MYSQL_DSN} CacheRedis: - Host: ${REDIS_HOST} Auth: AccessSecret: ${JWT_SECRET} # 私钥绝不进仓库
# 启动时注入环境变量即可(shell / Docker / K8s Secret) MYSQL_DSN='prod_user:xxxx@tcp(db.prod:3306)/user' \ REDIS_HOST=redis.prod:6379 \ JWT_SECRET='***' \ ./user-api -f etc/user-api.prod.yaml
${ENV} 覆盖(方式二)。生产环境最佳实践是「prod 模板进仓库 + 真实值全走 env」。${VAR} 取不到且没设默认值时,go-zero 直接加载失败(fail-fast),这反而是好事——不会带着空密码悄悄连上库。可写 ${VAR:-default} 给兜底默认值(取决于 go-zero 版本是否支持该语法,谨慎使用)。Mode 字段go-zero 内置的「行为开关」,不是配置切换,是运行时行为切换。
rest.RestConf(内嵌 service.ServiceConf)有个 Mode 字符串字段。go-zero 用它决定日志格式 / 运行时行为:
"dev" / "test" / "pro",没有 "prod"。你要是写成 Mode: prod,框架不认、按默认(非 pro)行为跑,日志格式和 prod 预期不一致。统一用 pro。Log 字段更精细Log: Mode: file # console / file / volatile Encoding: json # flat(dev 友好) / json(pro 友好,便于采集) Path: /var/log/user-api Level: info
Mode 与 Log.Encoding 配合:dev 用 flat+控制台、pro 用 json+文件,让日志既好读又好被 Loki/ES 采集。三种方式不是三选一,而是叠加。
-f etc/xxx.dev.yaml 决定用哪套连接/开关${MYSQL_DSN} 等用部署环境变量覆盖Mode: dev/test/pro 控制日志格式与采样本地、Docker、K8s 三处怎么传 -f。
# 开发 go run user.go -f etc/user-api.dev.yaml # 测试 ./user-api -f etc/user-api.test.yaml # 生产 ./user-api -f etc/user-api.prod.yaml
# Dockerfile ENTRYPOINT ["/app/user-api", "-f", "/app/etc/user-api.yaml"] # 不同环境用不同的配置文件挂载(ConfigMap / 构建参数),或传环境变量覆盖 ${ENV}
# deployment.yaml —— 用 env 注入密钥,配置文件由 ConfigMap 挂到 /app/etc env: - name: MYSQL_DSN valueFrom: { secretKeyRef: { name: user-secret, key: dsn } } - name: JWT_SECRET valueFrom: { secretKeyRef: { name: user-secret, key: jwt } } # 容器启动参数 -f /app/etc/user-api.prod.yaml(prod 模板已含 ${MYSQL_DSN} 等)
/app/etc + 敏感值用 Secret 转 env」。这样镜像一次构建,dev/test/prod 只换挂载与 env,符合 12-factor。能不读就别读;真要区分,集中在 svc 里。
Mode// config.go —— 把 Mode 透传到业务可访问的地方 type Config struct { rest.RestConf // 内嵌了 Mode 字段 Auth struct { AccessSecret string AccessExpire int64 } } // svc 中需要时用(尽量克制) if c.Mode == "pro" { // 生产才开的开关,如关闭调试接口 }
if c.Mode == "dev" 等于把环境差异又写回代码,违背「配置外置」初衷。能用配置项表达的差异(库地址、采样率、日志级别)一律放 yaml,Mode 只做框架行为控制,业务里尽量别依赖它。呼应「go-zero 全局变量」那篇:环境信息只在启动时读一次、通过 config → svc 注入,别在包里写 var Env = "prod" 全局变量。需要环境相关行为时,在 svc 装配时决定并传依赖进去(比如 dev 注入 mock 客户端、pro 注入真实客户端)。
照着做,多环境不翻车。
| 该做 / 别做 | 说明 |
|---|---|
| ✅ 配置外置、代码不写死环境 | 差异全进 yaml,启动用 -f 选;逻辑里零 env 判断 |
✅ prod 密钥走 ${ENV} | 账号/私钥用环境变量注入,绝不进 git 仓库 |
| ✅ 一份构建产物多环境复用 | 镜像 build 一次,dev/test/prod 只换挂载与 env |
⚠️ Mode 写 pro 不写 prod | go-zero 只认 dev/test/pro,写错框架不按 prod 行为跑 |
| ❌ 别把 prod 配置文件明文提交 | prod 模板可提交,但真实值用 env;或用加密配置管理 |
❌ 别在 logic 里散落 if Mode | 环境差异回代码 = 反面模式,集中在 svc 装配决定 |
| ⚠️ 测试环境别开全量采样 | Sampler: 1.0 仅 dev;test/prod 按量调低,控成本 |
| ⚠️ 别用全局 var 存环境 | 环境信息随 config 进 svc,不要包级全局变量 |
目标一致(配置随环境变),机制不同。
| 方案 | 环境切换方式 | 特点 |
|---|---|---|
| go-zero(本文) | 多份 yaml + -f 参数 + ${ENV} + Mode | 无 profile 概念,纯文件 + 参数,最贴近 12-factor |
| Spring Boot | spring.profiles.active=dev + application-{profile}.yml | 内置 profile 机制,自动按名加载对应文件 |
| Python (pydantic-settings) | ENV_FILE=.env.prod / ENV=prod + settings 按 env 读 | 环境变量优先,12-factor 原生 |
| Go viper | viper.SetConfigName("dev") / SetEnvPrefix | 比 goctl 灵活,但要自己接;go-zero 不强制用 |
| 写死全局 env 变量 | var Env="prod" compile 期定 | 反面模式:换环境要重新编译,不可取 |