查看源代码 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)
如果要调用发布包中的特定模块和函数,可以通过两种方式实现:使用 eval
或 rpc
。
$ 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
安装后,必须通过包含在 erts-VSN/bin
目录中的 erlsrv
可执行文件显式管理服务。服务在安装后不会自动启动。
例如,如果有一个名为 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-gnu
、x86_64-unknown-linux-musl
、x86_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 系统上的 LD_LIBRARY_PATH
环境变量,使其包含捆绑对象的路径,或更新 Windows 系统上的 $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 分发 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}
格式轻松引用来自另一个应用程序的应用程序版本。这在伞形应用程序中特别有用。:quiet
- 一个布尔值,控制发行版是否应将步骤写入标准输出。默认为false
。:include_erts
- 一个布尔值、字符串或零元组匿名函数。如果是布尔值,则表示是否应将 Erlang 运行时系统 (ERTS)(包括 Erlang 虚拟机)包含在发行版中。默认值为true
,这也是推荐的值。如果是字符串,则表示现有 ERTS 安装的路径。如果是一个零元组匿名函数,则是一个返回上述任何值(布尔值或字符串)的函数。如果您希望使用目标上安装的 ERTS 版本,也可以将此选项设置为
false
。但是,请注意,目标上的 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”中)。有关更多信息,请参阅“叠加”部分。:steps
- 在组装发行版之前和之后执行的步骤列表。有关更多信息,请参阅“步骤”部分。:skip_mode_validation_for
- 一个应用程序名称(原子)列表,指定要跳过对“不安全”模式的严格验证的应用程序。当父应用程序模式为:permanent
但其依赖的应用程序之一设置为:load
时,就会出现“不安全”情况。谨慎使用,因为具有无效模式的发行版可能不再能够启动,除非进行额外的调整。默认为[]
。
请注意,每个发行版定义都可以作为匿名函数给出。如果某些发行版属性计算起来很昂贵,这很有用
releases: [
demo: fn ->
[version: @version <> "+" <> git_ref()]
end
]
除了上面的选项之外,还可以通过自定义文件、调整发行版步骤或在启动时运行自定义选项和命令来定制生成的发行版。接下来我们将详细介绍这两种方法。
叠加
通常需要在组装发行版后将额外的文件复制到发行版根目录。可以通过将这些文件放在 rel/overlays
目录中轻松完成此操作。其中的任何文件都会按原样复制到发行版根目录。例如,如果您放置了一个“rel/overlays/Dockerfile”文件,则“Dockerfile”将按原样复制到发行版根目录。
如果您要指定额外的叠加目录,可以使用 :overlays
选项。如果您需要动态复制文件,请参阅“步骤”部分。
步骤
可以在组装发行版之前和之后添加一个或多个步骤。可以使用 :steps
选项执行此操作
releases: [
demo: [
steps: [&set_configs/1, :assemble, ©_extra_files/1]
]
]
The :steps
option must be a list, and it must always include the atom :assemble
, which does most of the release assembling. You can pass anonymous functions before and after the :assemble
to customize your release assembling pipeline. Those anonymous functions will receive a Mix.Release
struct and must return the same or an updated Mix.Release
struct. It is also possible to build a tarball of the release by passing the :tar
step anywhere after :assemble
. If the release :path
is not configured, the tarball is created in _build/MIX_ENV/RELEASE_NAME-RELEASE_VSN.tar.gz
Otherwise it is created inside the configured :path
.
See Mix.Release
for more documentation on the struct and which fields can be modified. Note that the :steps
field itself can be modified and it is updated every time a step is called. Therefore, if you need to execute a command before and after assembling the release, you only need to declare the first steps in your pipeline and then inject the last step into the release struct. The steps field can also be used to verify if the step was set before or after assembling the release.
vm.args 和 env.sh (env.bat)
开发人员可能希望在发布启动时自定义给定的 VM 标志和环境变量。自定义这些文件的最简单方法是运行 mix release.init
。Mix 任务将自定义的 rel/vm.args.eex
、rel/remote.vm.args.eex
、rel/env.sh.eex
和 rel/env.bat.eex
文件复制到您的项目根目录。您可以修改这些文件,它们将在每次执行新的发布时进行评估。这些文件是普通的 EEx 模板,它们有一个名为 @release
的单一赋值,其中包含 Mix.Release
结构体。
The vm.args
和 remote.vm.args
文件可能包含 erl
命令 接受的任何 VM 标志。
The env.sh
和 env.bat
用于设置环境变量。在那里,您可以设置诸如 RELEASE_NODE
、RELEASE_COOKIE
和 RELEASE_TMP
之类的变量,以分别自定义您的节点名称、cookie 和临时目录。每当调用 env.sh
或 env.bat
时,变量 RELEASE_ROOT
、RELEASE_NAME
、RELEASE_VSN
和 RELEASE_COMMAND
已经设置,因此您可以依赖它们。有关更多信息,请参阅环境变量部分。
此外,虽然 vm.args
文件是静态的,但您可以使用 env.sh
和 env.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
注意我们只在 start/daemon 命令上设置端口。如果您还在其他命令(例如 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.sh
和 env.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
环境变量或在您的releases/RELEASE_VSN/env.sh
(或 Windows 上的env.bat
)中设置它来配置“tmp”目录。如果使用已弃用的config/releases.exs
,则默认为true
,否则默认为false
。:prune_runtime_sys_config_after_boot
- 如果:reboot_system_after_config
设置,每次系统启动时,发布都会将一个配置文件写入您的 tmp 目录。这些配置文件通常很小。但是,如果您担心磁盘空间或有其他限制,您可以要求系统在启动后删除这些配置文件。缺点是您将无法再在内部重新启动系统(无论是通过System.restart/0
还是通过bin/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_NODE
或RELEASE_MODE
rel/vm.args.eex
和rel/remote.vm.args.eex
- 被复制到每个发布中的模板文件,并提供 Erlang 虚拟机和其他运行时标志的静态配置。vm.args
在start
、daemon
和eval
命令上运行。remote.vm.args
为remote
和rpc
命令配置 VMrel/env.sh.eex
和rel/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.sh
和 env.bat
读取
RELEASE_ROOT
- 指向发布的根目录。如果系统包含 ERTS,那么它与:code.root_dir/0
相同。此变量始终计算,不能设置为自定义值RELEASE_COMMAND
- 指令给发布的命令,例如"start"
、"remote"
、"eval"
等等。这通常在env.sh
和env.bat
内部访问,以在不同条件下设置不同的环境变量。但请注意,RELEASE_COMMAND
在调用env.sh
和env.bat
时尚未验证,因此可能为空或包含无效值。此变量始终计算,无法设置为自定义值。RELEASE_NAME
- 发布的名称。在调用发布时可以设置为自定义值。RELEASE_VSN
- 发布的版本,否则使用最新版本。在调用发布时可以设置为自定义值。自定义值必须是releases/
目录中存在的发布版本。RELEASE_PROG
- 用于启动发布的命令行可执行文件。
在调用发布之前或在 env.sh
和 env.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
- 发布是否应该按需加载代码(交互式)或预加载代码(嵌入式)。默认为 "embedded",这会增加启动时间,但意味着运行时将更快响应,因为它不需要加载代码。如果您需要减少启动时间并在启动时减少内存使用量,请选择交互式。它仅适用于 start/daemon/install 命令。RELEASE_DISTRIBUTION
- 我们希望如何运行发行版。可以是name
(长名称)、sname
(短名称)或none
(发行版不会自动启动)。默认为sname
,它只允许在当前系统内访问。name
允许外部连接。RELEASE_BOOT_SCRIPT
- 启动发布时使用的引导脚本名称。此脚本用于运行start
和daemon
等命令。引导脚本预计位于releases/RELEASE_VSN/RELEASE_BOOT_SCRIPT.boot
路径。默认为start
。RELEASE_BOOT_SCRIPT_CLEAN
- 启动发布时使用的引导脚本名称,没有您的应用程序或其依赖项。此脚本由eval
、rpc
和remote
等命令使用。引导脚本预计位于releases/RELEASE_VSN/RELEASE_BOOT_SCRIPT_CLEAN.boot
路径。默认为start_clean
。
伞形项目
发布与伞形项目很好地集成,允许您发布一个或多个伞形子项目的子集。在伞形项目中执行发布与在常规应用程序中执行发布之间的唯一区别是,伞形项目要求您明确列出您的发布和每个发布的起点。例如,想象一下这个伞形应用程序:
my_app_umbrella/
apps/
my_app_core/
my_app_event_processing/
my_app_web/
其中 my_app_event_processing
和 my_app_web
都依赖于 my_app_core
,但它们彼此不依赖。
在您的伞形项目中,您可以定义多个发布:
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
。
最后,请记住,您无需从伞形项目根目录组装发布。您也可以从每个子应用程序单独组装发布。但是,从根目录执行此操作允许您将两个彼此不依赖的应用程序作为同一发布的一部分包含在内。
热代码升级
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
,它总是将计数器增加 1,您引入了 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
- 发布的版本。