檢視來源 透過版本部署
需要用到的工具
在本指南中,我們唯一需要的是一個正常運作的 Phoenix 應用程式。對於需要一個簡單應用程式來部署的人,請依照啟動和執行指南進行操作。
目標
在本指南中的主要目標是將您的 Phoenix 應用程式封裝成一個獨立目錄,其中包括 Erlang VM、Elixir,以及您的所有程式碼和依賴項。之後可以將此封裝放置到生產機器中。
組裝版本
如果您還不熟悉 Elixir 版本,我們建議您在繼續之前閱讀Elixir 的優秀文件。
完成後,您可以按照我們的部署指南中所有步驟進行操作,最後執行mix release
組合版本。讓我們來複習一下。
首先設定環境變數
$ mix phx.gen.secret
REALLY_LONG_SECRET
$ export SECRET_KEY_BASE=REALLY_LONG_SECRET
$ export DATABASE_URL=ecto://USER:PASS@HOST/database
接著載入依賴項以編譯程式碼和資產
# Initial setup
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
# Compile assets
$ MIX_ENV=prod mix assets.deploy
$ mix phx.gen.release
==> my_app
* creating rel/overlays/bin/server
* creating rel/overlays/bin/server.bat
* creating rel/overlays/bin/migrate
* creating rel/overlays/bin/migrate.bat
* creating lib/my_app/release.ex
Your application is ready to be deployed in a release!
# To start your system
_build/dev/rel/my_app/bin/my_app start
# To start your system with the Phoenix server running
_build/dev/rel/my_app/bin/server
# To run migrations
_build/dev/rel/my_app/bin/migrate
Once the release is running:
# To connect to it remotely
_build/dev/rel/my_app/bin/my_app remote
# To stop it gracefully (you may also send SIGINT/SIGTERM)
_build/dev/rel/my_app/bin/my_app stop
To list all commands:
_build/dev/rel/my_app/bin/my_app
phx.gen.release
任務生成了幾個檔案來協助我們進行版本發布。首先,它產生了 server
和 migrate
overlay 腳本,用於在版本內方便地執行 phoenix 伺服器或執行版本中的遷移。 rel/overlays
目錄中的檔案會複製到每個版本環境中。接著,它生成了 release.ex
檔案,用於在不依賴 mix
本身的情況下呼叫 Ecto 遷移。
注意:如果您是 Docker 使用者,您可以將 --docker
旗標傳遞給 mix phx.gen.release
,以產生已準備好部署的 Dockerfile。
接著,我們可以呼叫 mix release
來建構版本
$ MIX_ENV=prod mix release
Generated my_app app
* assembling my_app-0.1.0 on MIX_ENV=prod
* using config/runtime.exs to configure the release at runtime
Release created at _build/prod/rel/my_app!
# To start your system
_build/prod/rel/my_app/bin/my_app start
...
您可以呼叫 _build/prod/rel/my_app/bin/my_app start
來啟動版本,或呼叫 _build/prod/rel/my_app/bin/server
來啟動您的網站伺服器,您必須將 my_app
替換為您目前的應用程式名稱。
現在,您可以取得 _build/prod/rel/my_app
下的所有檔案,將其封裝,並在任何與組建版本相同的作業系統和架構的生產機器上執行。有關更多詳細資訊,請查看 mix release
的文件 。
但在我們完成本指南之前,還有版本中的另一個功能供大多數 Phoenix 應用程式使用,因此讓我們來討論一下。
Ecto 移轉和自訂指令
在生產系統中,一個常見的需求是執行設定生產環境所需的自訂指令。其中一個指令正是進行資料庫移轉。由於我們沒有 Mix
,這個建置工具,可以在發行版中,這是一個生產製品,所以我們需要將指令直接帶入發行版。
phx.gen.release
指令在專案的 lib/my_app/release.ex
中建立了以下 release.ex
檔案,其內容如下
defmodule MyApp.Release do
@app :my_app
def migrate do
load_app()
for repo <- repos() do
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :up, all: true))
end
end
def rollback(repo, version) do
load_app()
{:ok, _, _} = Ecto.Migrator.with_repo(repo, &Ecto.Migrator.run(&1, :down, to: version))
end
defp repos do
Application.fetch_env!(@app, :ecto_repos)
end
defp load_app do
Application.load(@app)
end
end
其中您可以用應用程式名稱取代前兩行。
現在,您可以使用 MIX_ENV=prod mix release
組建一個新的發行版,並且您可以透過呼叫 eval
指令來呼叫任何程式碼,包括上述模組中的函式
$ _build/prod/rel/my_app/bin/my_app eval "MyApp.Release.migrate"
就是這樣!如果您在 migrate
指令碼中查看,您將會看到它完全包含這個呼叫。
您可以使用這個方法建立任何在生產中執行的自訂指令。這個狀況下,我們使用 load_app
,它呼叫 Application.load/1
以載入目前的應用程式,但並不會啟動它。不過,您可能想要撰寫一條自訂指令,以啟動整個應用程式。這種情況下,必須使用 Application.ensure_all_started/1
。請記住,啟動應用程式將會啟動目前的應用程式的所有程序,包括 Phoenix 端點。這能透過改變您的監督樹來規避,以便在特定情況下不啟動特定子行程。例如,您可以在發行版本指令碼檔案中進行以下操作
defp start_app do
load_app()
Application.put_env(@app, :minimal, true)
Application.ensure_all_started(@app)
end
然後在您的應用程式中檢查 Application.get_env(@app, :minimal)
,並在設定時僅啟動部分子行程。
容器
Elixir 發行版適用於容器技術,例如 Docker。其意念是您在 Docker 容器中組建發行版,然後根據發行版製品建置一個映像。
如果您呼叫 mix phx.gen.release --docker
您會看到一個新檔案並包含這些內容
# Find eligible builder and runner images on Docker Hub. We use Ubuntu/Debian
# instead of Alpine to avoid DNS resolution issues in production.
#
# https://hub.docker.com/r/hexpm/elixir/tags?page=1&name=ubuntu
# https://hub.docker.com/_/ubuntu?tab=tags
#
# This file is based on these images:
#
# - https://hub.docker.com/r/hexpm/elixir/tags - for the build image
# - https://hub.docker.com/_/debian?tab=tags&page=1&name=bullseye-20230612-slim - for the release image
# - https://pkgs.org/ - resource for finding needed packages
# - Ex: hexpm/elixir:1.14.5-erlang-25.3.2.4-debian-bullseye-20230612-slim
#
ARG ELIXIR_VERSION=1.14.5
ARG OTP_VERSION=25.3.2.4
ARG DEBIAN_VERSION=bullseye-20230612-slim
ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"
FROM ${BUILDER_IMAGE} as builder
# install build dependencies
RUN apt-get update -y && apt-get install -y build-essential git \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
# prepare build dir
WORKDIR /app
# install hex + rebar
RUN mix local.hex --force && \
mix local.rebar --force
# set build ENV
ENV MIX_ENV="prod"
# install mix dependencies
COPY mix.exs mix.lock ./
RUN mix deps.get --only $MIX_ENV
RUN mkdir config
# copy compile-time config files before we compile dependencies
# to ensure any relevant config change will trigger the dependencies
# to be re-compiled.
COPY config/config.exs config/${MIX_ENV}.exs config/
RUN mix deps.compile
COPY priv priv
COPY lib lib
COPY assets assets
# compile assets
RUN mix assets.deploy
# Compile the release
RUN mix compile
# Changes to config/runtime.exs don't require recompiling the code
COPY config/runtime.exs config/
COPY rel rel
RUN mix release
# start a new build stage so that the final image will only contain
# the compiled release and other runtime necessities
FROM ${RUNNER_IMAGE}
RUN apt-get update -y && \
apt-get install -y libstdc++6 openssl libncurses5 locales ca-certificates \
&& apt-get clean && rm -f /var/lib/apt/lists/*_*
# Set the locale
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8
WORKDIR "/app"
RUN chown nobody /app
# set runner ENV
ENV MIX_ENV="prod"
# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/my_app ./
USER nobody
# If using an environment that doesn't automatically reap zombie processes, it is
# advised to add an init process such as tini via `apt-get install`
# above and adding an entrypoint. See https://github.com/krallin/tini for details
# ENTRYPOINT ["/tini", "--"]
CMD ["/app/bin/server"]
其中 my_app
是您應用程式的名稱。最後,您將有一個應用程式位於 /app
中,準備以 /app/bin/server
執行。
關於組態容器化應用程式的幾個重點
- 如果您將應用程式執行於容器中,則
Endpoint
需設定為監聽「公開」:ip
位址(如0.0.0.0
),才能從容器外部存取應用程式。主機是將容器的埠發布至其自己的公開 IP 或本地端埠,則取決於您的需求。 - 您能在執行階段提供的設定越多(使用
config/runtime.exs
),您的映像將在各種環境中越能重複使用。特別是,例如資料庫認證和 API 金鑰等機密資訊,不應編譯至映像中,而應在以此映像為基礎建立容器時提供。這是為何Endpoint
的:secret_key_base
預設設定於config/runtime.exs
之中。 - 如果可行,在執行階段所需的任何環境變數都應在
config/runtime.exs
中讀取,而不是分佈至您的程式碼各處。將所有變數明確顯示在同個地方,將能更輕鬆地確認容器取得所需項目,尤其當執行基礎架構作業的人員,不會處理 Elixir 程式碼時。特別是,函式庫不應直接讀取環境變數;所有設定都應該由頂層應用程式交付,最好不使用應用程式環境。