2.5 环境变量与配置管理


2.5 环境变量与配置管理

本节摘要:配置管理的目标是"构建一次、随处运行"——同一个镜像,在开发、测试、生产三个环境跑出不同行为,靠的是环境变量与配置文件的外置。本节先说明硬编码的三种危害,再辨析 environment、env_file、.env、宿主机环境变量四种注入方式的分工,给出变量替换与配置优先级规则,最后落到敏感信息的处理原则与配置文件的管理手法。

本节导航

  1. 能说清硬编码配置进镜像的三个危害,并解释外置配置的收益。
  2. 能区分 environment、env_file、.env 三者各自的作用对象。
  3. 能使用 ${VAR:-默认值} 语法为变量提供默认值,并按优先级解释取值结果。
  4. 能按原则处理密码、密钥等敏感信息,避免进镜像进版本库。
  5. 能判断配置文件该打进镜像还是挂载进来。

一、为什么不能硬编码

把配置写死在镜像里,等于把环境绑死在构建时。三个代价我们挨个踩过:

不可移植。 镜像里写死 localhost 的数据库地址,换台机器、换个环境就得改 Dockerfile 重新构建。镜像本该是"同一份代码的固化产物",硬编码让它变成了"某个环境的专属产物"。

安全风险。 密码、API 密钥写进镜像,就会随镜像分发到每个拉取方手里。镜像仓库权限再严,也多一层暴露面;更别说镜像层是历史不可变的,改掉密码重新构建,旧镜像里还留着旧密码。

难以维护。 改一个端口号就要重走构建、推送、拉取全流程,改配置的代价被放大成改代码的代价。

镜像层不可变还有一层含义:配置改错了想回滚,可以回滚到旧镜像,但镜像一旦推送,里面的配置就永远留在历史层里,除非重建整个镜像链。所以敏感配置从源头就不该进构建上下文。

环境变量把配置从镜像里挪到运行时:镜像不变,起容器时注入不同值,行为随之改变。这带来的直接收益是环境隔离、敏感信息可控、修改配置不再触发重建。

二、四种注入方式的分工

Compose 里容易混的是四个东西,先分清它们作用在哪个环节:

方式 作用对象 值从哪里来 典型用途
environment 容器内进程 compose 文件内直接写 非敏感常量、调试开关
env_file 容器内进程 指定的文件 一批变量统一注入
.env 文件 compose 文件本身 项目目录下 .env 给 compose 插值用
宿主机环境变量 compose 文件本身 当前 shell 导出 临时覆盖、CI 注入

最容易混淆的是 env_file 与 .env:env_file 把变量送进容器,服务级配置用;.env 是 compose 文件自己的变量来源,供 ${VAR} 插值用,不自动进容器。举一个实际组合:

version: "3.9" services: web: image: nginx:1.21 ports: - "80:80" environment: - NGINX_PORT=80 - DEBUG=true env_file: - .env

这里的 environment 直接写非敏感配置;env_file 从 .env 读一批变量注入容器。项目目录下的 .env 同时也在为 compose 文件提供插值变量,两件事各司其职。

environment 有两种写法,列表形式与字典形式效果等价,字典形式更紧凑。env_file 支持列出多个文件,后列的文件覆盖先列的,同名字段的覆盖关系按顺序来,多环境叠加时按需排列文件顺序。

三、变量替换与配置优先级

compose 文件里的值可以写成变量引用,启动时替换:

services: web: image: nginx:1.21 ports: - "${NGINX_PORT}:80"

运行时先看宿主机有没有导出 NGINX_PORT,再看项目目录 .env 有没有定义,都没有就报错。给变量一个兜底值更稳:

environment: - NGINX_PORT=${NGINX_PORT:-80}

:-80 的含义是"变量未设置时用 80"。设置了就用设置值,没设置也不报错。这条语法是 compose 文件可移植性的地基:默认值写进文件,覆盖值由环境决定。插值发生在 compose 解析阶段,docker-compose config 渲染出来的值就是容器实际拿到的值,想确认某个变量最终取值,跑这条命令最准。

配置优先级金字塔

一个变量可能同时在多个层级被赋值,取值顺序从金字塔尖往下:上层优先。

配置优先级金字塔

完整取值规则一句话:命令行与文件内显式值最高,其次是宿主机环境变量与 .env 文件,最后是 ${VAR:-默认值} 里的默认值。写配置时把"该用哪一层"想清楚,三层各管各的,就基本不会出幺蛾子。

这条规则落在实践上就是:基础文件写默认值,环境文件写覆盖值,紧急情况下用宿主机变量临时顶替。我们踩过的坑是"到处都能改"导致的定位困难——同一个变量三个文件都写了,改错一个还以为没生效。给变量固定"家":每个变量只在某一层定义,其余层通过引用复用。

四、敏感信息的处理原则

密码、密钥、令牌,全部按下面四条原则处理。

不进镜像。 Dockerfile 里的 ENV 写死密码是最危险的习惯,镜像一到别人手里密码就泄了。

不进 compose 主文件。 docker-compose.yml 要进版本库给团队共享,密码写进去等于公开。密码只放 .env,并把 .env 加进 .gitignore,版本库里只留一个 .env.example 模板。补充一条:.env 即使进了 .gitignore,历史提交里可能还留着旧版本,git 历史清理是专项活;稳妥起见,密钥类信息从一开始就用 Secrets 或外部注入,别让它们出现在任何会被版本控制的文件里。

不打印。 应用日志别输出完整连接串,错误信息里截断密码,否则 .env 保护得再好也白搭。

最小权限与轮换。 应用只用自己能访问的库,账号别给超级用户;密钥定期轮换,泄露了也能及时止损;容器内尽量别用 root 跑业务进程,镜像构建阶段创建普通用户,运行时指定用户身份。权限最小化不只是安全口号,它让"某个容器被攻破"的损失被限制在单容器内。

集群环境用 Secrets。 Swarm 模式可以走 Docker Secrets:echo "mysecretpassword" | docker secret create db_password - 创建密钥,compose 里声明 secrets: - db_password 引用,容器内以文件形式挂到 /run/secrets 目录,应用读文件取值。密钥加密存储、按服务授权,比环境变量更进一步。

version: "3.9" services: db: image: postgres:14 secrets: - db_password secrets: db_password: external: true

五、配置文件的管理

环境变量之外,应用还要读配置文件。两种放法,按"改不改"决定。

静态配置打进镜像。 默认配置、示例配置随镜像分发,用 Dockerfile 的 COPY 指令复制:COPY nginx.conf /etc/nginx/nginx.conf。适合基本不变的配置。

动态配置挂载进来。 运行时要改的配置用卷挂载:

version: "3.9" services: web: image: nginx:1.21 ports: - "80:80" volumes: - ./nginx.conf:/etc/nginx/nginx.conf

宿主机改文件、容器内重启进程即生效,不用重建镜像。配置文件同样要进版本库(这是配置,不是机密),用覆盖文件或 profile 区分环境。模板引擎也可以参与:Jinja2 之类工具按环境渲染配置文件,再挂载进容器,适合配置项特别多的应用。

六、环境区分的实操建议

多环境落地,我们习惯三件套:前缀——变量名带环境前缀,DEV_、TEST_、PROD_,一眼看出归属;独立文件——每个环境一份 env 文件,compose 按 profile 选;默认值兜底——凡是环境相关的变量都写 ${VAR:-默认},缺配置时应用也能以安全默认值启动,而不是直接崩。变量命名也建议统一风格:全大写加下划线,与 shell 习惯一致,避免大小写混用造成的心智负担;变量注释写在 .env.example 里,别写进 compose 主文件,保持主文件干净。

更复杂的组织,配置项成百上千时,可以引入集中配置管理工具,比如 Vault 做加密存储与审计、Consul 做配置分发。那是团队级基建,单机部署阶段用 .env 加前缀已经足够。

应用启动时校验配置是个低成本高回报的习惯:环境变量缺失、端口非法、连接串格式错误,在启动阶段就快速失败,而不是运行几小时后才暴露。模板引擎渲染配置的场景,渲染后先校验再挂载,坏配置不上线。

⚠️ .env 与 env_file 指向同一个文件时,变量会以"注入容器"的方式生效,但 .env 里若有大段注释或复杂转义,容器解析可能报错。分工明确:容器注入用 env_file,compose 插值用 .env。

💡 给 compose 文件配一个 .env.example:把变量名、注释、示例值都放进去,提交到版本库;真正的 .env 不入库。新同事克隆项目后复制一份改改就能跑,这是配置管理的低成本高回报动作。

七、变量替换的进阶写法

基础的 ${VAR:-默认值} 之外还有几个变形:${VAR-默认值} 不带冒号,只在变量未设置时用默认值,变量为空串则保留空串,适合"空值也算配置"的场景;${VAR:?自定义错误} 在变量缺失时直接报错退出,适合必填项——数据库密码没配就别启动;想在配置里写字面量美元符,用 $$ 转义,Compose 不会把它当变量开头。变量替换不只用于 environment,ports、volumes、image 里的值都能写成 ${VAR},端口号、卷名、镜像标签全部外置,配合 .env 实现一份配置多环境复用。

应用侧读取环境变量同样讲究:代码里 os.environ.get("NGINX_PORT", "80") 给个默认值,镜像不带任何变量也能以合理默认启动;直接索引的写法在变量缺失时会抛异常。两头都给默认值,容错面最大。

八、配置生效与校验

改完环境变量,容器不会自动收到新值——环境变量在容器创建时注入,改了配置必须让 Compose 重建容器,up -d 会检测配置变化并重建受影响的服务。验证配置对不对,用 docker-compose config 渲染最终结果:插值全部算完、覆盖文件合并完毕,一眼看出每个变量的实际取值。我们每次改完 compose 都先跑这条命令,语法错误与变量缺失当场暴露。

日志脱敏单独提一句:应用报错别打完整连接串,密码部分截断,否则 .env 保护得再好,日志文件也会泄密。容器内验证也值得养习惯:docker exec 进容器执行 env 命令,与 config 渲染结果对照,两层验证基本不会漏。

一节小结

  • 硬编码三宗罪:不可移植、泄密风险、改配置要重建镜像。
  • 四种注入分清楚:environment 与 env_file 进容器,.env 与宿主机变量供 compose 插值。
  • 默认值语法防崩:${VAR:-80} 让缺配置时按默认运行,而不是启动失败。
  • 优先级金字塔:显式值高于环境变量,环境变量高于默认值。
  • 敏感信息四不:不进镜像、不进主文件、不打印、集群用 Secrets。
  • 配置文件按改不改分:静态配置 COPY 进镜像,动态配置挂载进来。
  • 多环境三件套:前缀、独立文件、默认值兜底。

配置就位之后,服务能起能连了,但"起了"不等于"能干活"——2.6 节用健康检查与条件依赖解决就绪问题。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U