如何在 Ubuntu 16.04 上使用 Distillery 和 edeliver 自动部署 Elixir-Phoenix

简介

Elixir](https://elixir-lang.org/)基于Erlang编程语言,是一种函数式编程语言,因其注重开发人员的工作效率、易于编写高度并发和可扩展的应用程序而广受欢迎。

Phoenix是一个基于 Elixir 的网络框架,可用于创建高性能的网络应用程序。

当与另外两个工具结合使用时— Distilleryedeliver —,你可以完全自动地将 Phoenix 项目从开发环境部署到生产服务器。

Distillery 可将 Elixir 应用程序编译成单个软件包,然后部署到其他地方。它还能生成允许代码热插拔的软件包,这意味着你可以在不停机的情况下升级实时应用程序。所有这些都几乎无需任何配置即可完成,这也是 Distillery 与许多其他选择的不同之处。

edeliver 可以自动完成构建和部署过程,它可以处理重复性任务,如构建应用程序、将构建好的软件包传输到服务器、迁移数据库以及启动/更新服务器。如有需要,您甚至还可以对 edeliver 进行配置,以便进行中间暂存设置。

在本教程中,您将在本地开发机器和生产服务器上安装 Erlang、Elixir 和 Phoenix 1.3,简化两个位置之间的 SSH 通信,然后创建一个示例 Phoenix 项目,使用 edeliver 进行构建和部署。 最后,使用 Nginx 反向代理和 SSL 证书确保生产服务器的安全。

在本教程结束时,您将掌握一条命令,它可以

  • 构建与生产环境兼容的 Phoenix 版本
  • 将版本部署到生产环境
  • 在生产环境中启动应用程序
  • 在不停机的情况下部署新版本,从而热插拔当前的生产版本

先决条件

开始之前,请确保具备以下条件:

  • 基于 Ubuntu 的本地开发机器。 虽然本教程的说明是针对基于 Ubuntu 的本地开发机器编写的,但这一部署流程的优势在于它完全独立于生产环境。有关在其他操作系统上设置本地开发机器的说明,请参阅Elixir 官方安装文档。或者,要设置基于 Ubuntu 的远程开发机器,请遵循本初始服务器设置教程
  • 在至少有 1GB 内存的 Ubuntu 16.04 生产服务器上建立一个具有 sudo 权限的非root 用户账户,按照本初始服务器设置教程 的前四个步骤进行设置。
    • 由于我们的目标是实现部署过程自动化,因此在执行设置教程的第 4 步时不要输入 SSH 口令。此外,请务必在设置教程的第 7 步使用 "sudo ufw allow 4000 "命令允许访问端口 "4000"。 在本教程中,我们将使用该端口测试 Phoenix。
  • 按照如何在 Ubuntu 16.04 上安装 Nginx 指南在生产服务器上安装 Nginx。
  • 已完全注册的域名。本教程将始终使用 "example.com"。您可以在 Namecheap 上购买域名,也可以在 Freenom 上免费获得域名,或者使用您选择的域名注册商。
  • 为服务器设置以下两个 DNS 记录。您可以按照 this hostname tutorial 了解如何添加这些记录的详细信息。
    • 指向服务器公共 IP 地址的 A 记录 example.com
    • 指向服务器公共 IP 地址的 A 记录,包含 www.example.com
  • 按照在Ubuntu 16.04上使用Nginx服务器块设置Let's Encrypt教程,使用SSL证书保护Nginx。确保在Nginx设置教程第4步中选择选项2 "重定向",因为这将在本教程创建的生产服务器上自动重定向到HTTPS。

步骤 1 - 在本地开发机器上安装 Elixir 和 Phoenix

因为 Elixir 是在 Erlang 虚拟机上运行的,所以我们需要先安装虚拟机,然后才能安装 Elixir 本身。由于我们要确保使用的是最新的 Erlang 稳定版本,因此我们将从 Erlang 解决方案软件仓库中安装 Erlang。

首先,下载 Erlang 解决方案软件源并将其添加到本地开发机器上。

1[environment local]
2cd ~
3wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
4sudo dpkg -i erlang-solutions_1.0_all.deb

现在,更新软件包列表并安装 esl-erlang 软件包,该软件包提供 Erlang 编程语言以及有用的工具、库和中间件,统称为 Erlang/OTP 平台。

1[environment local]
2sudo apt-get update
3sudo apt-get install esl-erlang

然后,安装 Elixir。

1[environment local]
2sudo apt-get install elixir

接下来,使用 Mix —Elixir 捆绑的构建工具,用于创建 Elixir 项目和管理依赖关系 —安装 Elixir 自带的软件包管理器 Hex,稍后将使用它来安装 Phoenix。

该命令的 local 部分告诉 Mix 在本地安装 hex

1[environment local]
2mix local.hex

提示确认安装时,输入 Y

1[environment local]
2[secondary_label Output]
3Are you sure you want to install "https://repo.hex.pm/installs/1.5.0/hex-0.17.1.ez"? [Yn] Y
4* creating .mix/archives/hex-0.17.1

现在,使用 Hex 安装 Phoenix 1.3.0 混合存档,这是一个 Zip 文件,其中包含生成新的 Phoenix 基本项目所需的所有内容。

1[environment local]
2mix archive.install https://github.com/phoenixframework/archives/raw/master/phx_new-1.3.0.ez

再次提示确认安装时,输入 Y

1[environment local]
2[secondary_label Output]
3Are you sure you want to install "https://github.com/phoenixframework/archives/raw/master/phx_new-1.3.0.ez"? [Yn] Y
4* creating .mix/archives/phx_new-1.3.0

<$>[警告] 警告: 如果您从 phx_new.ez 压缩包中安装 Phoenix,您将获得 Phoenix 的最新版本,该版本可能不同于本教程中使用的版本— 1.3.0。因此,您必须根据所使用的 Phoenix 版本调整本教程。 <$>

在本地开发机上安装了 Elixir 和 Phoenix 后,让我们在生产服务器上安装所需的组件。

第 2 步 - 在生产服务器上安装 Elixir 和 Phoenix

由于我们需要在本地开发机器和生产服务器上运行 Phoenix 项目,因此我们需要在两个地方安装相同的语言和工具。

使用 [步骤 1](# step-1-%E2%80%94-installing-elixir-and-phoenix-on-the-local-development-machine)中的相同命令,下载 Erlang 解决方案软件源并将其添加到生产服务器。

1cd ~
2wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb
3sudo dpkg -i erlang-solutions_1.0_all.deb

更新软件包列表并安装 esl-erlang 软件包。

1sudo apt-get update
2sudo apt-get install esl-erlang

安装 Elixir

1sudo apt-get install elixir

使用 Mix 安装 Hex。

1mix local.hex

提示确认安装时,输入 Y

1[secondary_label Output]
2Are you sure you want to install "https://repo.hex.pm/installs/1.5.0/hex-0.17.1.ez"? [Yn] Y
3* creating .mix/archives/hex-0.17.1

本地开发机和生产服务器现在都已准备好运行 Phoenix,但让我们通过设置 SSH 主机别名,更方便地从本地开发机连接到生产服务器。

第 3 步 - 设置 SSH 主机别名

由于我们的目标是实现完全自动化的部署流程,因此我们在[初始生产服务器设置](# prerequisites)过程中生成了一个 SSH 密钥对,它不会提示输入口令。

现在,我们可以使用命令 ssh -i ~/.ssh/private_key_file [email protected] 从本地开发机器连接到生产服务器。

在这里,我们以用户 sammy 的身份连接到 example.com-i标记告诉 SSH 使用位于~/.ssh/private_key_file的私钥文件进行连接。

不过,我们可以设置一个 SSH 主机别名,让它在连接生产服务器时自动知道要使用哪个私钥、用户和域,从而使该命令和部署过程本身变得更加简单。

打开本地开发计算机上的 ~/.ssh/config 进行编辑。

1[environment local]
2nano ~/.ssh/config

然后,复制以下几行。

1[environment local]
2[label ~/.ssh/config]
3Host example.com 
4    HostName example.com
5    User sammy
6    IdentityFile ~/.ssh/private_key_file

<$>[注] 注意: 如果您的 config 文件中已经包含了一些内容,请另外加入一行空行,将新配置与任何现有配置分隔开来。 <$>

主机 "行提供了一个别名,用于标识此特定配置。 为了便于记忆,我们使用自己的域名。主机名 "行告诉 SSH 要连接的主机。User "行让 SSH 知道要以哪个用户身份连接,而 "IdentityFile "则告诉 SSH 要使用哪个私钥文件。

保存更改并关闭文件。

最后,连接生产服务器测试配置。

1[environment local]
2ssh example.com

你应该能够在不指定用户、私钥文件或域的情况下进行连接。 如果无法连接,请根据屏幕上的提示,重新执行之前的步骤来解决问题。

现在我们已经简化了连接生产服务器的过程,可以创建一个用于部署的 Phoenix 示例项目。

第 4 步 - 创建测试项目

默认情况下,当你创建一个新的Phoenix项目时,它会配置一个PostgreSQL数据库适配器和Brunch(一个基于JavaScript的Web应用程序构建工具)。为了避免这种额外的复杂性,我们将创建一个名为 "myproject "的简单 Phoenix 项目,但不使用数据库适配器,也不使用 Brunch,方法是分别传递"--no-ecto "和"--no-brunch "标记。

切换到主目录,创建新项目。

1[environment local]
2cd ~
3mix phx.new --no-ecto --no-brunch myproject

输出包括 Phoenix 创建的作为 myproject 项目脚手架的目录和文件、确认是否要安装所需依赖项的提示,以及有关如何启动 Phoenix 内置服务器的说明。

提示确认安装时输入 Y

 1[environment local]
 2[secondary_label Output]
 3* creating myproject/config/config.exs
 4* creating myproject/config/dev.exs
 5* creating myproject/config/prod.exs
 6...
 7
 8Fetch and install dependencies? [Yn] Y
 9* running mix deps.get
10* running mix deps.compile
11
12We are all set! Go into your application by running:
13
14    $ cd myproject
15
16Start your Phoenix app with:
17
18    $ mix phx.server
19
20You can also run your app inside IEx (Interactive Elixir) as:
21
22    $ iex -S mix phx.server

现在,让我们看看我们的测试项目是否正常运行。

进入 myproject 目录,运行 mix phx.server 命令编译项目并启动服务器。

1[environment local]
2cd ~/myproject
3mix phx.server

输出结果会告诉你 Phoenix 编译的文件数量和类型,并就编译过程中遇到的问题发出警告,如果编译成功,还会告诉你从哪里可以找到该项目。

第一次在本地开发机器上编译基于 Elixir 的应用程序时,系统会提示你安装 Rebar,这是一个用于 Erlang 的编译和依赖工具,Mix 依赖它。 在提示符下输入 Y

 1[environment local]
 2[secondary_label Output]
 3==> file_system
 4Compiling 6 files (.ex)
 5Generated file_system app
 6...
 7Could not find "rebar3", which is needed to build dependency :ranch
 8I can install a local copy which is just used by Mix
 9Shall I install rebar3? (if running non-interactively, use "mix local.rebar --force") [Yn] Y
10...
11Compiling 11 files (.ex)
12Generated myproject app
13[info] Running MyprojectWeb.Endpoint with Cowboy using http://0.0.0.0:4000

要测试当前设置,请将网络浏览器指向 http://localhost:4000。 您应该会看到默认的 Phoenix Framework 主页欢迎您访问 Phoenix。 如果没有看到,请确保防火墙允许连接端口 4000,然后查看终端输出以获取进一步说明。

确认一切正常后,按两次 CTRL+C 键停止服务器,以便为 [步骤 5](# step-5-%E2%80%94-configuring-the-project-to-use-distillery-and-edeliver)中的进一步配置做好准备。

现在您有了一个功能齐全的本地 Phoenix 项目,让我们来配置它以使用 Distillery 和 edeliver。

第 5 步 - 配置项目以使用 Distillery 和 edeliver

Phoenix 项目会在 config/prod.exs 中存储项目运行端口和项目主机 URL 等配置细节,因此我们首先要编辑该文件,告诉 Phoenix 如何在生产环境中访问项目。

在本地开发计算机上打开 config/prod.exs 进行编辑。

1[environment local]
2nano ~/myproject/config/prod.exs

查找以下代码块:

1[environment local]
2[label config/prod.exs]
3...
4config :myproject, MyprojectWeb.Endpoint,
5  load_from_system_env: true,
6  url: [host: "example.com", port: 80],
7  cache_static_manifest: "priv/static/cache_manifest.json"
8...

load_from_system_env 设置为 true 时,Phoenix 默认从 PORT 环境变量获取项目运行的端口。这被称为 HTTP 端口。

url:[主机]"和 "url:[端口]"用于在项目内生成链接。HTTP 和 URL 之间的这种差异在设置代理时特别有用,因为代理端点暴露在与 Phoenix 项目不同的端口上。

为简单起见,我们将硬编码 myproject 运行的 HTTP 端口。 这将减少活动部件的数量,从而提高自动部署流程的可靠性。

除了要修改的默认选项外,我们还将添加两个新选项。

server "选项会告诉 Distillery 配置项目在启动时启动 HTTP 服务器,这正是我们在全自动部署过程中想要的。

每当项目的代码发生变化时,"code_reloader "选项就会告诉项目刷新所有连接的网络浏览器。 虽然这在开发过程中非常有用,但并不适合生产环境,因此我们将关闭它。

现在,修改默认配置。

 1[environment local]
 2[label config/prod.exs]
 3...
 4config :myproject, MyprojectWeb.Endpoint,
 5  http: [port: 4000],
 6  url: [host: "example.com", port: 80],
 7  cache_static_manifest: "priv/static/manifest.json",
 8  server: true,
 9  code_reloader: false
10...

<$>[注意] 注意: 为避免潜在的配置问题,请在继续之前仔细检查是否已在 cache_static_manifest 行末尾添加了,。 <$>

完成更改后,保存并关闭 config/prod.exs

在 [step 4](# step-4-%E2%80%94-creating-a-test-project)中创建 "myproject "项目时,Phoenix 自动生成了一个".gitignore "文件,在 [step 6](# step-6-%E2%80%94-configuring-edeliver-and-distillery)中,当我们使用 edeliver 向构建服务器推送代码更改时,需要用到这个文件。

默认情况下,".gitignore "文件会告诉 Git 忽略依赖项和编译文件,以免版本库变得过于庞大。 此外,该文件还会告诉 Git 忽略所有 Phoenix 项目的 config 目录中的 prod.secret.exs 文件,该文件包含非常敏感的信息,如生产数据库密码和签名令牌的应用秘密。

由于 myproject 项目需要生产服务器上的 prod.secret.exs 才能正常运行,而我们无法通过 Git 将其移动到生产服务器上,因此我们必须手动将其转移到服务器上。

在生产服务器的主目录中,新建一个名为 app_config 的目录。 您将在此存放 prod.secret.exs

1cd ~
2mkdir app_config

现在,使用 scpprod.secret.exs 复制到生产服务器上的 app_config 目录。

1[environment local]
2scp ~/myproject/config/prod.secret.exs example.com:/home/sammy/app_config/prod.secret.exs

最后,通过列出生产服务器上 app_config 的内容来验证传输是否完成。

1ls ~/app_config

如果在输出中没有看到 prod.secret.exs,请查看本地开发机器上的终端以获取更多信息。

在生产服务器上安装了 prod.secret.exs 后,我们就可以在 mix.exs("myproject "项目的主配置文件)中安装用于构建流程的 Distillery 和用于部署的 edeliver。

在本地开发计算机上打开 mix.exs

1[environment local]
2nano ~/myproject/mix.exs

现在,找到以下代码块:

 1[environment local]
 2[label Dependencies in mix.exs]
 3  ...
 4  defp deps do
 5    [
 6      {:phoenix, "~> 1.3.0"},
 7      {:phoenix_pubsub, "~> 1.0"},
 8      {:phoenix_html, "~> 2.10"},
 9      {:phoenix_live_reload, "~> 1.0", only: :dev},
10      {:gettext, "~> 0.11"},
11      {:cowboy, "~> 1.0"}
12    ]
13  end
14  ...

deps是一个私有函数,明确定义了myproject` 项目的所有依赖关系。 虽然并非严格要求,但它有助于保持项目配置的条理性。

在依赖列表中添加 edeliverdistillery

 1[environment local]
 2[label Dependencies in mix.exs]
 3  ...
 4  defp deps do
 5    [
 6      {:phoenix, "~> 1.3.0"},
 7      {:phoenix_pubsub, "~> 1.0"},
 8      {:phoenix_html, "~> 2.10"},
 9      {:phoenix_live_reload, "~> 1.0", only: :dev},
10      {:gettext, "~> 0.11"},
11      {:cowboy, "~> 1.0"},
12      {:edeliver, "~> 1.4.3"},
13      {:distillery, "~> 1.4"}
14    ]
15  end
16  ...

<$>[注] 注意: 为避免潜在的配置问题,请仔细检查是否已在新的 edeliver 条目之前的行尾添加了一个 ,。 <$>

保存更改并关闭 mix.exs

现在,告诉 mix 获取新的依赖项,以便它们在运行时可用。

1[environment local]
2cd ~/myproject/
3mix deps.get

输出结果告诉我们,"edeliver "和 "distillery "已成功添加到我们的项目中。

 1[environment local]
 2[secondary_label Output]
 3Resolving Hex dependencies...
 4Dependency resolution completed:
 5  ...
 6* Getting edeliver (Hex package)
 7  Checking package (https://repo.hex.pm/tarballs/edeliver-1.4.4.tar)
 8  Fetched package
 9* Getting distillery (Hex package)
10  Checking package (https://repo.hex.pm/tarballs/distillery-1.5.2.tar)
11  Fetched package

最后,在本地开发机器上重启 Phoenix 服务器,测试当前配置。

1[environment local]
2mix phx.server

将浏览器指向 http://localhost:4000。 您应该会看到与 [步骤 4](# step-4-%E2%80%94-creating-a-test-project)中相同的默认 Phoenix 主页。 如果没有,请重新执行前面的步骤,并查看本地开发计算机的终端以获取更多信息。

准备继续时,按两次 CTRL+C,停止服务器,以便下一步进行进一步配置。

安装好 Distillery 和 edeliver 后,我们就可以配置它们进行部署了。

第 6 步 - 配置 Edeliver 和 Distillery

Distillery 需要一个构建配置文件,但默认情况下不会生成。不过,我们可以通过运行 mix release.init 生成默认配置。

进入本地开发计算机上的 myproject 目录并生成配置文件。

1[environment local]
2cd ~/myproject
3mix release.init

输出结果确认文件已创建,并包含有关如何编辑和构建版本的进一步说明。

1[environment local]
2[secondary_label Output]
3An example config file has been placed in rel/config.exs, review it,
4make edits as needed/desired, and then run `mix release` to build the release

在执行热升级时,edeliver 会在 rel/myproject 目录中查找发行版,但 Distillery 默认会将发行版放在 _build 目录中。因此,让我们修改 Distillery 的默认配置文件 rel/config.exs,将生产版本放到正确的位置。

在编辑器中打开 rel/config.exs

1[environment local]
2nano rel/config.exs

查找以下部分:

1[environment local]
2[label rel/config.exs]
3...
4environment :prod do
5  set include_erts: true
6  set include_src: false
7  set cookie: :"f3a1[Q^31~]3~N=|T|T=0NvN;h7OHK!%%c.}$)iP9!X|TS[X@sqG=m`yBYVt4/`:"
8end
9...

该代码块告诉 Distillery 我们希望它如何构建独立的生产发布包。include_erts 表示是否要捆绑 Erlang 运行时系统,这在目标系统没有安装 Erlang 或 Elixir 时非常有用。include_src`表示是否要包含源代码文件。cookie "值用于验证 Erlang 节点之间的通信。

关闭文件。

现在我们可以配置 edeliver 了,但必须手动创建其配置文件。

进入本地开发计算机上的 myproject 目录,新建一个名为 .deliver的目录,然后在 .deliver/config下打开一个新文件进行编辑。

1[environment local]
2cd ~/myproject
3mkdir .deliver
4nano .deliver/config

在该文件中,我们将指定构建和生产服务器的详细信息。由于我们在构建和生产时使用的是同一台服务器,因此我们的主机和用户在构建和生产时都是相同的。此外,我们将在 app_build 目录中执行构建,并将编译后的生产文件放到 app_release 目录中。

将以下内容复制到文件中。

 1[environment local]
 2[label .deliver/config]
 3APP="myproject"
 4
 5BUILD_HOST="example.com"
 6BUILD_USER="sammy"
 7BUILD_AT="/home/sammy/app_build"
 8
 9PRODUCTION_HOSTS="example.com" 
10PRODUCTION_USER="sammy" 
11DELIVER_TO="/home/sammy/app_release"

接下来,我们将在构建文件夹中创建一个指向 prod.secret.exs 的符号链接,该文件是我们在 [步骤 5](# step-5-%E2%80%94-configuring-the-project-to-use-distillery-and-edeliver)中转移到生产服务器上的 app_config 目录中的。该符号链接是在_edeliver 钩子_中创建的。在构建、阶段和部署过程的每个阶段,edeliver 都会调用一个特定的钩子。在我们的自动部署设置中,我们监听的是 pre_erlang_get_and_update_deps 钩子,它会在 edeliver 获取依赖项并开始编译之前被调用。

将以下内容添加到 .deliver/config

 1[environment local]
 2[label .deliver/config]
 3pre_erlang_get_and_update_deps() {
 4  local _prod_secret_path="/home/sammy/app_config/prod.secret.exs"
 5  if [ "$TARGET_MIX_ENV" = "prod" ]; then
 6    __sync_remote "
 7      ln -sfn '$_prod_secret_path' '$BUILD_AT/config/prod.secret.exs'
 8    "
 9  fi
10}

编辑完成后保存并关闭文件。

由于 edeliver 使用 Git 将最新提交的代码推送到构建服务器以进行进一步操作,因此部署前的最后一步就是为我们的项目创建一个 Git 仓库。

在本地开发机器上的 myproject 目录中,使用 git init 命令创建一个空的 Git 仓库。

1[environment local]
2cd ~/myproject
3git init

在把文件添加到 Git 索引之前,我们还需要把包含发布压缩包的目录添加到 .gitignore 文件中。否则,发布几个版本后,Git 仓库就会变得非常庞大。

1[environment local]
2echo ".deliver/releases/" >> .gitignore

接下来,将 myproject 项目中的整套文件添加到 Git 暂存区,以便在下一次提交时包含它们。

1[environment local]
2git add .

现在,设置 Git 与该仓库相关联的身份。 这将有助于追踪项目变更的来源。

1[environment local]
2git config user.email "[email protected]"
3git config user.name "Your Name"

最后,使用 -m 选项将文件提交到版本库,并说明提交的原因。

1[environment local]
2git commit -m "Setting up automated deployment"

输出会重复您的提交信息,然后报告更改的文件数、插入的行数以及添加到版本库的文件名。

1[environment local]
2[secondary_label Output]
3[master (root-commit) e58b766] Setting up automated deployment
4 39 files changed, 2344 insertions(+)
5 create mode 100644 .deliver/config
6...

现在,我们的项目已提交到 Git,Distillery 和 edeliver 也已配置完成,我们已经准备好进行首次部署了。

第 7 步 - 部署项目

这种部署流程的一个好处是,你几乎可以在本地开发机器上完成所有工作,很少需要接触生产服务器。

现在,让我们将 myproject 项目推送到生产服务器,从而完成所有工作。

首先,在本地开发机器上使用 mix 构建项目的发布版本,并通过 edeliver 将其传输到构建服务器。

1[environment local]
2cd ~/myproject
3mix edeliver build release

输出结果会实时更新构建过程的每个步骤,如果一切按预期进行,就会告诉你构建成功。

 1[environment local]
 2[secondary_label Output]
 3BUILDING RELEASE OF MYPROJECT APP ON BUILD HOST
 4
 5-----> Authorizing hosts
 6-----> Ensuring hosts are ready to accept git pushes
 7-----> Pushing new commits with git to: [email protected]
 8-----> Resetting remote hosts to fc86f878d96...
 9-----> Cleaning generated files from last build
10-----> Fetching / Updating dependencies
11-----> Compiling sources
12-----> Generating release
13-----> Copying release 0.0.1 to local release store
14-----> Copying myproject.tar.gz to release store
15
16RELEASE BUILD OF MYPROJECT WAS SUCCESSFUL!

如果构建不成功,edeliver 会指出遇到问题时试图执行的代码行。 你可以利用这些信息排除故障。

构建完成后,将版本转移到生产服务器。

1[environment local]
2mix edeliver deploy release to production

同样,输出结果会实时更新流程的每个步骤,如果一切正常,还会告诉你已将构建发布到生产中。

1[environment local]
2[secondary_label Output]
3DEPLOYING RELEASE OF MYPROJECT APP TO PRODUCTION HOSTS
4
5-----> Authorizing hosts
6-----> Uploading archive of release 0.0.1 from local release store
7-----> Extracting archive myproject.0.1.tar.gz
8
9DEPLOYED RELEASE TO PRODUCTION!

如果在部署过程中遇到问题,请检查终端输出以获取更多信息。

最后,在生产服务器上启动 myproject 项目。

1[environment local]
2mix edeliver start production

输出结果会告诉你项目运行的用户、运行的主机以及生产服务器上使用的版本的路径。 响应将是 "START DONE!"。

 1[environment local]
 2[secondary_label Output]
 3EDELIVER MYPROJECT WITH START COMMAND
 4
 5-----> starting production servers
 6
 7production node:
 8
 9  user    : sammy
10  host    : example.com
11  path    : /home/sammy/app_release
12  response:
13
14START DONE!

将浏览器指向 http://example.com:4000,测试部署过程。 您应该会再次看到默认的 Phoenix Framework 主页。 如果没有,请仔细检查生产服务器上的端口 4000 是否已打开,然后查看本地开发机器的终端以获取更多信息。

现在,我们已经验证了完整的构建和部署流程,让我们进一步在生产服务器上执行代码更新,而无需停机。

第 8 步 - 在不中断生产的情况下升级项目

我们的构建和部署流程有一个特点,那就是可以热插拔代码,在不停机的情况下更新生产服务器上的项目。 让我们对项目做一些修改,试试看。

打开项目主页文件进行编辑。

1[environment local]
2nano ~/myproject/lib/myproject_web/templates/page/index.html.eex

找到下面一行:

1[environment local]
2[label ~/myproject/web/templates/page/index.html.eex]
3...
4<h2><%= gettext "Welcome to %{name}", name: "Phoenix!" %></h2>
5...

现在,用以下内容替换这一行:

1<h2>Hello, World!</h2>

保存并关闭文件。

既然已经更新了代码库,我们还需要增加应用程序的版本。有了版本号,就可以更方便地跟踪发布情况,并在必要时回滚到以前的版本。

在本地开发计算机上打开 mix.exs

1[environment local]
2nano ~/myproject/mix.exs

查找以下图块:

 1[environment local]
 2[label mix.exs]
 3  ...
 4  def project do
 5    [app: :myproject,
 6     version: "0.0.1",
 7     elixir: "~> 1.2",
 8     elixirc_paths: elixirc_paths(Mix.env),
 9     compilers: [:phoenix, :gettext] ++ Mix.compilers,
10     build_embedded: Mix.env == :prod,
11     start_permanent: Mix.env == :prod,
12     deps: deps()]
13  end
14  ...

将版本从 0.0.1 增加到 0.0.2

 1[environment local]
 2[label mix.exs]
 3  ...
 4  def project do
 5    [app: :myproject,
 6     version: "0.0.2",
 7     elixir: "~> 1.2",
 8     elixirc_paths: elixirc_paths(Mix.env),
 9     compilers: [:phoenix, :gettext] ++ Mix.compilers,
10     build_embedded: Mix.env == :prod,
11     start_permanent: Mix.env == :prod,
12     deps: deps()]
13  end
14  ...

然后,保存并关闭文件。

现在,我们需要添加并提交修改到 Git,以便 edeliver 知道应该将它们推送到构建服务器。

1[environment local]
2git add .
3git commit -m "Changed welcome message"

最后,我们可以热插拔更改了。 这一次,我们只需使用一条命令,它与 [步骤 7](# step-7-%E2%80%94-deploying-the-project)中使用的三条相关命令相当。

只需一条命令,就能在生产服务器上构建、部署和重新启动应用程序。

1[environment local]
2mix edeliver upgrade production

同样,输出结果会实时显示整个过程的每一步,如果成功,则以 "升级完成!"结束。

 1[environment local]
 2[secondary_label Output]
 3EDELIVER MYPROJECT WITH UPGRADE COMMAND
 4
 5-----> Upgrading to revision 2fc28b6 from branch master
 6-----> Detecting release versions on production hosts
 7-----> Deploying upgrades to 1 online hosts
 8-----> Checking whether installed version 0.0.1 is in release store
 9-----> Building the upgrade from version 0.0.1
10-----> Authorizing hosts
11-----> Validating * version 0.0.1 is in local release store
12-----> Ensuring hosts are ready to accept git pushes
13-----> Pushing new commits with git to: sammy@example.com
14-----> Resetting remote hosts to 2fc28b6...
15-----> Cleaning generated files from last build
16-----> Checking out 2fc28b6...
17-----> Fetching / Updating dependencies
18-----> Compiling sources
19-----> Checking version of new release
20-----> Uploading archive of release 0.0.1 from local release store
21-----> Extracting archive myproject_0.0.1.tar.gz
22-----> Generating release
23-----> Removing built release 0.0.1 from remote release directory
24-----> Copying release 0.0.2 to local release store
25-----> Copying myproject.tar.gz to release store
26-----> Upgrading production hosts to version 0.0.2
27-----> Authorizing hosts
28-----> Uploading archive of release 0.0.2 from local release store
29-----> Upgrading release to 0.0.2
30
31UPGRADE DONE!

要验证一切正常,请在浏览器中重新加载 http://example.com:4000。 你应该能看到新信息。 如果没有,请重新执行之前的步骤,并检查终端是否有其他错误和警告信息。

现在,部署过程只需一条命令,而且我们还利用了 Erlang 最著名的功能之一:代码热插拔。最后,让我们把应用程序放在 Nginx 代理后面,以加强生产中的应用。

步骤 9 - 在生产服务器上设置反向代理

虽然我们可以直接将应用程序接入互联网,但反向代理可以提供更好的安全性。 为了便于配置、支持 SSL 并能设置自定义 HTTP 响应头,我们将使用 Nginx 作为代理。

如果你遵循了在 Ubuntu 16.04 上使用 Nginx 服务器块设置 Let's Encrypt 教程中的先决条件,你应该已经在生产服务器上为我们的项目创建了一个单独的 Nginx 服务器块。

打开该服务器块的配置文件进行编辑。

1sudo nano /etc/nginx/sites-available/example.com

首先,我们需要告诉 Nginx Phoenix 项目的位置和监听端口。 由于我们在本地通过端口 "4000 "为项目提供服务,因此我们要告诉 Nginx,我们的代理端点位于 "127.0.0.1:4000"。

将以下代码复制到默认服务器配置块上方的配置文件中。

1[label /etc/nginx/sites-available/example.com]
2upstream phoenix {
3    server 127.0.0.1:4000;
4}

现在,在同一文件中找到以下代码块:

1[label /etc/nginx/sites-available/example.com]
2    ...
3        location / {
4                # First attempt to serve request as file, then
5                # as directory, then fall back to displaying a 404.
6                try_files $uri $uri/ =404;
7        }
8    ...

为使代理正常工作,我们需要告诉 Nginx 将所有指向网络服务器的连接重定向到我们的 Phoenix 项目,包括请求头、客户端通过代理服务器的 IP 地址以及客户端本身的 IP 地址。

我们还将配置 Nginx 通过 WebSockets转发接收到的请求,WebSockets 是网络服务器和客户端之间的消息传递协议,它将标准的无状态 HTTP 连接升级为持久连接。

Phoenix 有一个名为 "通道 "的功能,我们在本教程中并未探讨,但 "通道 "需要 WebSockets 的支持。如果没有此配置,频道将无法工作,因为 WebSocket 请求无法到达服务器。

用下面的内容替换之前的 location 块:

 1[label /etc/nginx/sites-available/example.com]
 2  location / {
 3    allow all;
 4
 5    # Proxy Headers
 6    proxy_http_version 1.1;
 7    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
 8    proxy_set_header Host $http_host;
 9    proxy_set_header X-Cluster-Client-Ip $remote_addr;
10
11    # WebSockets
12    proxy_set_header Upgrade $http_upgrade;
13    proxy_set_header Connection "upgrade";
14
15    proxy_pass http://phoenix;
16  }

保存并关闭文件以继续。

现在,验证新的 Nginx 配置。

1sudo nginx -t

Nginx 应报告语法正常,测试成功。 如果没有,请根据屏幕上的信息解决问题。

重启 Nginx 以传播更改。

1sudo systemctl restart nginx

最后,为了安全起见,禁止通过 HTTP 4000 端口访问应用程序。

1sudo ufw delete allow 4000

然后,查看 UFW 的状态。

1sudo ufw status

此时,防火墙应只允许 SSH 和 Nginx 访问。

1[secondary_label Output]
2Status: active
3
4To Action From
5--                         ------      ----
6OpenSSH ALLOW Anywhere
7Nginx Full ALLOW Anywhere
8OpenSSH (v6)               ALLOW Anywhere (v6)
9Nginx Full (v6)            ALLOW Anywhere (v6)

最后,将浏览器指向 https://example.com,测试是否一切正常。

现在,您拥有了一个完全自动化的构建和部署流程,以及一个由反向代理和 SSL 证书保护的生产服务器。

结论

尽管我们已经设置了 edeliver,只需一条命令就能构建 Phoenix 项目并将其部署到生产服务器上,但还有很多事情可以做。

大多数生产型 Phoenix 应用程序都使用数据库。在 如何在 Ubuntu 16.04 上使用 MySQL 部署 Elixir-Phoenix 应用程序 中,您将继续使用该应用程序,添加 MySQL 数据库并将新功能部署到生产中。

如果您的生产基础架构由 Phoenix 节点集群组成,您可以使用 edeliver 同时在所有节点上部署和执行热插拔。

或者,如果你想要一个更可靠的设置,你可以创建一个完整的暂存基础架构,并使用 edeliver 来管理暂存和部署过程。

要了解有关上述主题的更多信息,或者要了解有关扩展当前 edeliver 安装的更多信息,请访问该项目的GitHub 官方主页

Published At
Categories with 技术
comments powered by Disqus