到底什么是发行版和目标系统?
发行版是启动 Erlang VM 和启动项目所需的一组应用程序。通过用于生成
.script
和.boot
的发行版资源文件(.rel
)描述它。启动文件是二进制形式的脚本文件,Erlang 运行时系统(ERTS)使用其启动 Erlang 节点,有点像启动操作系统。甚至在命令行上运行erl
也是使用启动脚本。目标系统是能够在其它机器上(虚拟机或其它)启动的 Erlang 系统。ERTS 通常与目标系统捆绑在一起。
如欲了解更多信息,请参阅 Adopting Erlang 中关于发行版的章节。
向项目的 rebar.config
添加 relx
部分:
{relx, [{release, {<release_name>, <release_vsn>},
[<app>]},
{release, {<release_name>, <release_vsn>},
[<app>]},
{dev_mode, true},
{include_erts, false},
{extended_start_script, true}]}.
运行 rebar3 release
将构建发行版,在 _build/<profile>/rel/<release name>/bin/<release name>
下面提供用于启动节点的脚本。
<release_name>
必须是原子,<release_vsn>
可以是下面的任意一个:
版本类型 | 结果 |
---|---|
string() | 使用字符串作为版本。比如 "0.1.0" |
git | semver | 使用存储库上最新的 git 标签构造版本 |
{cmd, string()} | 使用在 Shell 中执行 string() 的内容的结果。比如使用文件 VERSION :{cmd, "cat VERSION | tr -d '[:space:]'"} |
{git, short | long} | 使用当前提交的简短的(8 个字符)或完整的 Git 引用 |
{file, File} | 使用文件的内容。比如,比使用 cmd 更好的使用 VERSION 文件的方式是:{file, "VERSION"} |
每个 <app>
是原子的应用程序名称(比如,myapp
),或元组。关于 <app>
的元组语法,请查看 App Syntax。
可以向项目的 rebar.config
中的 relx
下添加多个 release
部分。
可以指定共享相同配置的不同发行版:
{relx, [{release, {<release name>, "0.0.1"},
[<app>]},
{release, {<other release name>, "0.1.0"},
[<app>]},
{dev_mode, false},
{include_erts, true},
]}.
也可以通过使用 4 元组 release
定义的方式,指定具有独立配置的发行版:
{relx, [{release, {<release name>, "0.0.1"},
[<app>],
[{dev_mode, false},
{include_erts, true}]},
{release, {<release name>, "0.1.0"},
[<app>],
[{dev_mode, true}]}
]}.
可以使用 rebar3 release -n <release_name>
构建指定的发行版。
{release, {myrelease, "0.1.0"},
[<app1>, <app2>]}
在发行版中,每个 <app>
是原子的应用程序名称,或具有应用程序名称和额外选项的元组:
% No options specified
myapp
% All options specified
{myapp, "1.1.0", transient, [otherapp]}
% Some options specified
{myapp, "1.1.0"}
{myapp, "1.1.0", transient}
{myapp, "1.1.0", [otherapp]}
{myapp, transient}
{myapp, [otherapp]}
{myapp, transient, [otherapp]}
在这个例子中,发行版中包含 myapp
,其版本是 "1.1.0"
,其启动类型是 transient
,其包含的应用程序列表是 [otherapp]
。这些选项直接对应于发行版资源文件(release resource file (.rel
))。
{<app_name>, % atom()
<app_vsn>, % string()
<start_type>, % permanent | transient | temporary | load | none
<included_apps>} % [atom()]
<app_name>
之后的元素可以省略,只要顺序保持不变
如果包含 <app_vsn>
(不常见),其必须匹配 .app.src
。否则 relx
通过 .app.src
确定之
发行版资源文件语法,为完整起见,在下面重复一遍:
<start_type>
默认为 permanent
<included_apps>
默认为 .app.src
中的 <included_apps>
。如果指定,必须是 .app.src
中的 <included_apps>
的子集默认情况下,发行版包含应用程序的源文件(如果存在的话)。
如果不希望包含源文件,那么将 include_src
设置为 false。
{include_src, false}
下面的指令从输出的发行版中移除指定的应用程序。
{exclude_apps, [app1, app2]}
在发行版中,应用程序将被从任何应用程序的 .app
文件中移除,它们被列在 applications
下。
下面的指令从输出的发行版中移除应用程序模块。
{exclude_modules, [
{app1, [app1_mod1, app1_mod2]},
{app2, [app2_mod1, app2_mod2]}
]}.
模块将被从应用程序的 .app
文件的 module
列表中移除。
其它模式包括 prod
和 minimal
:
{relx, [...
{mode, <mode>},
...
]
}.
模式 | 扩展选项 |
---|---|
dev | [{dev_mode, true}, {include_src, true}, {debug_info, keep}, {include_erts, false}] |
prod | [{include_src, false}, {debug_info, strip}, {include_erts, true}, {dev_mode, false}] |
minimal | [{include_src, false}, {debug_info, strip}, {include_erts, false}, {dev_mode, false}] |
在开发过程中,可能希望对应用程序的所有更改在发行版中立即可用。relx
提供多种模式,包括用于该特定使用场景的模式 dev
。它创建符号链接,而非将组成发行版的应用程序拷贝到发行版结构中,因此编译、重新启动或加载更改的模块就已足够。
prod
模式增加常用于生产版本的选项:include_src
为 false
,以便发行版中不包含源码;debug_info
被设置为 strip
,使得 BEAM 文件更小,并且仅移除发行版中极可能不包含的工具使用的数据;include_erts
被设置为 true
,在当前 Erlang 运行时进行捆绑,生成可以拷贝到兼容的目标系统,并且无需先安装 Erlang 即可运行的发行版。
Rebar3 Prod Profile
当在
rebar3
prod
profile 中构建时,比如使用rebar3 as prod release
,将自动启用relx
prod
模式。
minimal
模式与 prod
的唯一不同是,它不包含 Erlang 运行时。
可以通过包含显式设置的方式,重写模式添加的选项。比如,如果想在 BEAM 模块中保留调试信息,那么可以使用如下配置:
[
{mode, prod},
{debug_info, keep}
]
默认情况下,relx
将检查发行版包含的项目应用程序的模块中使用的外部函数是否存在。这意味着如果调用的函数未被包含在发行版中,将给出警告,即使它是 rebar.config
中的依赖或 OTP 中包含的应用程序。
这有助于避免一个常见错误,即忘记将应用程序添加到依赖它的应用程序的 .app.src
中。
如果出于某种原因,希望禁用该检查,那么在 relx
配置中将其设置为 false:
{check_for_undefined_functions, false}
选项 src_tests
在模块的源代码缺失或比目标代码更新时,将发出警告:
{src_tests, true}
这对捕获依赖源文件的任何修改非常有用。因为 rebar3 release
将自动编译项目中应用程序的所有更改,依赖应该是唯一可能过时的模块。
默认情况下,relx
提供设置节点名称和 Cookie 的基础 vm.args
文件。如欲获取选项及其用法的完整列表,请参阅 Erlang documentation。
## Name of the node
-name {{release_name}}@127.0.0.1
## Cookie for distributed Erlang
-setcookie {{release_name}}
为提供自定义的 vm.args
或 vm.args.src
,只需在项目根目录的顶级 config/
目录中创建该文件。如果将其命名为 vm.args
或 vm.args.src
之外的名称,那么必须添加到 relx
配置:
{vm_args, "config/vm_prod.args"}
或
{vm_args_src, "config/vm_prod.args.src"}
通过 sys.config
和 sys.config.src
,在发布时,传递应用程序配置:
[
{<app_name>, [{<key>, <val>}, ...]}
].
如果项目中存在文件 config/sys.config.src
或 config/sys.config
,那么 relx
将自动地在发行版中包含其中之一(如果两者都存在,那么 .src
优先)。
为将指定文件设置为应用程序配置文件,可以设置 sys_config
或 sys_config_src
:
{sys_config, "config/sys_prod.config"}
{sys_config_src, "config/sys_prod.config.src"}
被包含进发行版时,该文件将被重命名为 sys.config
或 sys.config.src
。
如果都不存在,那么使用带有空列表的文件。
在 config docs 和 systools docs 中,阅读关于 Erlang 配置的更多信息。
OTP-21+ 和 Rebar3 3.6+
从 Erlang/OTP 21 和 Rebar3 3.6.0 开始,可以使用配置选项 sys_config_src
和 vm_args_src
显式地包含运行时渲染的模板,将定义为 ${VARIABLE}
的变量替换为 Shell 环境中的等价值。从 Rebar3 3.14.0 开始,在使用变量时,可以通过将变量定义为 ${VARIABLE:-DEFAULT}
的方式,设置默认值。
从 Rebar3 3.14.0 开始,如果配置文件存在,那么将包含配置,因此仅当文件未被命名为 config/sys.config.src
和 config/vm.args.src
时,才需要在 relx 配置中包含 {sys_config_src, <filename>}
或 {vm_args_src, <filename>}
。
%% sys.config.src
[
{appname,
[
{port, ${PORT:-8080}},
{log_level, ${LOG_LEVEL:-info}},
{log_root, "${LOG_ROOT:-/var/log/appname}"}
]}
].
# vm.args.src
-name ${NODE_NAME}
%% rebar.config
{relx, [{release, {<release name>, "0.0.1"},
[<app>]},
{mode, dev}]}.
当使用 .src
文件进行配置时,无需设置 RELX_REPLACE_OS_VARS=true
。
覆盖提供定义包含在目标系统中的文件和模板的能力。例如,用于管理节点的自定义脚本或在 Heroku 上运行所需的 Procfile。
{relx, [
...
{overlay_vars, "vars.config"},
{overlay, [{mkdir, "log/sasl"},
{template, "priv/app.config", "etc/app.config"},
{copy, "Procfile", "Procfile"}]}
]}.
支持的操作包括:
mkdir
:在发行版内创建目录copy
:从本地目录拷贝文件到发行版内的位置behave
:行为与 copy
相同,但展开其中的变量Relx 的模版暴露 Mustache 模版系统(查看 mustache)的变量以及全部能力。查看文档,获取支持的全部语法。
在默认情况下,有一组变量可用,下一个会话将描述它们,另外可以在 {overlay_vars, "vars.config"}
指定的文件中声明自定义变量,其形式如下:
%% some variables
{key, value}.
{other_key, other_val}.
%% includes variables from another file
"./some_file.config".
默认变量的定义如下:
预定义覆盖变量
名称 | 描述 |
---|---|
log | <logname>:<loglevel> 格式的当前日志级别 |
output_dir | 构建的发行版的当前输出目录 |
target_dir | 与 output_dir 相同;为向后兼容而存在 |
overridden | 被重写的应用程序的当前列表(应用程序名称列表) |
goals | 系统中用户指定的目标列表 |
lib_dirs | 库目录的列表,包括用户指定的和派生的 |
config_file | 系统中使用的配置文件列表 |
providers | 用于此 relx 运行的提供者名称列表 |
sys_config | sys.config 文件的位置 |
root_dir | 当前项目的根目录 |
default_release_name | 用于 relx 运行的当前默认发行版名称 |
default_release_version | 用于 relx 运行的当前默认发行版版本 |
default_release | 用于 relx 运行的当前默认发行版 |
release_erts_version | 使用中的 Erlang 运行时系统的版本 |
erts_vsn | 与 release_erts_version 相同(为向后兼容) |
release_name | 当前正在执行的发行版 |
release_version | 当前正在执行的版本 |
rel_vsn | 与 release_version 相同。为向后兼容而存在 |
release_applications | 发行版中包含的应用程序列表 |
可以拆分覆盖文件,以处理更复杂的场景。接下来看一个简化的示例。
在构建应用程序时,我们希望通过拼写为 "prod"
或 "dev"
的覆盖变量 build
区分生产构建和开发构建,因此 app.confg
文件应该在其配置中包含该变量,我们可以开启或禁用特性。
为此,我们构建三个覆盖文件:
dev.config
prod.config
base.config
对于开发构建,使用 dev.config
作为 overlay_vars
;对于生产,使用 prod.config
。
%% base.config
{data_dir, "/data/yolo_app"}.
{version, "1.0.0"}.
{run_user, "root"}.
%% dev.config
%% Include the base config
"./base.config".
%% The build we have
{build, "dev"}.
%% prod.config
%% Include the base config
"./base.config".
%% The build we have
{build, "prod"}.
目标系统不能有像使用 dev_mode
时那样创建的符号连接,并且通常希望在系统中包含 ERTS,以便不需要预先在目标机器上安装它。
如果使用 prod
profile 构建发行版,那么 Rebar3 将自动地将 {mod, prod}
添加到 Relx 配置。比如:
$ rebar3 as prod tar
===> Verifying dependencies...
===> Analyzing applications...
===> Compiling relx_overlays
===> Assembling release myrel-0.1.0...
===> Release successfully assembled: _build/prod/rel/myrel
===> Building release tarball myrel-0.1.0.tar.gz...
===> Tarball successfully created: _build/prod/rel/myrel/myrel-0.1.0.tar.gz
可以将 Tar 包 myrel-0.1.0.tar.gz
拷贝到其它兼容的系统及启动:
$ mkdir myrel
$ mv myrel-0.1.0.tar.gz myrel/
$ cd myrel
$ tar -zxvf myrel-0.1.0.tar.gz
$ bin/myrel console
当需要将 ERTS 排除在发行版之外时,可以在 rebar.config
中的 profile
下设置 prod
profile 配置。比如,如果想使用目标机器上的 ERTS 和基础应用程序(比如 kernal
和 stdlib
),那么在 relx
配置元组中,设置 mode
为 minimal
,system_libs
为 false
。
{profiles, [{prod, [{relx, [{mode, minimal},
{system_libs, false}]}]}]}.
或手动地设置 include_erts
为 false
:
{profiles, [{prod, [{relx, [{include_erts, false},
{system_libs, false}]}]}]}
当运行 rebar3 as prod tar
时,生成的 Tar 包将不包含 ERTS 或 kernal
和 stdlib
之类的应用程序。
如果希望包含用于运行 rebar3
的版本之外的 Erlang 运行时系统,比如在 macOS 上构建,但希望包含为 GNU/Linux 版本构建的 ERTS,那么在 relx
配置元组中,为 include_erts
提供路径,而非布尔值,同时为 system_libs
提供路径:
{include_erts, "/path/to/erlang"},
{system_libs, "/path/to/erlang"},
使用这些路径与 Profile 可以更容易地设置交叉编译。
relx
附带的扩展启动脚本提供若干种启动和连接发行版的方式。
对于本地开发,可以使用 console
。在生产环境中,无论是在 tmux
之类的工具中手动启动,还是使用 systemd
之类的 init 系统,或者在 Docker 容器中运行发行版,都需要 foreground
。
如果想在使用 foreground
启动的节点上打开控制台,那么使用 remote_console
。
命令的完整列表如下:
命令 | 描述 |
---|---|
foreground | 启动发行版,输出到标准输出 |
remote | 将远程 Shell 连接到正在运行的节点 |
console | 使用交互式 Shell 启动发行版 |
console_clean | 启动交互式 Shell,但不启动发行版的应用程序 |
rpc [Mod [Fun [Args]]]] | 在正在运行的节点上,运行 apply(Mod, Fun, Args) |
eval [Exprs] | 在正在运行的节点上,运行表达式 |
status | 验证节点是否正在运行,然后运行状态钩子脚本 |
restart | 重启应用程序,但不重启 VM |
reboot | 重启整个 VM |
stop | 停止正在运行的节点 |
pid | 打印 OS 进程的 PID |
ping | 如果节点活着,那么打印 pong |
daemon | 使用 run_erl (命名管道)在后台启动发行版 |
daemon_attach | 使用 to_erl (命名管道)连接到以 Daemon 方式启动的节点 |
此外,扩展启动脚本包含使用 release_handler 的命令:
命令 | 描述 |
---|---|
unpack [Version] | 解开发行版 Tar 包 |
install [Version] | 安装发行版 |
uninstall [Version] | 卸载发行版 |
upgrade [Version] | 将正在运行的发行版升级到新版本 |
downgrade [Version] | 将正在运行的发行版降级到新版本 |
versions | 打印可用的发行版版本 |
如欲理解上述命令的工作方式,请查看 OTP 设计原则章节 Release Handling。
对于详细的工作流程,包括版本增量和应用程序更新,请查看 Richard Jones 围绕 rebar3
构建的 relflow 工具。
对于发行版安装后的基本版本升级,假设我们有一个名为 myrel
的发行版,它有版本 0.0.1
和 0.0.2
:
bin/myrel install 0.0.1
bin/myrel versions
upgrade
升级到该版本:bin/myrel upgrade 0.0.2
downgrade
命令:bin/myrel downgrade 0.0.1
可以在扩展启动脚本的特定操作上定义钩子,操作包括 start
、stop
和 install_upgrade
;可以为每个操作定义 pre
和 post
钩子。
钩子是内建的(发行版中已经包含它们)或自定义的(用户为自定义功能编写的脚本)。提供预打包功能的内建脚本包括:
/var/run/<rel_name>.pid
){extended_start_script_hooks, [
{pre_start, [{custom, "hooks/pre_start"}]},
{post_start, [
{pid, "/tmp/foo.pid"},
{wait_for_process, some_process},
{custom, "hooks/post_start"}
]},
{pre_stop, [
{custom, "hooks/pre_stop"}]},
{post_stop, [{custom, "hooks/post_stop"}]},
]},
{post_stop, [{custom, "hooks/post_stop"}]}
]}.
生成的扩展启动脚本附带一组用于管理发行版的内建命令:foreground
、stop
、restart
等。
有时,暴露一些特定于应用程序的定制命令很有用。比如,如果正在运行一个游戏服务器,那么调用 bin/gameserver games
输出有用的信息很方便。
扩展启动脚本扩展支持创建附加到启动脚本的可用命令列表的自定义 Shell 脚本。扩展 Shell 脚本可以接受参数,并且可以访问启动脚本自身中定义的所有 Shell 变量。以在 rebar.config
中定义扩展开始,比如:
%% start script extensions
{extended_start_script_extensions, [
{status, "extensions/status"}
]}.
这里是正在添加的 status
脚本扩展,它将调用 extensions/status
Shell 脚本。
该路径相对于生成的发行版中的启动脚本的位置,可以使用 overlay
将它放在正确的位置:
{copy, "scripts/extensions/status", "bin/extensions/status"},
扩展脚本本身是标准的 Shell 脚本,可以以如下方式实现上面描述的游戏服务器示例:
case $1 in
help)
echo "bin/gameserver status"
;;
*)
;;
esac
# get the status tuple from gameserver
Status=$(relx_nodetool eval "pool_debug:status(json).")
# now print it out
code="Json = binary_to_list($Status),
io:format(\"~p~n\", [Json]),
halt()."
echo $(erl -boot no_dot_erlang -sasl errlog_type error -noshell -eval "$code")
可以在运行发行版的目标系统上设置 RELX_RPC_TIMEOUT
环境变量值,以选择脚本在放弃与正在运行的 Erlang 系统的联系之前可以等待多长时间。如果未指定值,那么默认为 NODETOOL_TIMEOUT
的值(从毫秒转换为秒)。如果未设置 NODETOOL_TIMEOUT
本身,那么默认为 60 秒。