环境与配置管理
"在我本地是好的"是工程史上最经典的借口。它之所以反复出现,根源是环境不一致:开发用一套依赖、测试用一套、生产又是另一套,配置散落各处,密钥混进代码仓库。环境与配置管理的目标就一句话——让同一段代码在不同环境里行为可预期、可复现,且密钥绝不外泄。这件事不做扎实,所有的 CI、测试、回滚都建立在流沙上。
多环境:dev / staging / prod
不同环境承担不同职责,不要混用:
| 环境 | 作用 | 数据 | 稳定性要求 |
|---|---|---|---|
| dev | 本地开发、调试 | mock 或脱敏样本 | 不要求,常崩无妨 |
| staging | 上线前验证、集成测试 | 类生产的脱敏数据 | 尽量稳定,接近生产 |
| prod | 真实用户 | 真实数据 | 最高,变更受控 |
关键原则:staging 要尽可能像 prod——同样的镜像、同样的配置结构、同样的依赖版本,只是值不同。staging 和 prod 差异越大,staging 的验证价值越低,上线就是在赌。
常见反模式:staging 连的是测试库的旧数据、跑的是不同的依赖版本、甚至没有和 prod 一样的中间件。这种 staging 过不了的东西,上线照样崩。
配置与代码分离
配置(数据库地址、第三方 key、功能开关、阈值)不应该写死在代码里。Twelve-Factor App 的核心一条:配置通过环境变量注入,代码保持环境无关。
text
错误:
const dbUrl = "mysql://prod-user:pwd@10.0.0.5/orders"
正确:
const dbUrl = process.env.DB_URL这样同一个镜像可以跑在所有环境,只是注入的 .env 不同。发布物和环境解耦,是可重复部署的基础。
| 配置类型 | 存放位置 |
|---|---|
| 业务参数、阈值、开关 | 配置中心 / 环境变量 |
| 数据库、缓存地址 | 环境变量 |
| API 密钥、证书 | 密钥管理服务(见下) |
| 框架默认值、不环境相关的常量 | 代码里没关系 |
密钥管理:绝不能进代码仓库
把密钥提交到 git 是工程上的大忌,而且一旦提交,"删除"也无济于事——历史里永远在。正确做法:
text
密钥的生命周期:
生成 → 存进密钥服务(KMS / Vault / 云厂商 Secret Manager)
→ 运行时按需拉取或注入环境变量
→ 轮换(定期更换)
→ 吊销(泄露时立即失效)务实的底线:
- 仓库里加
.env.example只放占位符,真值永不提交。 - CI/CD 从密钥服务注入,不在脚本里硬编码。
- 定期轮换密钥,泄露时能快速吊销而不是全员改密码。
Feature Flag:把发布和解耦分开
传统发布是"上线即生效",风险集中在一刻。Feature Flag(功能开关)把"代码部署"和"功能开启"拆开:
text
代码合并 → 部署到生产(功能默认关闭)
→ 小比例打开开关灰度
→ 观察,有问题一键关闭,无需回滚代码
→ 全量放开| 场景 | Flag 价值 |
|---|---|
| 高风险新功能 | 灰度发布,出问题秒级关闭 |
| A/B 测试 | 不同用户走不同逻辑 |
| 紧急止血 | 不重新部署就能关掉出问题的功能 |
| 未完成功能 | 主干开发,功能未完成也能合并 |
Flag 不是越多越好。每个 flag 都是技术债——长期不清理的 flag 让代码充满死分支。上线稳定后要及时清理过期 flag。
环境一致性:容器化
保证环境一致最有效的手段是容器化(Docker)。镜像把操作系统、依赖、运行时打包在一起,"在我机器上能跑"变成"在哪里都能跑"。
text
开发:docker-compose up 起一套本地依赖
CI:用同一个 Dockerfile 构建镜像跑测试
生产:部署同一个镜像要点:dev / staging / prod 用同一个镜像,差异只在配置。不要每个环境自己拼一套 Dockerfile,那样又回到不一致的老路。
常见错误:配置漂移
环境管理最隐蔽的问题是"配置漂移"——时间久了,各环境手工改过一些东西,没人记得改了什么,prod 成了一个谁也不敢动的黑箱。
text
症状:
- "这个服务器的某个参数当年谁手动调过,别动"
- 重建一台机器,跑起来行为不一样
- 灾难恢复时发现没人能从零搭出当前的生产环境正确做法:所有环境变更必须走代码(基础设施即代码,IaC)。手动 SSH 上去改的东西不进版本控制,就是漂移的源头。无论是 Nginx 配置、防火墙规则还是环境变量,都要能从一份声明文件 + 一条命令完整重建。
务实的检查清单
- dev / staging / prod 是否用同一个镜像或构建产物,只差配置?
- 所有配置是否都从环境变量或配置中心读取,没有硬编码?
- 密钥是否从未进过 git 仓库,是否定期轮换?
- 是否用 Feature Flag 把高风险发布改成灰度?
- 环境能否从一份 IaC 声明完整重建,还是依赖某人的记忆?
- 是否禁止手工 SSH 改配置,所有变更走代码评审?