强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

Erlang/OTP 完全指南 / 19 - 发布与部署

第 19 章:发布与部署 — relx、热代码升级、版本管理

本章学习如何将 Erlang 应用打包为可部署的 Release,以及如何实现热代码升级(Hot Code Upgrade)。


19.1 Release 概念

19.1.1 什么是 Release?

Release 是一个自包含的软件包,包含:

  • 应用代码
  • 依赖库
  • Erlang 运行时系统(ERTS)
  • 配置文件
  • 启动脚本
_release/
└── myapp/
    ├── bin/
    │   ├── myapp              ← 启动脚本
    │   └── myapp.bat          ← Windows 启动脚本
    ├── erts-14.0/             ← Erlang 运行时
    ├── lib/
    │   ├── myapp-0.1.0/       ← 应用代码
    │   ├── cowboy-2.12.0/     ← 依赖
    │   └── stdlib-5.2/        ← 标准库
    ├── releases/
    │   └── 0.1.0/
    │       ├── sys.config     ← 应用配置
    │       ├── vm.args        ← VM 参数
    │       └── myapp.rel      ← Release 规范
    └── log/

19.1.2 为什么需要 Release?

优势说明
自包含无需在目标机器安装 Erlang
版本化可管理多个版本
热升级运行时替换代码
可靠OTP 监督树在 Release 模式下自动启动

19.2 rebar3 Release

19.2.1 rebar.config 配置

%% rebar.config
{relx, [
    {release, {myapp, "0.1.0"}, [
        myapp,      %% 你的应用
        sasl,       %% SASL(系统日志)
        cowboy,     %% 依赖
        jsx
    ]},
    
    {dev_mode, true},              %% 开发模式(符号链接)
    {include_erts, false},         %% 不包含 ERTS(本地有 Erlang)
    {sys_config, "config/sys.config"},
    {vm_args, "config/vm.args"},
    
    %% 生产配置覆盖
    {extended_start_script, true}  %% 生成 start/stop/attach 脚本
]}.

{profiles, [
    {prod, [
        {relx, [
            {dev_mode, false},
            {include_erts, true}
        ]}
    ]}
]}.

19.2.2 构建命令

# 构建 Release
rebar3 release

# 使用生产配置构建
rebar3 as prod release

# 生成 tar 包(用于部署)
rebar3 as prod tar

# 生成 OTP upgrade package
rebar3 relup

# 生成 overlay(模板文件)
rebar3 release --overlay_vars config/vars.config

19.2.3 VM 参数配置

%% config/vm.args
-name myapp@127.0.0.1
-setcookie myapp_secret_cookie

## 性能调优
+P 1048576            %% 最大进程数
+Q 65536              %% 最大端口数
+A 128                %% 异步线程数

## 垃圾回收
+MBas aobf
+MBacul 0

## SMP
-smp enable

19.2.4 应用配置

%% config/sys.config
[
    {myapp, [
        {http_port, 8080},
        {db_host, "localhost"},
        {db_port, 5432},
        {pool_size, 10}
    ]},
    {sasl, [
        {sasl_error_logger, {file, "log/sasl-error.log"}},
        {errlog_type, error}
    ]}
].

19.3 运行 Release

# 前台运行(调试)
_build/prod/rel/myapp/bin/myapp foreground

# 后台运行(生产)
_build/prod/rel/myapp/bin/myapp start

# 停止
_build/prod/rel/myapp/bin/myapp stop

# 附加到运行中的节点
_build/prod/rel/myapp/bin/myapp attach

# 重启
_build/prod/rel/myapp/bin/myapp restart

# 版本升级
_build/prod/rel/myapp/bin/myapp upgrade "0.2.0"

# 控制台模式(可交互)
_build/prod/rel/myapp/bin/myapp console

19.4 热代码升级

19.4.1 原理

Erlang 的热代码升级允许在不停机的情况下替换运行中的代码:

版本 0.1.0 → 版本 0.2.0

1. 加载新版本的模块代码
2. 调用 upgrade 回调
3. 切换到新版本代码
4. 继续处理请求

19.4.2 配置热升级

%% rebar.config - 配置 relup
{relx, [
    {release, {myapp, "0.2.0"}, [myapp, sasl]},
    {relup, true}
]}.

19.4.3 code_change 回调

%% GenServer 的 code_change 回调
-module(my_server).
-behaviour(gen_server).

%% 旧版本状态 → 新版本状态
code_change({down, "0.2.0"}, NewState, _Extra) ->
    %% 降级:从新状态转为旧状态
    {ok, downgrade_state(NewState)};
code_change("0.1.0", OldState, _Extra) ->
    %% 升级:从旧状态转为新状态
    {ok, upgrade_state(OldState)};
code_change(_Vsn, State, _Extra) ->
    {ok, State}.

%% 状态转换函数
upgrade_state(#{name := Name, age := Age}) ->
    #{name => Name, age => Age, email => "default@example.com"}.

downgrade_state(#{name := Name, age := Age, email := _Email}) ->
    #{name => Name, age => Age}.

19.4.4 执行热升级

# 构建新版本
rebar3 as prod release

# 生成升级包
rebar3 relup
rebar3 as prod tar

# 在运行中的节点上执行升级
bin/myapp upgrade "0.2.0"

# 如果失败,可以降级
bin/myapp downgrade "0.1.0"

19.5 版本管理

19.5.1 语义化版本

主版本号.次版本号.修订号

例如:1.2.3

主版本号:不兼容的 API 变更
次版本号:向后兼容的功能新增
修订号:向后兼容的问题修复

19.5.2 应用版本 vs Release 版本

%% src/myapp.app.src - 应用版本
{application, myapp, [
    {vsn, "0.1.0"}  %% 应用自身版本
]}.

%% rebar.config - Release 版本
{relx, [
    {release, {myapp, "1.0.0"}, [myapp, ...]}  %% Release 整体版本
]}.

19.6 实战:完整发布流程

19.6.1 开发到生产流程

# 1. 开发
rebar3 shell

# 2. 运行测试
rebar3 eunit
rebar3 ct

# 3. 类型检查
rebar3 dialyzer

# 4. 构建 Release
rebar3 as prod release

# 5. 打包
rebar3 as prod tar

# 6. 部署到服务器
scp _build/prod/rel/myapp/myapp-0.1.0.tar.gz server:/opt/

# 7. 在服务器上解压和启动
tar xzf myapp-0.1.0.tar.gz
bin/myapp start

19.6.2 升级流程

# 1. 修改代码,更新版本号
# 2. 构建新版本
rebar3 as prod release

# 3. 生成升级包
rebar3 relup

# 4. 打包
rebar3 as prod tar

# 5. 上传到服务器
scp myapp-0.2.0.tar.gz server:/opt/

# 6. 执行热升级
bin/myapp upgrade "0.2.0"

19.7 注意事项

⚠️ 常见陷阱

  1. 热升级需要测试回退路径
  2. 状态迁移代码需要覆盖所有版本路径
  3. NIF 库无法热升级
  4. 监督树结构变更需要特殊处理
  5. 确保 sys.config 和 vm.args 在所有环境中一致

💡 最佳实践

  1. 开发时使用 dev_mode(符号链接更快)
  2. 生产构建包含 ERTS(include_erts, true
  3. 使用 extended_start_script 生成管理脚本
  4. 定期测试升级和降级流程
  5. 使用容器(Docker)简化部署

19.8 扩展阅读


上一章:18 - 分布式 下一章:20 - 性能优化