版本发布
📘
版本和目标系统到底是什么?
版本是一组启动 Erlang 虚拟机并启动项目所需的应用程序。这通过版本资源文件(
.rel
)描述,该文件用于生成.script
和.boot
。引导文件是脚本文件的二进制形式,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 ref。 |
{file, File} |
使用文件的内容。例如,使用VERSION 文件比使用cmd 更好的方法是:{file, "VERSION"} |
每个<app>
都是应用名称的原子(例如,myapp
),或一个元组。有关<app>
的元组语法,请参阅应用语法。
您可以在项目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]
。这些选项直接对应于版本资源文件 (.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 配置文件
在
rebar3
prod
配置文件中构建时,例如使用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
将提供一个基本vm.args
文件,该文件设置节点名称和 cookie。有关选项及其用法的完整列表,请查看Erlang 文档。
## 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 文档和systools 文档中阅读有关 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
。在下一节中,我们将看到运行时配置的旧形式。
OTP-21 和 Rebar3 3.6 之前
通过设置 RELX_REPLACE_OS_VARS=true
,vm.args
和 sys.config
文件都可以包含 OS 环境变量,这些变量将被替换为节点启动时环境中的当前值。这意味着用于启动 Web 服务器并在端口上监听的版本的 vm.args
和 sys.config
可能如下所示
# vm.args
-name ${NODE_NAME}
%% sys.config
[
{appname, [{port, "${PORT}"}]}
].
然后可以用来启动同一个版本的多个节点,并且节点名称不同。
#!/bin/bash
export RELX_REPLACE_OS_VARS=true
for i in `seq 1 10`;
do
NODE_NAME=node_$i PORT=808$i _build/default/rel/<release>/bin/<release> foreground &
sleep 1
done
覆盖:构建时配置
覆盖提供了定义文件和模板以包含在目标系统中的能力。例如,用于管理节点的自定义脚本或在 Heroku 上运行所需的 Procfile。
{relx, [
...
{overlay_vars, "vars.config"},
{overlay, [{mkdir, "log/sasl"},
{template, "priv/app.config", "etc/app.config"},
{copy, "Procfile", "Procfile"}]}
]}.
支持的操作有
mkdir
用于在发布版本中创建目录copy
用于将文件从本地目录复制到发布版本中的某个位置template
的行为方式与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 |
版本中包含的应用列表 |
拆分配置
可以拆分覆盖文件以处理更复杂的情况。为了解释这一点,让我们看一个简化的例子。
我们构建应用程序,并希望通过让覆盖变量 build
拼写出 "prod"
或 "dev"
来区分生产和开发构建,以便 app.config
文件可以在其配置中包含它,并且我们可以启用或禁用功能。
为此,我们构建三个覆盖文件
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"}.
可部署的压缩包
包含 ERTS
目标系统不能有像使用 dev_mode
时创建的符号链接,并且我们通常希望将 ERTS 与系统一起包含进来,以便它不需要事先安装在目标系统上。
如果使用 prod
配置文件构建版本,Rebar3 将自动将 {mode, 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
现在可以将 tarball 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
当需要将 ERTS 从版本中排除时,可以在 rebar.config
的 profiles
下设置 prod
配置文件配置。例如,要在目标上使用 ERTS 和基本应用程序(如 kernel
和 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
时,生成的 tarball 将不包含 ERTS 或 kernel
和 stdlib
等应用程序。
包含为其他系统构建的 ERTS
如果您希望包含一个与运行 rebar3
的版本不同的 Erlang 运行时系统,例如您在 MacOS 上构建,但希望包含为 GNU/Linux 版本构建的 ERTS,则可以为 include_erts
提供路径而不是布尔值,并为 system_libs
提供路径,仍然在 relx
配置元组中
{include_erts, "/path/to/erlang"},
{system_libs, "/path/to/erlang"},
将这些路径与配置文件一起使用可以产生更简单的方法来设置交叉编译。
扩展启动脚本
命令
relx
附带的扩展启动脚本提供了启动和连接到版本的几种方法。
对于本地开发,您可能会使用 console
。在生产环境中,您将希望使用 foreground
,无论您是在 tmux
等工具中手动启动,还是使用 systemd
等初始化系统,或者在 Docker 容器中运行版本。
要打开使用 foreground
启动的节点上的控制台,请使用 remote_console
。
完整的命令列表如下。
命令 | 描述 |
---|---|
foreground |
将输出发送到标准输出的启动版本 |
remote |
连接到正在运行的节点的远程 shell |
console |
使用交互式 shell 启动版本 |
console_clean |
在没有版本的应用程序的情况下启动交互式 shell |
rpc [Mod [Fun [Args]]]] |
在正在运行的节点上运行 apply(Mod, Fun, Args) |
eval [Exprs] |
在正在运行的节点上运行表达式 |
status |
验证节点是否正在运行,然后运行状态挂钩脚本 |
restart |
重新启动应用程序,但不重新启动虚拟机 |
reboot |
重新启动整个虚拟机 |
stop |
停止正在运行的节点 |
pid |
打印 OS 进程的 PID |
ping |
如果节点处于活动状态,则打印 pong |
daemon |
使用 run_erl (命名管道)在后台启动版本 |
daemon_attach |
连接到使用 to_erl (命名管道)作为守护程序启动的节点 |
版本处理:安装和升级
此外,扩展启动脚本还包含用于使用 release_handler 的命令
命令 | 描述 |
---|---|
unpack [Version] |
解压缩版本 tarball |
install [Version] |
安装版本 |
uninstall [Version] |
卸载版本 |
upgrade [Version] |
将正在运行的版本升级到新版本 |
downgrade [Version] |
将正在运行的版本降级到新版本 |
versions |
打印可用的版本号 |
要了解这些命令的工作原理,请参阅 OTP 设计原则章节 版本处理。
有关包括版本增量和 appup 生成在内的详细工作流程,请查看 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
挂钩都可用。
挂钩可以是内置的(即它们已包含在版本中)或自定义的(用户为自定义功能编写的脚本)。提供预打包功能的内置脚本为
- pid:将 BEAM pid 写入可配置的文件位置(默认情况下为
/var/run/<rel_name>.pid
)。 - wait_for_vm_start:等待虚拟机启动(即,当它可以被 ping 时)。
- wait_for_process:等待可配置名称出现在 Erlang 进程注册表中。
{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 脚本,描述的游戏服务器示例可以用以下方式实现
#!/bin/bash
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")
其他配置
可以在运行版本的 target 系统上设置 RELX_RPC_TIMEOUT
环境值,以选择脚本在放弃联系正在运行的 Erlang 系统之前可以等待多长时间。如果未指定值,则默认为 NODETOOL_TIMEOUT
值(从毫秒转换为秒)。如果 NODETOOL_TIMEOUT
本身未设置,则默认为 60 秒。