版本發布

📘

什麼是版本發布和目標系統?

版本發布是指啟動 Erlang 虛擬機並啟動專案所需的一組應用程式。 這是通過版本資源文件 (.rel) 描述的,該文件用於生成 .script.boot。 啟動文件是腳本文件的二進制形式,Erlang 運行時系統 (ERTS) 使用它來啟動 Erlang 節點,有點像啟動操作系統。 即使在命令列上運行 erl 也在使用啟動腳本。

目標系統是可以 boots 在另一台機器(虛擬機或其他)上的 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/<設定檔>/rel/<版本名稱>/bin/<版本名稱> 下的節點。

<版本名稱> 必須是一個原子。 <版本號碼> 可以是以下其中之一

版本類型 結果
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} 使用文件的內容。 例如,使用 VERSION 文件比使用 cmd 更好的方法是:{file, "VERSION"}

每個 <app> 都是應用程式名稱的原子(例如,myapp),或是一個元組。 有關 <app> 的元組語法,請參閱 應用程式語法

您可以在專案的 rebar.configrelx 下新增多個 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, {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.srcincluded_apps 的子集。

在版本發布中包含原始碼

預設情況下,版本發布將包含應用程式的原始碼文件(如果有的話)。

如果您不想包含原始碼文件,請將 include_src 設定為 false。

{include_src, false}

應用程式排除

以下允許您從輸出版本中移除特定的應用程式。

{exclude_apps, [app1, app2]}

應用程式將從版本中任何在其 applications 下列出它們的應用程式的 .app 文件中移除。

模組排除

以下指令允許您從輸出版本中移除應用程式模組。

{exclude_modules, [
    {app1, [app1_mod1, app1_mod2]},
    {app2, [app2_mod1, app2_mod2]}
]}.

模組將從應用程式的 .app 文件的 module 清單中移除。

模式

其他模式包括 prodminimal

{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_srcfalse,因此版本中不包含原始碼;debug_info 設定為 strip,這會使 BEAM 文件更小,並且只移除您很可能未包含在版本中的工具所使用的數據;include_erts 設定為 true,以便捆綁目前的 Erlang 運行時,從而使您可以將版本複製到相容的目標並在不安裝 Erlang 的情況下運行。

📘

Rebar3 生產設定檔

當使用 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.argsvm.args.src,只需在專案根目錄的頂層 config/ 目錄中建立文件即可。 如果您將其命名為 vm.argsvm.args.src 以外的名稱,則必須將其新增到 relx 設定中

{vm_args, "config/vm_prod.args"}

{vm_args_src, "config/vm_prod.args.src"}

應用程式設定

要在版本運行時傳遞應用程式設定,可以使用 sys.configsys.config.src

[
  {<app_name>, [{<key>, <val>}, ...]}
].

如果專案中存在文件 config/sys.config.srcconfig/sys.config,則 relx 將自動將其中一個(如果兩個都存在,則 .src 優先)包含在版本中。

要設定特定文件用作應用程式設定文件,可以使用 sys_configsys_config_src 進行設定

{sys_config, "config/sys_prod.config"}
{sys_config_src, "config/sys_prod.config.src"}

當包含在版本中時,文件將重新命名為 sys.configsys.config.src

如果兩者都不存在,則使用包含空清單的文件。

config 文件systools 文件 中閱讀有關 Erlang 設定的更多資訊。

環境變數替換

使用 OTP-21+ 和 Rebar3 3.6+

從 Erlang/OTP 21 和 Rebar3 3.6.0 開始,可以使用配置選項 sys_config_srcvm_args_src 來明確包含將在運行時渲染的模板,並將定義為 ${VARIABLE} 的變數替換為其在 shell 環境中的等效值。自 Rebar3 3.14.0 起,使用變數時,可以選擇透過將變數定義為 ${VARIABLE:-DEFAULT} 來設定預設值。

從 Rebar3 3.14.0 開始,如果配置檔存在,則會包含它們,因此只有當檔案名稱不是 config/sys.config.srcconfig/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=truevm.argssys.config 檔案都可以包含作業系統環境變數,這些變數將被節點啟動所在環境中的目前值替換。這表示用於啟動在特定埠上監聽的網路伺服器的版本的 vm.argssys.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

覆蓋:建置時設定

覆蓋(Overlay)提供了在目標系統中定義要包含的檔案和模板的功能。例如,用於管理節點的自訂腳本或在 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"}.

可部署的 Tarball

包含 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

現在,可以將壓縮包 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.configprofiles 下設定 prod 設定檔配置。例如,若要使用目標系統上的 ERTS 和基本應用程式(如 kernelstdlib),請在 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 時,生成的壓縮包將不包含 ERTS 或應用程式(如 kernelstdlib)。

使用為其他系統建置的 ERTS

如果您希望包含的 Erlang 運行時系統版本與您用於運行 rebar3 的版本不同,例如您在 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 啟動版本,輸出到 stdout
remote 將遠端 shell 連接到正在運行的節點
console 使用互動式 shell 啟動版本
console_clean 啟動不包含版本應用程式的互動式 shell
rpc [Mod [Fun [Args]]]] 在正在運行的節點上運行 apply(Mod, Fun, Args)
eval [Exprs] 在正在運行的節點上運行表達式
status 驗證節點是否正在運行,然後運行狀態鉤子腳本
restart 重新啟動應用程式,但不重新啟動 VM
reboot 重新啟動整個 VM
stop 停止正在運行的節點
pid 列印作業系統行程的 PID
ping 如果節點處於活動狀態,則列印 pong
daemon 使用 run_erl 在背景中啟動版本(命名管道)
daemon_attach 使用 to_erl 連接到以守護程式方式啟動的節點(命名管道)

版本處理:安裝和升級

此外,擴展啟動腳本還包含使用 release_handler 的命令

命令 說明
unpack [Version] 解壓縮版本壓縮包
install [Version] 安裝版本
uninstall [Version] 卸載版本
upgrade [Version] 將正在運行的版本升級到新版本
downgrade [Version] 將正在運行的版本降級到新版本
versions 列印可用版本的版本號

要了解這些工作原理,請參閱 OTP 設計原則章節 版本處理

有關包含版本增量和 appup 生成的詳細工作流程,請查看 Richard Jones 圍繞 rebar3 構建的 relflow 工具。

對於安裝版本後的基本版本升級,假設我們有一個名為 myrel 的版本,版本號為 0.0.10.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

鉤子 (Hooks)

可以為擴展啟動腳本的特定操作定義鉤子,這些操作包括 startstopinstall_upgrade;每個操作都有 prepost 鉤子可用。

鉤子可以是內建的(即已包含在版本中),也可以是自訂的(使用者編寫的用於自訂功能的腳本)。提供預先打包功能的內建腳本如下:

  • pid:將 BEAM pid 寫入可配置的檔案位置(預設為 /var/run/<rel_name>.pid)。
  • wait_for_vm_start:等待 VM 啟動(即可以 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"}]}
]}.

擴充功能

生成的擴展啟動腳本附帶一組內建命令,允許您管理版本:foregroundstoprestart 等。

有時,公開一些特定於您的應用程式的自訂命令很有用。例如,如果您正在運行遊戲伺服器,則只需調用 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")

其他設定

可以在運行版本的目標系統上設定 RELX_RPC_TIMEOUT 環境變數,以選擇腳本在放棄與正在運行的 Erlang 系統聯繫之前可以等待多長時間。如果未指定任何值,則預設為 NODETOOL_TIMEOUT 值(從毫秒轉換為秒)。如果未設定 NODETOOL_TIMEOUT 本身,則預設值為 60 秒。

參考

上次修改時間:2022 年 5 月 27 日:修正支援預設值的 rebar3 版本 (6181f59)