檢視原始碼 mix release (Mix v1.16.2)

組建當前專案的獨立版本

$ MIX_ENV=prod mix release
$ MIX_ENV=prod mix release NAME

組建版本後,可以將其打包並部署到目標,只要目標執行與執行 mix release 指令的機器相同的作業系統 (OS) 發行版和版本即可。

可以在 mix.exs 檔案中,於 def project 內的 :releases 鍵下,設定版本

def project do
  [
    releases: [
      demo: [
        include_executables_for: [:unix],
        applications: [runtime_tools: :permanent]
      ],

      ...
    ]
  ]
end

可以指定多個版本,其中鍵為版本名稱,值為包含版本設定的關鍵字清單。透過以下方式發布特定名稱:

$ MIX_ENV=prod mix release demo

如果給定的名稱不存在,會引發錯誤。

如果呼叫 mix release,而未指定版本名稱,且已設定多個版本,除非在專案設定的根目錄設定 default_release: NAME,否則會引發錯誤。

如果呼叫 mix release,且未設定任何版本,會使用應用程式名稱和預設值組建版本。

為何需要版本?

版本允許開發人員預先編譯並將所有程式碼和執行時間打包成單一單元。版本的優點包括

  • 程式碼預載。VM 有兩種載入程式碼的機制:互動式和嵌入式。預設情況下,它會以互動模式執行,在首次使用模組時動態載入模組。當應用程式首次呼叫 Enum.map/2 時,VM 會找到 Enum 模組並載入它。有一個缺點:在生產環境中啟動新的伺服器時,可能需要載入許多其他模組,導致第一批要求的回應時間異常激增。透過版本,系統會預先載入所有模組,並保證系統在開機後就能處理要求。

  • 設定和自訂。版本讓開發人員能精細控制系統設定和用於啟動系統的 VM 旗標。

  • 獨立。版本不需要在生產成品中包含原始碼。所有程式碼都已預先編譯並打包。版本甚至不需要伺服器中的 Erlang 或 Elixir,因為它預設包含 Erlang VM 及其執行時間。此外,Erlang 和 Elixir 標準函式庫都會被精簡,只保留實際使用的部分。

  • 多個版本。可以為每個應用程式組建不同的版本,並採用不同的設定,甚至可以完全使用不同的應用程式。

  • 管理腳本。版本附帶腳本,用於啟動、重新啟動、遠端連線至執行中系統、執行 RPC 呼叫、作為守護程式執行、作為 Windows 服務執行等。

執行版本

組建版本後,您可以在版本內呼叫 bin/RELEASE_NAME start 來啟動它。在生產環境中,您會執行

$ MIX_ENV=prod mix release
$ _build/prod/rel/my_app/bin/my_app start

bin/my_app start 將啟動連線至目前標準輸入/輸出的系統,記錄預設也會寫入其中。這是執行系統的首選方式。許多工具(例如 systemd)、平台即服務(例如 Heroku)和許多容器平台(例如 Docker)都能處理標準輸入/輸出,並將記錄內容重新導向其他地方。這些工具和平台也會在系統崩潰時負責重新啟動系統。

您也可以執行一次性命令,在類 Unix 系統上將版本作為守護程式執行,或將其安裝為 Windows 服務。我們將在接下來探討這些內容。您也可以透過呼叫 bin/RELEASE_NAME 來列出所有可用的命令。

一次性命令 (eval 和 rpc)

如果您想在版本中呼叫特定模組和函式,您可以透過兩種方式執行:使用 evalrpc

$ bin/RELEASE_NAME eval "IO.puts(:hello)"
$ bin/RELEASE_NAME rpc "IO.puts(:hello)"

eval 命令會啟動 VM 的自己的執行個體,但不會啟動版本中的任何應用程式,也不會啟動發行。例如,如果您需要在執行實際系統之前進行一些準備工作,例如遷移資料庫,eval 會是很好的選擇。只要記住,您在 eval 期間使用的任何應用程式都必須明確啟動。

您可以透過呼叫 Application.ensure_all_started/1 來啟動應用程式。從 Elixir v1.16 開始,保證已至少載入應用程式。在較早的版本中,如果您需要載入應用程式但不要啟動它們,您也需要呼叫 Application.load/1

執行命令的另一種方式是使用 rpc,它會連線至目前執行的系統,並指示它執行給定的表達式。這表示您需要保證系統已啟動,並小心您執行的指令。您也可以使用 remote 將遠端 IEx 會話連線至系統。

輔助模組

在操作系統時,您可能會發現自己經常將某段程式碼當作一次性指令執行。您可以考慮建立一個模組來將這些任務分組

# lib/my_app/release_tasks.ex
defmodule MyApp.ReleaseTasks do
  def eval_purge_stale_data() do
    # Eval commands needs to start the app before
    # Or Application.load(:my_app) if you can't start it
    Application.ensure_all_started(:my_app)

    # Code that purges stale data
    ...
  end

  def rpc_print_connected_users() do
    # Code that print users connected to the current running system
    ...
  end
end

在上面的範例中,我們使用執行函式的指令名稱作為函式名稱的前綴,但這完全是可選的。

並執行它們

$ bin/RELEASE_NAME eval "MyApp.ReleaseTasks.eval_purge_stale_data()"
$ bin/RELEASE_NAME rpc "MyApp.ReleaseTasks.rpc_print_connected_users()"

守護程式模式 (類 Unix)

您可以使用指令以守護程式模式執行發佈版本

$ bin/RELEASE_NAME daemon

在守護程式模式中,系統會透過 run_erl 在背景中啟動。您可能還想在守護程式模式中啟用 heart,以便在系統發生故障時自動重新啟動系統。請參閱產生的 releases/RELEASE_VSN/env.sh 檔案。

守護程式會將其所有標準輸出寫入發佈版本根目錄中的「tmp/log/」目錄。您可以透過執行 tail -f tmp/log/erlang.log.1 或類似的指令來查看日誌檔。一旦檔案過大,索引字尾會遞增。開發人員也可以從發佈版本根目錄呼叫「to_erl tmp/pipe/」來附加到守護程式的標準輸入。但是,請注意,附加到系統時應極為小心,因為用於退出 Elixir 系統的常用指令,例如按兩次 Ctrl+C 或 Ctrl+\,實際上會關閉守護程式。因此,即使在守護程式模式中,也應優先使用 bin/RELEASE_NAME remote

您可以透過設定 RELEASE_TMP 環境變數來自訂用於記錄和在守護程式模式中進行管道傳輸的 tmp 目錄。請參閱「自訂」區段。

服務模式 (Windows)

雖然 Windows 上沒有守護程式,但可以在 erlsrv 的協助下將發佈的系統安裝為 Windows 上的服務。這可以透過執行以下指令來完成

$ bin/RELEASE_NAME install

安裝後,必須透過 erlsrv 可執行檔明確管理服務,該可執行檔包含在 erts-VSN/bin 目錄中。服務不會在安裝後自動啟動。

例如,如果您有一個名為 demo 的版本,您可以安裝服務,然後從版本根目錄啟動服務,如下所示

$ bin/demo install
$ erts-VSN/bin/erlsrv.exe start demo_demo

服務名稱為 demo_demo,因為該名稱是通過將節點名稱與版本名稱連接起來構建的。由於 Elixir 自動對兩者使用相同的名稱,因此服務將被引用為 demo_demo

install 命令必須以管理員身份執行。

bin/RELEASE_NAME 命令

bin/RELEASE_NAME 支援以下命令

start        Starts the system
start_iex    Starts the system with IEx attached
daemon       Starts the system as a daemon (Unix-like only)
daemon_iex   Starts the system as a daemon with IEx attached (Unix-like only)
install      Installs this system as a Windows service (Windows only)
eval "EXPR"  Executes the given expression on a new, non-booted system
rpc "EXPR"   Executes the given expression remotely on the running system
remote       Connects to the running system via a remote shell
restart      Restarts the running system via a remote command
stop         Stops the running system via a remote command
pid          Prints the operating system PID of the running system via a remote command
version      Prints the release name and version to be booted

部署

需求

版本建立在主機上,這是一部包含 Erlang、Elixir 以及編譯應用程式所需的任何其他相依項目的機器。然後將版本部署到目標,目標可能是與主機相同的機器,但通常是分開的,而且通常有很多目標(多個執行個體,或將版本部署到異質環境)。

要在不進行交叉編譯的情況下從主機直接部署到單獨的目標,主機和目標之間必須相同

  • 目標架構(例如,x86_64 或 ARM)
  • 目標供應商 + 作業系統(例如,Windows、Linux 或 Darwin/macOS)
  • 目標 ABI(例如,musl 或 gnu)

這通常以目標三元組的形式表示,例如,x86_64-unknown-linux-gnux86_64-unknown-linux-muslx86_64-apple-darwin

因此,更精確地說,要從主機直接部署到單獨的目標,Erlang 執行時間系統 (ERTS) 和任何原生相依項目 (NIF) 都必須為相同的目標三元組編譯。如果您在 MacBook (x86_64-apple-darwin) 上建置並嘗試部署到典型的 Ubuntu 機器 (x86_64-unknown-linux-gnu),版本將無法運作。您應該在 x86_64-unknown-linux-gnu 主機上建置版本。正如我們將看到的,這可以用多種方式完成,例如在目標本身上發布,或使用虛擬機器或容器,通常作為發布管線的一部分。

除了匹配目標三元組外,目標還必須具備應用程式在執行時所需的所有系統套件。常見的一種情況是在建置使用 :crypto:ssl 的應用程式時需要 OpenSSL,它動態連結到 ERTS。此類原生相依項目的另一個常見來源來自包含 NIF(原生實現函式)的相依項目,這些相依項目可能會動態連結到它們使用的函式庫。

當然,某些作業系統和套件管理員在版本之間可能有所不同,因此如果您的目標是讓主機和目標之間具有完全相容性,最好確保主機和目標上的作業系統和系統套件管理員具有相同的版本。在某些系統中這甚至可能是一個要求,特別是對於嘗試建立完全可重製環境的套件管理員(Nix、Guix)。

類似地,在為 Windows 建立獨立套件和版本時,請注意 Erlang 執行時期系統會依賴某些 Microsoft 函式庫(Visual Studio 2013 的 Visual C++ 可重新散發套件)。這些函式庫會在安裝 Erlang 時安裝(如果之前不存在),但它並非標準 Windows 環境的一部分。在沒有這些函式庫的電腦上部署獨立版本,將會在嘗試執行版本時導致失敗。解決此問題的方法之一是在第一次部署版本時下載並安裝這些 Microsoft 函式庫(Erlang 安裝程式版本 10.6 附帶「Microsoft Visual C++ 2013 可重新散發套件 - 12.0.30501」)。

或者,您也可以將已編譯的物件檔案打包到版本中,只要它們是針對相同的目標編譯的。如果這樣做,您需要使用包含類 Unix 系統上已打包物件的路徑或 Windows 系統上 $PATH 環境變數的 LD_LIBRARY_PATH 環境變數來更新。

目前,由於此過程中涉及的複雜性,沒有官方方法可以將版本從一個目標三元組交叉編譯到另一個目標三元組。

技術

有幾種方法可以保證版本是在與目標具有相同屬性的主機上建置的。一個簡單的選項是擷取原始碼、編譯程式碼並在目標上組裝版本。它會像這樣

$ git clone remote://path/to/my_app.git my_app_source
$ cd my_app_source
$ mix deps.get --only prod
$ MIX_ENV=prod mix release
$ _build/prod/rel/my_app/bin/my_app start

如果您願意,您也可以將版本編譯到一個單獨的目錄,這樣您可以在組裝版本後刪除所有原始碼

$ git clone remote://path/to/my_app.git my_app_source
$ cd my_app_source
$ mix deps.get --only prod
$ MIX_ENV=prod mix release --path ../my_app_release
$ cd ../my_app_release
$ rm -rf ../my_app_source
$ bin/my_app start

但是,如果您有多個生產節點或版本組裝過程很長,這個選項可能會很昂貴,因為每個節點都需要個別組裝版本。

您可以使用幾種不同的方式自動化此過程。一種選擇是將其作為持續整合 (CI) / 持續部署 (CD) 管道的部分。當您有 CI/CD 管道時,您的 CI/CD 管道中的機器通常會在與您的生產伺服器完全相同的目標三元組上執行(如果沒有,則應該如此)。在這種情況下,您可以透過呼叫 MIX_ENV=prod mix release 在 CI/CD 管道的結尾組裝版本,並將成品推送到 S3 或任何其他網路儲存空間。要執行部署,您的生產機器可以從網路儲存空間擷取部署並執行 bin/my_app start

自動化部署的另一種機制是使用映像,例如 Amazon Machine Images,或容器平台,例如 Docker。例如,您可以使用 Docker 在本地執行一個系統,其目標三重與您的生產伺服器完全相同。在容器內,您可以呼叫 MIX_ENV=prod mix release 並建立一個包含作業系統、所有相依套件以及發行的完整映像和/或容器。

換句話說,只要您記得在相同的目標三重中建置系統,就有多種方法可以部署系統,並且可以自動化發行並將其整合到所有這些方法中。

部署系統後,可以透過將 SIGINT/SIGTERM 傳送到系統來關閉系統,這是大多數容器、平台和工具執行的動作,或透過明確呼叫 bin/RELEASE_NAME stop 來關閉。系統收到關閉要求後,每個應用程式及其各自的監督樹將一個一個地停止,順序與其啟動順序相反。

自訂

開發人員有幾種方法可以在發行中自訂產生的成品。

選項

下列選項可以在每個發行定義的 mix.exs 中設定

  • :applications - 一個關鍵字清單,其中應用程式名稱為鍵,其模式為值。預設情況下,:applications 包括目前的應用程式以及目前應用程式所依賴的所有應用程式,遞迴。您可以透過在此處列出新應用程式或變更現有應用程式的模式來包含新應用程式或變更現有應用程式的模式。

    將盡可能保留所提供應用程式的順序,只有 :kernel:stdlib:sasl:elixir 會列在指定的應用程式清單之前。支援的值為

    • :permanent (預設) - 應用程式已啟動,如果應用程式終止,節點會關閉,無論原因為何
    • :transient - 應用程式已啟動,如果應用程式異常終止,節點會關閉
    • :temporary - 應用程式已啟動,如果應用程式終止,節點不會關閉
    • :load - 應用程式僅載入
    • :none - 應用程式是發行的一部分,但未載入或啟動

    如果您變更應用程式的模式,該模式將套用至其所有子應用程式。但是,如果應用程式有兩個父應用程式,則優先順序最高的父應用程式的模式會獲勝(根據上述清單,:permanent 優先順序最高)。

  • :strip_beams - 控制 BEAM 檔案是否應移除其偵錯資訊、文件區塊和其他非必要元資料。預設為 true。可以設定為 false 以停用移除。也接受 [keep: ["Docs", "Dbgi"]] 以保留通常會移除的特定區塊。您也可以將 :compress 選項設定為 true 以啟用 BEAM 檔案的個別壓縮,儘管通常建議壓縮整個發行。

  • :cookie - 代表 Erlang Distribution cookie 的字串。如果未設定此選項,則在組建第一個發行時,會將隨機 cookie 寫入 releases/COOKIE 檔案。在執行階段,我們將首先嘗試從 RELEASE_COOKIE 環境變數中擷取 cookie,然後我們將讀取 releases/COOKIE 檔案。

    如果您手動設定這個選項,我們建議 cookie 選項使用長且隨機產生的字串,例如:Base.url_encode64(:crypto.strong_rand_bytes(40))。我們也建議將 cookie 中的字元限制為 Base.url_encode64/1 回傳的子集。

  • :validate_compile_env - 預設情況下,發行版會將所有執行時期設定與在應用程式或其相依項編譯時透過 Application.compile_env/3 函式標記的任何設定進行比對。如果兩者不符,表示您的系統設定錯誤,無法啟動。您可以將此選項設為 false 來停用此檢查。

  • :path - 發行版應安裝到的路徑。預設為 "_build/MIX_ENV/rel/RELEASE_NAME"

  • :version - 發行版版本,為字串或 {:from_app, app_name}。預設為目前的應用程式版本。 {:from_app, app_name} 格式可用於輕鬆從其他應用程式參照應用程式版本。這在 umbrella 應用程式中特別有用。

  • :quiet - 布林值,控制發行版是否應將步驟寫入標準輸出。預設為 false

  • :include_erts - 布林值、字串或零元匿名函式。如果為布林值,則表示是否應將包含 Erlang VM 的 Erlang 執行時期系統 (ERTS) 包含在發行版中。預設為 true,這也是建議值。如果為字串,則表示現有 ERTS 安裝的路徑。如果為零元匿名函式,則為回傳上述任何一種 (布林值或字串) 的函式。

    您也可以將此選項設為 false,如果您希望使用目標上安裝的 ERTS 版本。不過,請注意,目標上的 ERTS 版本必須與組建發行版時使用的 ERTS 版本完全相同。將其設為 false 也會停用熱程式碼升級。因此,:include_erts 應謹慎設為 false,而且僅在您在執行它的伺服器上組建發行版時才這麼做。

  • :include_executables_for - 列出應為哪些作業系統產生可執行檔的原子清單。預設設為 [:unix, :windows]。您可以如下自訂這些設定

    releases: [
      demo: [
        include_executables_for: [:unix] # Or [:windows] or []
      ]
    ]
  • :rel_templates_path - 複製到發行版本的範本檔案路徑,例如「vm.args.eex」、「remote.vm.args.eex」、「env.sh.eex」(或「env.bat.eex」)和「overlays」。預設為專案根目錄中的「rel」。

  • :overlays - 額外檔案的目錄清單,將原樣複製到發行版本中。預設情況下,:rel_templates_path 中的「overlays」目錄始終包含在此清單中(通常位於「rel/overlays」)。有關更多資訊,請參閱「Overlays」區段。

  • :steps - 組裝發行版本時要執行的步驟清單。有關更多資訊,請參閱「步驟」區段。

  • :skip_mode_validation_for - 指定要略過「不安全」模式的嚴格驗證的應用程式名稱(原子)清單。「不安全」情況是指父應用程式模式為 :permanent,但其依賴的其中一個應用程式設定為 :load。請小心使用此功能,因為模式無效的發行版本可能無法在沒有其他調整的情況下啟動。預設為 []

請注意,每個發行版本定義都可以指定為匿名函式。如果某些發行版本屬性難以計算,這會很有用

releases: [
  demo: fn ->
    [version: @version <> "+" <> git_ref()]
  end
]

除了上述選項之外,還可以透過調整發行版本步驟或在啟動時執行自訂選項和指令,自訂產生的發行版本。我們將在接下來詳細說明這兩種方法。

Overlays

通常需要在組裝發行版本後將額外檔案複製到發行版本根目錄。這可以透過將這些檔案放置在 rel/overlays 目錄中來輕鬆完成。其中的任何檔案都會原樣複製到發行版本根目錄。例如,如果您放置了「rel/overlays/Dockerfile」檔案,「Dockerfile」將原樣複製到發行版本根目錄。

如果您想要指定額外的 overlay 目錄,可以使用 :overlays 選項來執行此動作。如果您需要動態複製檔案,請參閱「步驟」區段。

步驟

可以在組裝發行版本之前和之後新增一個或多個步驟。這可以使用 :steps 選項來完成

releases: [
  demo: [
    steps: [&set_configs/1, :assemble, &copy_extra_files/1]
  ]
]

:steps 選項必須是清單,且必須始終包含原子 :assemble,它執行大部分的發行組建。您可以在 :assemble 之前和之後傳遞匿名函式,以自訂您的發行組建管道。這些匿名函式將接收 Mix.Release 結構,且必須傳回相同或已更新的 Mix.Release 結構。也可以在 :assemble 之後任何地方傳遞 :tar 步驟,以建置發行的 tarball。如果發行 :path 未設定,tarball 會建立在 _build/MIX_ENV/RELEASE_NAME-RELEASE_VSN.tar.gz 中。否則,會建立在已設定的 :path 中。

請參閱 Mix.Release,以取得結構和可修改欄位的更多文件。請注意,:steps 欄位本身可以修改,且每次呼叫步驟時都會更新。因此,如果您需要在組建發行之前和之後執行命令,您只需要在管道中宣告第一個步驟,然後將最後一個步驟注入發行結構。步驟欄位也可以用於驗證步驟是在組建發行之前還是之後設定的。

vm.args 和 env.sh (env.bat)

開發人員可能想要自訂發行啟動時提供的 VM 旗標和環境變數。自訂這些檔案最簡單的方法是執行 mix release.init。Mix 任務會將自訂的 rel/vm.args.eexrel/remote.vm.args.eexrel/env.sh.eexrel/env.bat.eex 檔案複製到您的專案根目錄。您可以修改這些檔案,且每次執行新的發行時都會評估它們。這些檔案是一般的 EEx 範本,且它們有一個稱為 @release 的單一指定,其中包含 Mix.Release 結構。

vm.argsremote.vm.args 檔案可能包含 erl 命令 接受的任何 VM 旗標。

env.shenv.bat 用於設定環境變數。在其中,您可以設定變數,例如 RELEASE_NODERELEASE_COOKIERELEASE_TMP,以分別自訂您的節點名稱、Cookie 和 tmp 目錄。每當呼叫 env.shenv.bat 時,變數 RELEASE_ROOTRELEASE_NAMERELEASE_VSNRELEASE_COMMAND 已設定,因此您可以依賴它們。請參閱環境變數部分,以取得更多資訊。

此外,雖然 vm.args 檔案是靜態的,但您可以使用 env.shenv.bat 動態設定 VM 選項。例如,如果您想要確保 Erlang Distribution 僅在執行時已知的特定埠上偵聽,您可以設定下列內容

case $RELEASE_COMMAND in
  start*|daemon*)
    ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min $BEAM_PORT inet_dist_listen_max $BEAM_PORT"
    export ELIXIR_ERL_OPTIONS
    ;;
  *)
    ;;
esac

請注意,我們僅在啟動/守護程式指令上設定埠。如果您也限制其他指令(例如 rpc)上的埠,則您將無法建立遠端連線,因為埠已被節點使用。

在 Windows 上,您的 env.bat 會如下所示

IF NOT %RELEASE_COMMAND:start=%==%RELEASE_COMMAND% (
  set ELIXIR_ERL_OPTIONS="-kernel inet_dist_listen_min %BEAM_PORT% inet_dist_listen_max %BEAM_PORT%"
)

env.shenv.bat 檔案內,您可以存取給予發佈指令的命令列引數。例如,給定此 env.sh.eex

echo $@

或此 env.bat.eex

echo %*

使用 bin/myapp start --foo bar baz 啟動發佈會列印 start --foo bar baz

應用程式組態

Mix 提供兩種機制來組態應用程式的應用程式環境和相依關係:建置時間和執行時間。在這個區段,我們將學習這些機制如何套用於發佈。可以在 Mix 模組的「組態」區段中找到此主題的簡介。

建置時間組態

每當您呼叫 mix 指令時,Mix 會載入 config/config.exs 中的組態(如果該檔案存在)。我們稱此組態為建置時間組態,因為它會在您編譯程式碼或組裝發佈時進行評估。

換句話說,如果您的組態執行類似下列動作

import Config
config :my_app, :secret_key, System.fetch_env!("MY_APP_SECRET_KEY")

:my_app 下的 :secret_key 鍵會在建置發佈時在主機上計算。因此,如果組裝發佈的機器無法存取用於執行程式碼的所有環境變數,則會因為環境變數遺失而導致載入組態失敗。幸運的是,Mix 也提供執行時間組態,這應該是優先選項,我們將在接下來看到。

執行階段組態

若要於您的版本中啟用執行階段組態,您只需要建立一個名為 config/runtime.exs 的檔案

import Config
config :my_app, :secret_key, System.fetch_env!("MY_APP_SECRET_KEY")

此檔案將於您的 Mix 專案或版本啟動時執行。

您的 config/runtime.exs 檔案需要遵循三個重要的規則

  • 它必須在頂端 import Config,而非已棄用的 use Mix.Config
  • 它不得透過 import_config 匯入任何其他組態檔案
  • 它不得以任何方式存取 Mix,因為 Mix 是建置工具,且於版本中不可用

如果存在 config/runtime.exs,它將複製到您的版本中,並於開機程序的早期執行,僅於 Elixir 與 Erlang 的主要應用程式已啟動時執行。

您可以透過設定每個版本組態中的 :runtime_config_path 來變更執行階段組態檔案的路徑。此路徑會於建置時間解析,因為指定的組態檔案總是複製到版本內部

releases: [
  demo: [
    runtime_config_path: ...
  ]
]

透過將 :runtime_config_path 設定為 false,可用於防止執行階段組態檔案包含在版本中。

組態提供者

版本也支援自訂機制,稱為組態提供者,用於於系統開機時載入任何類型的執行階段組態。例如,如果您需要存取保險庫或從 JSON 檔案載入組態,可以使用組態提供者來達成。前一節概述的執行階段組態由 Config.Reader 提供者處理。請參閱 Config.Provider 模組以取得更多資訊和範例。

下列選項可以設定在您的 mix.exs 中版本金鑰的內部,以控制組態提供者如何運作

  • :reboot_system_after_config - 於組態後重新開機系統,以便您可以在 config/runtime.exs 中組態系統應用程式,例如 :kernel:stdlib。一般來說,最好使用 vm.args 檔案來組態 :kernel:stdlib,但此選項可供需要更複雜組態的人使用。設定為 true 時,版本將首先以互動模式開機,以計算組態檔案並將其寫入「tmp」目錄。然後,它會在已組態的 RELEASE_MODE 中重新開機。您可以透過設定 RELEASE_TMP 環境變數來組態「tmp」目錄,可以明確設定或在您的 releases/RELEASE_VSN/env.sh(或 Windows 上的 env.bat)中設定。如果使用已棄用的 config/releases.exs,預設為 true,否則為 false

  • :prune_runtime_sys_config_after_boot - 如果設定 :reboot_system_after_config,每次系統開機時,發行版會將組態檔寫入暫存目錄。這些組態檔通常很小。但如果你擔心磁碟空間或有其他限制,你可以要求系統在開機後移除這些組態檔。缺點是你將無法再在內部重新啟動系統(透過 System.restart/0bin/RELEASE_NAME restart 都無法)。如果你需要重新啟動,你必須終止作業系統程序並啟動新的程序。預設為 false

  • :start_distribution_during_config - 如果設定 :reboot_system_after_config,發行版只在評估組態檔後才啟動 Erlang VM 發行版功能。如果你需要在組態期間進行發行,你可以將其設定為 true。預設為 false

  • :config_providers - 一個包含自訂組態提供者的元組清單。更多資訊請參閱 Config.Provider。預設為 []

自訂和組態摘要

一般來說,以下檔案可用於自訂和組態正在執行的系統

  • config/config.exs(和 config/prod.exs) - 提供組建時間的應用程式組態,這些組態會在組裝發行版時執行

  • config/runtime.exs - 提供執行時間的應用程式組態。它會在每次 Mix 專案或發行版開機時執行,並可透過組態提供者進一步擴充。如果你想偵測自己是否在發行版內,你可以檢查發行版特定的環境變數,例如 RELEASE_NODERELEASE_MODE

  • rel/vm.args.eexrel/remote.vm.args.eex - 範本檔案,會複製到每個版本中,並提供 Erlang 虛擬機器和其他執行時期旗標的靜態組態。vm.args 會在 startdaemoneval 指令中執行。remote.vm.args 會為 remoterpc 指令組態 VM

  • rel/env.sh.eexrel/env.bat.eex - 範本檔案,會複製到每個版本中,並在每個指令中執行,以設定環境變數,包括 VM 和一般環境的特定變數

目錄結構

版本組織方式如下

bin/
  RELEASE_NAME
erts-ERTS_VSN/
lib/
  APP_NAME-APP_VSN/
    ebin/
    include/
    priv/
releases/
  RELEASE_VSN/
    consolidated/
    elixir
    elixir.bat
    env.bat
    env.sh
    iex
    iex.bat
    remote.vm.args
    runtime.exs
    start.boot
    start.script
    start_clean.boot
    start_clean.script
    sys.config
    vm.args
  COOKIE
  start_erl.data
tmp/

我們記錄這個結構以求完整性。實際上,開發人員在組裝版本後不應修改任何這些檔案。請改用 env 腳本、自訂組態提供者、疊加和本指南中描述的所有其他機制,來組態版本的運作方式。

環境變數

系統會設定不同的環境變數。下列變數會在早期設定,且只能由 env.shenv.bat 讀取

  • RELEASE_ROOT - 指向版本的根目錄。如果系統包含 ERTS,則與 :code.root_dir/0 相同。這個變數總是會計算,且無法設定為自訂值

  • RELEASE_COMMAND - 傳遞給版本的指令,例如 "start""remote""eval" 等。這通常會在 env.shenv.bat 中存取,以在不同條件下設定不同的環境變數。不過,請注意,在呼叫 env.shenv.bat 時,尚未驗證 RELEASE_COMMAND,因此它可能是空的或包含無效值。這個變數總是會計算,且無法設定為自訂值

  • RELEASE_NAME - 版本的名稱。呼叫版本時,可以設定為自訂值

  • RELEASE_VSN - 發行版本,否則使用最新版本。在呼叫發行時可以設定為自訂值。自訂值必須是 releases/ 目錄中現有的發行版本

  • RELEASE_PROG - 用於啟動發行的命令列可執行檔

可以在呼叫發行或在 env.shenv.bat 中設定下列變數

  • RELEASE_COOKIE - 發行 Cookie。預設使用 releases/COOKIE 中的值。可以設定為自訂值

  • RELEASE_NODE - 發行節點名稱,格式為 name 或在分散式模式下為 name@host(選用)。可以設定為自訂值。名稱部分只能包含字母、數字、底線和連字號

  • RELEASE_SYS_CONFIG - sys.config 檔案的位置。可以設定為自訂路徑,且不得包含 .config 副檔名

  • RELEASE_VM_ARGS - vm.args 檔案的位置。可以設定為自訂路徑

  • RELEASE_REMOTE_VM_ARGS - remote.vm.args 檔案的位置。可以設定為自訂路徑

  • RELEASE_TMP - 發行中用於寫入暫時檔案的目錄。可以設定為自訂目錄。預設為 $RELEASE_ROOT/tmp

  • RELEASE_MODE - 發行是否應依需求載入程式碼(互動式)或預先載入(嵌入式)。預設為「嵌入式」,這會增加開機時間,但表示執行時間會回應得更快,因為它不必載入程式碼。如果您需要減少開機時間並減少開機時的記憶體使用量,請選擇互動式。它僅適用於 start/daemon/install 命令

  • RELEASE_DISTRIBUTION - 我們希望如何執行發行。可能是 name(長名稱)、sname(短名稱)或 none(不會自動啟動發行)。預設為 sname,這僅允許在目前的系統中存取。 name 允許外部連線

  • RELEASE_BOOT_SCRIPT - 啟動發行時要使用的開機腳本名稱。在執行 startdaemon 等命令時會使用此腳本。開機腳本預期位於 releases/RELEASE_VSN/RELEASE_BOOT_SCRIPT.boot 路徑。預設為 start

  • RELEASE_BOOT_SCRIPT_CLEAN - 發布乾淨啟動時使用的啟動腳本名稱,不含您的應用程式或其相依性。此腳本由 evalrpcremote 等指令使用。預期啟動腳本會位於 releases/RELEASE_VSN/RELEASE_BOOT_SCRIPT_CLEAN.boot 路徑。預設為 start_clean

Umbrella

發布與 Umbrella 專案整合良好,讓您可以發布 Umbrella 子專案的一個或多個子集。在 Umbrella 專案中執行發布與一般應用程式之間唯一的差異,在於 Umbrella 需要您明確列出您的發布和每個發布的起點。例如,想像這個 Umbrella 應用程式

my_app_umbrella/
  apps/
    my_app_core/
    my_app_event_processing/
    my_app_web/

其中 my_app_event_processingmy_app_web 都依賴 my_app_core,但它們彼此不依賴。

在您的 Umbrella 中,您可以定義多個發布

releases: [
  web_and_event_processing: [
    applications: [
      my_app_event_processing: :permanent,
      my_app_web: :permanent
    ]
  ],

  web_only: [
    applications: [my_app_web: :permanent]
  ],

  event_processing_only: [
    applications: [my_app_event_processing: :permanent]
  ]
]

請注意,您不需要在 :applications 中定義所有應用程式,只要定義進入點即可。另外請記住,系統中所有應用程式的建議模式為 :permanent

最後,請記住,您不需要從 Umbrella 根目錄組裝發布。您也可以個別從每個子應用程式組裝發布。然而,從根目錄執行此操作,讓您可以將兩個彼此不依賴的應用程式包含在同一個發布中。

熱程式碼升級

Erlang 和 Elixir 有時以能夠升級正在生產環境中執行的節點而聞名,且不關閉該節點。然而,此功能並未由 Elixir 發布預設支援。

我們不提供熱程式碼升級的原因,是因為在實務上執行它們非常複雜,因為它們需要仔細編寫您的處理程序和應用程式,以及廣泛的測試。由於大多數團隊可以使用其他與語言無關的技術來升級其系統,例如藍綠部署、金絲雀部署、滾動部署等,因此熱升級很少是可行的選項。讓我們了解原因。

在熱代碼升級中,您想將節點從版本 A 更新到版本 B。為此,第一步是為在兩個版本之間變更的每個應用程式撰寫食譜,明確說明應用程式在版本之間的變更方式,這些食譜稱為 .appup 檔案。雖然建立 .appup 檔案的某些步驟可以自動化,但並非所有步驟都可以自動化。此外,應用程式中的每個程序都需要明確編寫熱代碼升級的程式碼。讓我們看一個範例。假設您的應用程式有一個計數器程序作為 GenServer

defmodule Counter do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def bump do
    GenServer.call(__MODULE__, :bump)
  end

  ## Callbacks

  def init(:ok) do
    {:ok, 0}
  end

  def handle_call(:bump, counter) do
    {:reply, :ok, counter + 1}
  end
end

您將此程序新增為監督樹的一部分,並發佈系統的版本 0.1.0。現在讓我們假設在版本 0.2.0 中,您新增了兩個變更:取代總是將計數器增加一的 bump/0,您引入了傳遞確切值來增加計數器的 bump/1。您也變更了狀態,因為您想儲存最大增加值

defmodule Counter do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def bump(by) do
    GenServer.call(__MODULE__, {:bump, by})
  end

  ## Callbacks

  def init(:ok) do
    {:ok, {0, 0}}
  end

  def handle_call({:bump, by}, {counter, max}) do
    {:reply, :ok, {counter + by, max(max, by)}}
  end
end

如果您要在這樣的應用程式中執行熱代碼升級,它將會崩潰,因為在初始版本中,狀態只是一個計數器,但在新版本中,狀態是一個元組。此外,您將 call 訊息的格式從 :bump 變更為 {:bump, by},而且程序可能暫時同時混用舊訊息和新訊息,因此我們需要處理兩者。最終版本將會是

defmodule Counter do
  use GenServer

  def start_link(_) do
    GenServer.start_link(__MODULE__, :ok, name: __MODULE__)
  end

  def bump(by) do
    GenServer.call(__MODULE__, {:bump, by})
  end

  ## Callbacks

  def init(:ok) do
    {:ok, {0, 0}}
  end

  def handle_call(:bump, {counter, max}) do
    {:reply, :ok, {counter + 1, max(max, 1)}}
  end

  def handle_call({:bump, by}, {counter, max}) do
    {:reply, :ok, {counter + by, max(max, by)}}
  end

  def code_change(_, counter, _) do
    {:ok, {counter, 0}}
  end
end

現在,您可以繼續在 .appup 檔案中列出此程序,並執行熱代碼升級。這是執行熱代碼升級必要的許多步驟之一,而且必須考慮到系統中要升級的每個程序和應用程式。.appup 食譜 提供了良好的參考和更多範例。

建立 .appup 之後,下一步是建立一個 .relup 檔案,其中包含更新發行版本身所需的所有指令。Erlang 文件提供了一個關於 建立和升級目標系統 的章節。Learn You Some Erlang 有關於熱代碼升級的章節

總體而言,在熱代碼升級期間會進行許多步驟、複雜性和假設,這最終是 Elixir 未提供熱代碼升級的原因。然而,熱代碼升級仍然可以由希望在專案中或作為獨立函式庫實作這些步驟的團隊來達成。mix release

命令列選項

  • --force - 強制重新編譯
  • --no-archives-check - 不檢查封存檔
  • --no-deps-check - 不檢查依賴項
  • --no-elixir-version-check - 不檢查 Elixir 版本
  • --no-compile - 組合發行版前不編譯
  • --overwrite - 覆寫現有檔案,而不是提示使用者採取行動
  • --path - 發行版的路徑
  • --quiet - 不將進度寫入標準輸出
  • --version - 發行版的版本