OTP Releases:构建与部署Elixir系统的最佳实践
1. 生产环境启动系统
在生产环境中启动系统时,可以通过设置
MIX_ENV
环境变量来实现。具体操作如下:
$ MIX_ENV=prod elixir -S mix run --no-halt
此命令会重新编译代码及其所有依赖项,所有
.beam
文件会存储在
_build/prod
文件夹中,并且
Mix
会确保
BEAM
实例从该文件夹加载文件。
需要注意的是,开发环境下编译的代码并未经过优化,虽然开发环境便于开发,但代码执行效率较低。因此,在评估系统在高负载下的性能时,应始终在生产环境中进行编译。若在开发环境中进行测量,可能会对瓶颈产生误判,从而浪费时间和精力去优化在生产环境中并无问题的代码。
不过,使用
mix
和
elixir
启动系统存在一些缺点。首先,需要在主机上编译项目,这意味着系统源代码必须存在于主机上,并且需要获取并编译所有依赖项。因此,需要在目标主机上安装编译所需的所有工具,包括
Erlang
、
Elixir
、
Hex
、
Mix
以及在
Mix
工作流中集成的任何第三方工具。此外,如果在同一台机器上运行多个系统,协调不同系统所需的支持工具的不同版本会变得越来越困难。而
OTP
发布则是解决这些问题的有效方法。
2. OTP 发布概述
OTP
发布是一个独立的、已编译的、可运行的系统,它由系统所需的最少
OTP
应用程序集组成。
OTP
发布还可以选择包含最少的
Erlang
运行时二进制文件,从而使发布完全自给自足。发布不包含源代码、文档文件或测试等工件。
这种方法具有诸多优点。首先,可以在开发机器或构建服务器上构建系统,只传输二进制工件,主机无需安装任何工具。如果将最少的
Erlang
运行时嵌入到发布中,甚至无需在生产服务器上安装
Elixir
和
Erlang
,运行系统所需的一切都将包含在发布包中。此外,发布简化了一些操作任务,如连接到正在运行的系统并在系统上下文中执行自定义
Elixir
代码。最后,发布为系统的在线升级(和降级)铺平了道路,在
Erlang
中称为发布处理。
3. 构建发布
要构建发布,需要编译主
OTP
应用程序及其所有依赖项,然后将所有二进制文件与
Erlang
运行时一起包含在发布中。可以使用
mix release
命令来完成这一操作,具体步骤如下:
1. 进入项目目录:
$ cd to-do
- 构建开发环境下的发布:
$ mix release
* assembling todo-0.1.0 on MIX_ENV=dev
* using config/runtime.exs to configure the release at runtime
Release created at _build/dev/rel/todo
...
由于发布通常用于生产环境,因此建议在生产环境中进行构建。可以通过在命令前加上
MIX_ENV=prod
来实现,或者在
mix.exs
中强制设置发布任务的默认环境。以下是在
mix.exs
中设置默认环境的示例:
defmodule Todo.MixProject do
...
def cli do
[
preferred_envs: [release: :prod]
]
end
...
end
通过上述更改,执行
mix release
命令时,项目将在生产环境中编译并生成发布:
$ mix release
* assembling todo-0.1.0 on MIX_ENV=prod
...
mix release
完成后,发布将位于
_build/prod/rel/todo/
子文件夹中。
4. 使用发布
与发布进行交互的主要工具是位于
_build/prod/rel/todo/bin/todo
的 shell 脚本,它可以执行多种任务,如下所示:
| 任务 | 命令 |
| ---- | ---- |
| 前台启动系统和
iex
shell |
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo start_iex
|
| 后台启动系统 |
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo daemon
|
| 停止正在运行的系统 |
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo stop
|
| 连接到正在运行的系统的远程 shell |
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo remote
|
以下是具体操作示例:
-
前台启动系统和
iex
shell
:
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo start_iex
Starting database worker.
Starting database worker.
Starting database worker.
Starting to-do cache.
iex(todo@localhost)1>
这里设置了
RELEASE_NODE
环境变量为所需的节点名称,若不设置,
Elixir
将根据主机名选择默认值。若要使用长节点名,还需设置
RELEASE_DISTRIBUTION
环境变量。
- 后台启动系统 :
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo daemon
此命令通过
run_erl
工具启动系统,该工具会将标准输出重定向到
_build/prod/rel/todo/tmp/log
文件夹中的日志文件,方便分析系统的控制台输出。
- 连接到正在运行的系统的远程 shell :
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo remote
iex(todo@localhost)1>
此时,将在生产节点的上下文中运行一个
iex
shell 会话。按两次
Ctrl-C
退出 shell 只会停止远程 shell,
todo
节点仍将继续运行。
- 停止正在运行的系统 :
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo stop
此外,还可以直接连接到正在运行的进程的 shell,这可以捕获正在运行的节点的标准输出。操作步骤如下:
1. 后台启动带有
iex
运行的发布:
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo daemon_iex
-
使用
to_erl工具连接到 shell:
$ _build/prod/rel/todo/erts-13.0/bin/to_erl _build/prod/rel/todo/tmp/pipe/
iex(todo@localhost)1>
[memory_usage: 70117728, process_count: 230]
需要注意的是,连接到 shell 时要小心。与远程 shell 不同,连接的 shell 在正在运行的节点的上下文中运行,通过操作系统管道连接到正在运行的节点,因此一次只能有一个连接会话。此外,按
Ctrl-\
可能会意外停止正在运行的节点,应按
Ctrl-D
从正在运行的节点分离而不停止它。
若要获取帮助,只需在不提供任何参数的情况下调用
_build/prod/rel/todo/bin/todo
,这将在标准输出中打印帮助信息。更多关于构建发布的详细信息,请参考官方
Mix
文档:https://hexdocs.pm/mix/Mix.Tasks.Release.html。
5. 发布内容
一个完全独立的发布包含以下内容:
- 运行系统所需的已编译
OTP
应用程序
- 包含将传递给虚拟机的参数的文件
- 描述需要启动哪些
OTP
应用程序的启动脚本
- 包含
OTP
应用程序环境变量的配置文件
- 用于启动、停止和与系统交互的辅助 shell 脚本
-
Erlang
运行时二进制文件
这些内容都位于
_build/prod/rel/todo
文件夹中,下面详细介绍其中一些重要部分。
5.1 编译后的二进制文件
所有必需应用程序的编译版本位于
_build/prod/rel/todo/lib
文件夹中,例如:
$ ls -1 _build/prod/rel/todo/lib
asn1-5.1
compiler-8.3
cowboy-2.10.0
cowboy_telemetry-0.4.0
cowlib-2.12.1
crypto-5.2
eex-1.15.0
elixir-1.15.0
iex-1.15.0
kernel-9.0
logger-1.15.0
mime-2.0.3
plug-1.14.2
plug_cowboy-2.6.1
plug_crypto-1.2.5
poolboy-1.5.2
public_key-1.14
ranch-1.8.0
runtime_tools-2.0
sasl-4.2.1
ssl-11.0
stdlib-5.0
telemetry-1.2.1
todo-0.1.0
这个列表包括所有运行时依赖项,包括直接依赖项(在
mix.exs
中指定)和间接依赖项(依赖项的依赖项)。此外,一些
OTP
应用程序,如
kernel
、
stdlib
和
elixir
,会自动包含在发布中,它们是任何基于
Elixir
的系统所需的核心
OTP
应用程序。最后,
iex
应用程序也包含在内,这使得可以运行远程
iex
shell。
每个文件夹中都有一个
ebin
子文件夹,编译后的二进制文件和
.app
文件位于其中。每个
OTP
应用程序文件夹还可能包含
priv
文件夹,其中包含特定于应用程序的附加文件。
如果需要在发布中包含额外的文件,最好的方法是在项目根目录下创建一个
priv
文件夹,该文件夹(如果存在)会自动出现在发布的应用程序文件夹下。当需要访问
priv
文件夹中的文件时,可以使用
Application.app_dir(:an_app_name, "priv")
来查找该文件夹的绝对路径。
通过查看加载路径可以证明发布是独立的:
$ RELEASE_NODE="todo@localhost" _build/prod/rel/todo/bin/todo start_iex
iex(todo@localhost)1> :code.get_path()
[~c"ch13/todo_release/_build/prod/rel/todo/lib/../releases/0.1.0/consolidated",
~c"ch13/todo_release/_build/prod/rel/todo/lib/kernel-9.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/stdlib-5.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/compiler-8.3/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/elixir-1.15.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/sasl-4.2.1/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/logger-1.15.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/crypto-5.2/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/cowlib-2.12.1/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/asn1-5.1/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/public_key-1.14/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/ssl-11.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/ranch-1.8.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/cowboy-2.10.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/telemetry-1.2.1/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/cowboy_telemetry-0.4.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/eex-1.15.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/mime-2.0.3/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/plug_crypto-1.2.5/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/plug-1.14.2/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/plug_cowboy-2.6.1/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/poolboy-1.5.2/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/runtime_tools-2.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/todo-0.1.0/ebin",
~c"ch13/todo_release/_build/prod/rel/todo/lib/iex-1.15.0/ebin"]
可以看到,所有加载路径都指向发布文件夹。相比之下,当启动一个普通的
iex -S mix
shell 并运行
:code.get_path/0
时,会看到一个长得多的加载路径列表,其中一些指向构建文件夹,另一些指向系统
Elixir
和
Erlang
的安装路径。这表明发布是自给自足的,运行时只会在发布文件夹中查找模块。
此外,发布中还包含最少的
Erlang
二进制文件,它们位于
_build/prod/rel/todo/erts-X.Y
中,其中
X.Y
对应于运行时版本号(与
Erlang
版本号无关)。包含
Erlang
运行时使发布完全独立,并且允许在同一台机器上运行由不同
Elixir
或
Erlang
版本驱动的多个系统。
5.2 配置文件
配置文件位于
_build/prod/rel/todo/releases/0.1.0
文件夹中,其中
0.1.0
对应于
todo
应用程序的版本(在
mix.exs
中提供)。该文件夹中最相关的两个文件是
vm.args
和
env.sh
。
-
vm.args文件可用于向Erlang运行时提供标志,例如+P标志用于设置最大运行进程数。 -
env.sh文件可用于设置环境变量,如前面提到的RELEASE_NODE和RELEASE_DISTRIBUTION。
更多关于提供这些文件的自定义版本的详细信息,请参考:https://hexdocs.pm/mix/Mix.Tasks.Release.html#module-vm-args-and-env-sh-env-bat。
6. 在 Docker 容器中打包
在生产环境中运行系统有多种方式,如部署到平台即服务(PaaS),如
Heroku
、
Fly.io
或
Gigalixir
,或者在
Kubernetes
集群中运行,还可以在服务管理器(如
systemd
)下将系统作为服务运行。无论选择哪种部署策略,都应尽量将系统作为
OTP
发布来运行,在大多数情况下,这意味着在前台启动发布。有效的启动命令可以是
start_iex
或
start
。
-
start_iex命令会同时启动iex会话,允许连接到正在运行的BEAM节点的iexshell 并与生产系统进行交互,同时捕获节点的标准输出。但这种方法有风险,因为可能会意外停止节点(按两次Ctrl-C)。 -
start命令会在前台启动系统,但不启动iex会话,因此无法连接到主iexshell。不过,仍可以通过建立远程iexshell 会话与正在运行的系统进行交互,但此时节点的标准输出不会被捕获。
具体的部署步骤取决于所选的策略,由于选项众多,无法一一涵盖。
Phoenix
Web 框架的部署指南(https://hexdocs.pm/phoenix/deployment.html)提供了一些流行选择的基本介绍。
下面以在 Docker 容器中运行待办事项系统为例进行说明。Docker 是许多团队选择的流行选项,因为它有助于自动化部署,支持在本地运行类似生产环境的版本,并为各种部署选项(尤其是在云环境中)铺平了道路。这里假设你对 Docker 有一定的了解,若不熟悉,可以参考官方入门指南:https://docs.docker.com/get-started/。
Elixir 项目的 Docker 镜像通常分两个阶段构建。第一阶段(通常称为构建阶段)需要编译代码并组装
OTP
发布;第二阶段将发布复制到最终镜像中,该镜像将部署到目标主机。最终镜像不包含构建工具,如
Erlang
和
Elixir
,因为
OTP
发布本身包含了所需的最少
Erlang
和
Elixir
二进制文件。
构建 Docker 镜像需要在项目根目录下创建名为
Dockerfile
的文件,以下是第一阶段构建
OTP
发布的代码:
ARG ELIXIR="1.15.4"
ARG ERLANG="26.0.2"
ARG DEBIAN="bookworm-20230612-slim"
ARG OS="debian-${DEBIAN}"
FROM "hexpm/elixir:${ELIXIR}-erlang-${ERLANG}-${OS}" as builder
WORKDIR /todo
ENV MIX_ENV="prod"
RUN mix local.hex --force && mix local.rebar --force
COPY mix.exs mix.lock ./
COPY config config
COPY lib lib
RUN mix deps.get --only prod
RUN mix release
此示例中使用的基础 Docker 镜像由
Hex
包管理器团队维护(https://hub.docker.com/r/hexpm/elixir)。需要注意的是,为了简洁起见,这个 Docker 文件比较简单,没有利用 Docker 层缓存。因此,任何源文件的更改都将需要重新编译整个项目,包括所有依赖项。若要更精细地构建镜像,可以参考
Phoenix
Web 框架生成的
Dockerfile
(https://hexdocs.pm/phoenix/releases.html#containers)。
接下来是构建最终镜像的代码:
ARG DEBIAN="bookworm-20230612-slim"
FROM debian:${DEBIAN}
WORKDIR "/todo"
RUN apt-get update -y && apt-get install -y openssl locales
COPY \
--from=builder \
--chown=nobody:root \
/todo/_build/prod/rel/todo ./
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
综上所述,
OTP
发布为 Elixir 系统的构建、部署和管理提供了一种高效、独立的解决方案。通过合理利用
OTP
发布和 Docker 容器,可以简化系统的部署流程,提高系统的可维护性和可扩展性。
OTP Releases:构建与部署Elixir系统的最佳实践
7. 总结与建议
为了更好地理解和运用 OTP 发布,下面通过一个流程图来展示整个构建和使用发布的主要流程:
graph LR
classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px
classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px
classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px
A([开始]):::startend --> B(编译主OTP应用及依赖):::process
B --> C{选择环境}:::decision
C -->|开发环境| D(执行 mix release):::process
C -->|生产环境| E(设置 preferred_envs 或前缀 MIX_ENV=prod):::process
E --> F(执行 mix release):::process
D --> G(发布位于 _build/dev/rel/todo):::process
F --> H(发布位于 _build/prod/rel/todo):::process
G --> I{选择使用方式}:::decision
H --> I
I -->|前台启动带 iex| J(执行 start_iex 命令):::process
I -->|后台启动| K(执行 daemon 命令):::process
I -->|停止系统| L(执行 stop 命令):::process
I -->|连接远程 shell| M(执行 remote 命令):::process
K --> N(可连接本地 shell):::process
N --> O(使用 to_erl 工具):::process
J --> P([结束]):::startend
K --> P
L --> P
M --> P
O --> P
通过这个流程图,可以清晰地看到从开始构建到使用发布的整个过程。在实际操作中,我们还可以根据不同的需求和场景进行灵活调整。
另外,以下是一些使用 OTP 发布和 Docker 容器的建议:
-
环境管理
:
- 始终明确区分开发环境和生产环境,确保在生产环境中构建发布时使用正确的配置。
- 合理使用
vm.args
和
env.sh
文件来管理系统的运行参数和环境变量。
-
文件管理
:
- 利用项目根目录下的
priv
文件夹来包含额外的文件,方便在发布中使用。
- 定期清理不必要的文件,避免发布体积过大。
-
Docker 优化
:
- 学习并使用 Docker 层缓存技术,减少构建时间和资源消耗。
- 参考优秀的 Dockerfile 示例,如 Phoenix Web 框架生成的 Dockerfile,优化自己的构建流程。
8. 常见问题及解决方案
在使用 OTP 发布和 Docker 容器的过程中,可能会遇到一些常见问题,下面通过表格的形式列出并给出相应的解决方案:
| 问题描述 | 可能原因 | 解决方案 |
| ---- | ---- | ---- |
| 发布构建失败 | 依赖项未正确安装、网络问题、配置错误 | 检查网络连接,确保所有依赖项都正确安装,仔细检查
mix.exs
和配置文件 |
| 无法启动发布 | 环境变量设置错误、Erlang 运行时问题 | 检查
RELEASE_NODE
和
RELEASE_DISTRIBUTION
等环境变量是否正确设置,确保 Erlang 运行时版本兼容 |
| Docker 构建缓慢 | 未使用层缓存、依赖项下载慢 | 优化 Dockerfile 以利用层缓存,使用国内镜像源加速依赖项下载 |
| 本地 shell 连接异常 | 管道文件损坏、节点已停止 | 检查管道文件是否存在或损坏,确保节点正在运行 |
9. 未来趋势与展望
随着 Elixir 和 Erlang 生态系统的不断发展,OTP 发布和 Docker 容器的应用也将更加广泛和深入。未来可能会出现以下趋势:
-
自动化程度提高
:更多的工具和框架将支持自动化构建、部署和更新 OTP 发布,进一步简化开发和运维流程。
-
与云服务的集成加强
:OTP 发布和 Docker 容器将更好地与各种云服务集成,如 AWS、Azure 等,提供更强大的云计算能力。
-
安全性增强
:在构建和部署过程中,将更加注重安全性,包括依赖项的安全检查、容器的安全配置等。
为了跟上这些趋势,开发者需要不断学习和掌握新的技术和工具,提高自己的技能水平。同时,要积极参与社区讨论,分享经验和见解,共同推动 Elixir 生态系统的发展。
10. 实战案例分析
为了更直观地展示 OTP 发布和 Docker 容器的应用效果,下面通过一个简单的实战案例进行分析。
假设我们有一个简单的 Elixir Web 应用,用于处理用户的待办事项。我们希望将这个应用部署到生产环境中,并且使用 OTP 发布和 Docker 容器来实现。
10.1 构建 OTP 发布
首先,按照前面介绍的步骤,在生产环境中构建 OTP 发布:
# 设置 preferred_envs
# 在 mix.exs 中添加以下代码
defmodule Todo.MixProject do
...
def cli do
[
preferred_envs: [release: :prod]
]
end
...
end
# 执行 mix release
$ mix release
* assembling todo-0.1.0 on MIX_ENV=prod
...
发布完成后,发布文件将位于
_build/prod/rel/todo
文件夹中。
10.2 创建 Docker 镜像
接下来,创建 Dockerfile 来构建 Docker 镜像:
# 第一阶段:构建 OTP 发布
ARG ELIXIR="1.15.4"
ARG ERLANG="26.0.2"
ARG DEBIAN="bookworm-20230612-slim"
ARG OS="debian-${DEBIAN}"
FROM "hexpm/elixir:${ELIXIR}-erlang-${ERLANG}-${OS}" as builder
WORKDIR /todo
ENV MIX_ENV="prod"
RUN mix local.hex --force && mix local.rebar --force
COPY mix.exs mix.lock ./
COPY config config
COPY lib lib
RUN mix deps.get --only prod
RUN mix release
# 第二阶段:构建最终镜像
ARG DEBIAN="bookworm-20230612-slim"
FROM debian:${DEBIAN}
WORKDIR "/todo"
RUN apt-get update -y && apt-get install -y openssl locales
COPY \
--from=builder \
--chown=nobody:root \
/todo/_build/prod/rel/todo ./
RUN sed -i '/en_US.UTF-8/s/^# //g' /etc/locale.gen && locale-gen
然后,使用以下命令构建 Docker 镜像:
$ docker build -t todo-app .
10.3 运行 Docker 容器
最后,使用以下命令运行 Docker 容器:
$ docker run -p 4000:4000 todo-app
现在,我们的 Elixir Web 应用就可以通过
http://localhost:4000
访问了。
通过这个实战案例,我们可以看到使用 OTP 发布和 Docker 容器可以轻松地将 Elixir 应用部署到生产环境中,并且具有很好的独立性和可移植性。
总之,OTP 发布和 Docker 容器为 Elixir 系统的构建和部署提供了强大的支持。通过深入学习和实践,我们可以更好地利用这些技术,提高系统的质量和效率。希望本文对大家有所帮助,让我们一起在 Elixir 的世界中不断探索和进步!
超级会员免费看
1346

被折叠的 条评论
为什么被折叠?



