Do you want to pick up from where you left of?
Take me there

Distillery(基礎)

Distillery 是純粹使用 Elixir 編寫的發布版本管理工具。它可以生成幾乎不需要配置就可以部署到其他地方的發布版本。

什麼是一個發布版本?

一個發布版本是一個包含已編譯的 Erlang/Elixir 程式碼套件(例如 BEAM 位元組碼)。它還提供了啟動應用程式所需的所有腳本。

編寫一個或多個應用程式後,可能會希望使用這些應用程式與 Erlang/OTP 應用程式子集來建立一個完整系統。這稱為一個發布版本。ー Erlang 文件

發布版本可以簡化部署:它們是獨立(self-contained)的,並提供啟動套件所需的一切;可通過其提供的殼層腳本打開在背景啟動的遠端控制台來輕鬆管理,像是啟動/停止/重新啟動套件或是發送遠端指令等。此外,它們是可封存的加工品,這表示可以在將來的任何時候從其壓縮檔中還原舊發布版本(除非與基礎 OS 或系統函式庫不相容)使用發布版本也是執行熱升級和熱降級的前置作業,而熱升級和熱降級是 Erlang VM 最強大的功能之一。 ー Distillery 文件

一個發布版本將包含以下內容:

入門/安裝

要將 Distillery 加入到專案中,請將其作為相依性加入到 mix.exs 中。 ㄧ 如果是在保護傘程式上作業,則應該新增到專案根目錄裡的 mix.exs

defp deps do
  [{:distillery, "~> 2.0"}]
end

接著在終端機中呼用:

mix deps.get
mix compile

建立你的發布版本

在終端機中,執行

mix release.init

這個指令會產生一個帶有一些配置檔案的 rel 目錄。

要在終端機中產生一個發布版本請執行 mix release

一旦建立發布版本後,應該會在終端機中看到一些說明

==> Assembling release..
==> Building release book_app:0.1.0 using environment dev
==> You have set dev_mode to true, skipping archival phase
Release successfully built!
To start the release you have built, you can use one of the following tasks:

    # start a shell, like 'iex -S mix'
    > _build/dev/rel/book_app/bin/book_app console

    # start in the foreground, like 'mix run --no-halt'
    > _build/dev/rel/book_app/bin/book_app foreground

    # start in the background, must be stopped with the 'stop' command
    > _build/dev/rel/book_app/bin/book_app start

If you started a release elsewhere, and wish to connect to it:

    # connects a local shell to the running node
    > _build/dev/rel/book_app/bin/book_app remote_console

    # connects directly to the running node's console
    > _build/dev/rel/book_app/bin/book_app attach

For a complete listing of commands and their use:

    > _build/dev/rel/book_app/bin/book_app help

要執行應用程式,請在終端機中輸入以下內容 _build/dev/rel/MYAPP/bin/MYAPP foreground 在你的情況用你的專案名稱替換 MYAPP。現在是正在執行應用程式的發布版本!

在 Phoenix 中使用 Distillery

如果你是 Distillery 與 Phoenix 配合著使用,會需要執行一些額外的步驟,然後才能起作用。

首先需要編輯 config/prod.exs 檔案。

修改以下行數,從:

config :book_app, BookAppWeb.Endpoint,
  load_from_system_env: true,
  url: [host: "example.com", port: 80],
  cache_static_manifest: "priv/static/cache_manifest.json"

改為:

config :book_app, BookApp.Endpoint,
  http: [port: {:system, "PORT"}],
  url: [host: "localhost", port: {:system, "PORT"}],
  cache_static_manifest: "priv/static/cache_manifest.json",
  server: true,
  root: ".",
  version: Application.spec(:book_app, :vsn)

此時己經完成的事項是:

如果執行上述指令,可能會發現應用程試當機了,因為當前不存在資料庫而無法與其連接。這可以通過執行 Ecto mix 指令來修正。在終端機中,輸入以下內容:

MIX_ENV=prod mix ecto.create

這個指令會建立資料庫。現在嘗試重新執行應用程式,它會成功啟動。但是,你同時會注意到,資料庫遷移尚未執行。通常在開發時,會通過呼用 mix.ecto migrate 來手動執行這些遷移。但對於發布版本,必須對其進行配置,以便它可以獨立執行遷移。

在正式環境執行遷移

Distillery 能夠在發布版本生命週期的不同時間點之間執行程式碼。這些點稱為 boot-hooks。 Distillery 提供的鉤子包括

因著目的,將使用 post_start 鉤子在正式環境中執行應用程式遷移。首先,建立一個名為 migrate 的新發布版本工作。發布版本工作是一個模組函數,可以從終端機上呼用它,其中包含與應用程式內部運作分離的程式碼。對於特別是應用程式本身不需要執行的工作而言,這很有用。

defmodule BookAppWeb.ReleaseTasks do
  def migrate do
    {:ok, _} = Application.ensure_all_started(:book_app)

    path = Application.app_dir(:book_app, "priv/repo/migrations")

    Ecto.Migrator.run(BookApp.Repo, path, :up, all: true)
  end
end

ー 好的做法是在執行這些遷移之前,確保所有應用程式都已正確啟動。 Ecto.Migrator 允許對已連接的資料庫執行遷移。

接下來,建立一個新檔案 - rel/hooks/post_start/migrate.sh 並加入以下程式碼:

echo "Running migrations"

bin/book_app rpc "Elixir.BookApp.ReleaseTasks.migrate"

為了使這些程式碼正常執行,使用了 Erlang 的 rpc 模組,該模組允許使用遠端程式呼叫 (Remote Produce Call )服務。基本上,這可以在遠端節點上呼用函數並獲得應答。當在正式環境中執行時,應用程式可能會執行在多個不同的節點上。

最後,在 rel/config.exs 檔案中,將鉤子加入到正式環境的配置上。

現在將以下

environment :prod do
  set include_erts: true
  set include_src: false
  set cookie: :"TkJuF,3nc4)OWPBpPxPDb6mz$>)>a>/v/,l2}W*sUFaz<)bG,v*3pPESE,`XOk{,"
  set vm_args: "rel/vm.args"
end

取代為

environment :prod do
  set include_erts: true
  set include_src: false
  set cookie: :"TkJuF,3nc4)OWPBpPxPDb6mz$>)>a>/v/,l2}W*sUFaz<)bG,v*3pPESE,`XOk{,"
  set vm_args: "rel/vm.args"
  set post_start_hooks: "rel/hooks/post_start"
end

- 此鉤子僅存在於此應用程式的正式發布版本中。如果使用預設的開發環境發布版本,它將無法執行。

自訂指令

在使用發布版本時,有可能無法使用 mix 指令,因為 mix 可能未安裝到部署該發布版本的機器上。不過可以通過建立自訂指令來解決此問題。

自訂指令是啟動腳本的擴充,並且與在前景或 remote_console 的相同方式來使用,換句話說,它們看起來像是啟動腳本的一部分。像鉤子一樣,它們可以存取啟動腳本的輔助函數和環境 - Distillery 文件

其指令與發布版本的工作相似,因為它們都是做為方法的函數,但與指令不同處為,它們是通過終端機,而不是發布版本的腳本來執行。

現在能夠執行遷移了,或許也希望可以通過執行指令為資料庫播種初始資訊。因此,首先為發布版本的工作加入一個新方法。在 BookAppWeb.ReleaseTasks 中,加入以下內容

def seed do
  seed_path = Application.app_dir(:book_app_web, "priv/repo/seeds.exs")
  Code.eval_file(seed_path)
end

接著,建立一個新檔案 rel/commands/seed.sh 並加入以下程式碼:

#!/bin/sh

release_ctl eval "BookAppWeb.ReleaseTasks.seed/0"

- release_ctl() 是 Distillery 提供的殼層腳本,它允許在本機或乾淨的節點中執行指令。如果需要在正在執行的節點上使用此指令,則可以執行 release_remote_ctl()

這裡查看更多有關 Distillery 的 shell_scripts 資訊。

最後,將以下內容加入到 rel/config.exs 檔案中。

release :book_app do
  ...
  set commands: [
    seed: "rel/commands/seed.sh"
  ]
end

要確定是通過執行 MIX_ENV=prod mix release 來重新建立發布版本。一旦完成此動作後,就可以在終端機中執行 PORT=4001 _build/prod/rel/book_app/bin/book_app seed

Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!