Elixir 配置:config.exs 与环境变量
Elixir 应用的配置管理是构建健壮软件的基础,区分静态配置与敏感数据(如数据库密码、API 密钥)至关重要。正确处理环境变量不仅能保证安全性,还能让同一套代码在不同环境(开发、测试、生产)中灵活运行。
理解配置加载的生命周期
Elixir 应用在启动时会加载配置,理解其加载顺序是正确使用环境变量的前提。配置主要分为两个阶段:编译时和运行时。
大多数初学者容易犯的错误是将所有配置都写在 config.exs 或 dev.exs 中。这导致配置在编译时就被“固化”到了 BEAM 虚拟机字节码中,无法在运行时动态读取服务器上的环境变量。
核心原则:静态配置(如日志级别、服务端口)放在 config.exs;动态或敏感配置(如数据库密码、外部 API 地址)必须放在 config/runtime.exs 中。
第一步:准备本地环境变量
在代码读取环境变量之前,你需要先在系统中定义它们。在本地开发时,通常不希望在 shell 中手动 export 每一个变量。
- 创建 一个名为
.env的文件,放在项目根目录下(与mix.exs同级)。 - 编辑
.env文件,添加 你的配置项,每行一个,格式为KEY=VALUE。
例如:
DATABASE_URL=postgres://user:pass@localhost/my_app_dev
SECRET_KEY_BASE=super_secret_random_string
API_ENDPOINT=https://api.example.com
- 打开
.gitignore文件,确保.env被添加到忽略列表中,防止敏感信息被提交到代码仓库。
# .gitignore
...
.env
- 安装
dotenvy库。虽然 Elixir 标准库不自带.env解析,但这是社区标准做法。打开mix.exs,在defp deps do中添加 依赖:
defp deps do
[
{:dotenvy, "~> 0.8.0"}
]
end
- 运行
mix deps.get安装 依赖。
第二步:配置 runtime.exs 读取环境变量
Elixir 1.9 引入了 config/runtime.exs,专门用于在系统启动时评估配置。这是读取环境变量的最佳位置。
- 找到 项目根目录下的
config文件夹。 - 打开
config/runtime.exs文件。 - 修改 文件顶部,加载
.env文件(仅在开发环境):
import Config
# 仅在 :dev 环境加载 .env 文件
if config_env() == :dev do
Dotenvy.source!([".env", System.get_env()])
end
- 配置 具体的应用模块,例如数据库。使用
System.get_env/1或Dotenvy提供的强类型解析函数。推荐使用Dotenvy,因为它能处理类型转换和默认值。
例如,配置 Ecto 数据库 Repo:
# config/runtime.exs
# 定义一个解析环境变量的辅助函数(可选,或直接使用 Dotenvy)
env = fn
key, default -> System.get_env(key) || default
key -> System.get_env(key) || raise "Environment variable #{key} is missing!"
end
# 配置数据库
if config_env() != :test do
database_url =
env.("DATABASE_URL") ||
raise """
environment variable DATABASE_URL is missing.
For example: postgres://USER:PASS@HOST/DATABASE
"""
config :my_app, MyApp.Repo,
url: database_url,
pool_size: String.to_integer(env.("POOL_SIZE", "10"))
end
注意:Dotenvy 提供了更优雅的写法,例如 env!("DATABASE_URL", :string!),可以强制要求变量必须存在并转换类型。
第三步:在代码中使用配置
配置好 runtime.exs 后,在业务代码中获取这些值非常简单。
- 打开 任何需要使用配置的模块文件。
- 使用
Application.fetch_env!/2读取 值。
defmodule MyApp.Accounts do
alias MyApp.Repo
def get_api_endpoint do
# 读取 :my_app 应用下的 :api_endpoint 配置
Application.fetch_env!(:my_app, :api_endpoint)
end
end
- 确保 在
config/runtime.exs中设置了对应的值:
# config/runtime.exs
config :my_app, :api_endpoint, System.get_env("API_ENDPOINT")
第四步:生产环境部署设置
在生产环境(如 Docker 容器或云服务器),我们通常不使用 .env 文件,而是直接注入操作系统级别的环境变量。
-
设置 操作系统环境变量。
- Docker 环境:在
docker-compose.yml或运行命令中通过-e参数传入,或者在容器编排工具(如 Kubernetes)的 ConfigMap/Secret 中定义。 - Linux 服务器:编辑
/etc/environment或在用户的.bashrc中export变量。
- Docker 环境:在
-
验证
config/runtime.exs中的逻辑。由于代码中判断了if config_env() == :dev才加载.env,生产环境(MIX_ENV=prod)会自动跳过文件读取,直接读取System.get_env,符合预期。
常见问题排查
如果遇到配置未生效的情况,按照以下顺序排查。
| 症状 | 可能原因 | 解决方案 |
|---|---|---|
| 启动报错 "Environment variable ... is missing" | 变量未设置或拼写错误 | 检查 系统环境变量是否存在,核对 变量名的大小写。 |
| 修改了配置但应用行为不变 | 修改了 config.exs 而非 runtime.exs,或者已编译的代码未包含最新配置 |
确认 动态配置写在 runtime.exs 中;如果必须改 config.exs,需要重新编译 (mix clean && mix compile)。 |
| 生产环境读取到了本地测试值 | .env 文件被意外提交并在生产环境被读取 |
检查 config/runtime.exs 中的加载逻辑,确保 生产环境不加载 .env 文件。 |
调试配置的最佳实践:在 iex -S mix 会话中,使用 Application.get_all_env(:my_app) 查看 当前应用所有已加载的配置,验证环境变量是否正确注入。
iex -S mix
iex> Application.get_all_env(:my_app)
[
api_endpoint: "https://api.example.com",
ecto_repos: [MyApp.Repo],
...
]
暂无评论,快来抢沙发吧!