查看源代码 在 Heroku 上部署
我们需要什么
本指南中唯一需要的是一个正常运行的 Phoenix 应用程序。对于需要部署简单应用程序的用户,请按照 入门指南 操作。
目标
本指南的主要目标是在 Heroku 上运行 Phoenix 应用程序。
限制
Heroku 是一个很棒的平台,Elixir 在其上表现良好。但是,如果您计划利用 Elixir 和 Phoenix 提供的先进功能,例如以下内容,则可能会遇到限制:
连接受限。
- Heroku 限制了同时连接的数量 以及 每个连接的持续时间。通常使用 Elixir 来构建需要大量并发、持久连接的实时应用程序,而 Phoenix 能够 在一台服务器上处理超过 200 万个连接。
分布式集群不可用。
- Heroku 将 Dynos 之间隔离开来。这意味着 分布式 Phoenix 通道 和 分布式任务 等内容将需要依赖 Redis 等东西,而不是 Elixir 的内置分布。
内存中的状态(例如 Agent、GenServer 和 ETS 中的状态)每 24 小时会丢失一次。
- Heroku 每 24 小时重启 Dynos,无论节点是否处于健康状态。
内置的观察器 无法与 Heroku 一起使用。
- Heroku 允许连接到您的 Dyno,但您将无法使用观察器来观察 Dyno 的状态。
如果您刚开始使用,或者您不希望使用上述功能,那么 Heroku 应该足以满足您的需求。例如,如果您正在将运行在 Heroku 上的现有应用程序迁移到 Phoenix,并保持类似的功能集,那么 Elixir 的性能会与当前堆栈一样好,甚至更好。
如果您想要一个没有这些限制的平台即服务,请尝试使用 Gigalixir。如果您希望部署到云平台(例如 EC2、Google Cloud 等),请考虑使用 mix release
。
步骤
让我们将此过程分成几个步骤,以便我们能够跟踪当前位置。
- 初始化 Git 仓库
- 注册 Heroku 帐户
- 安装 Heroku 工具带
- 创建和设置 Heroku 应用程序
- 使项目准备好用于 Heroku
- 部署时间!
- 有用的 Heroku 命令
初始化 Git 仓库
Git 是一种流行的分布式版本控制系统,也用于将应用程序部署到 Heroku。
在我们能够推送到 Heroku 之前,我们需要初始化本地 Git 仓库并将我们的文件提交到该仓库。我们可以在项目目录中运行以下命令来实现:
$ git init
$ git add .
$ git commit -m "Initial commit"
Heroku 提供了一些关于如何使用 Git 的信息,请 点击此处查看。
注册 Heroku 帐户
注册 Heroku 帐户非常简单,只需前往 https://signup.heroku.com/ 并填写表格即可。
免费计划将为我们提供一个 Web Dyno 和一个工作程序 Dyno,以及一个免费的 PostgreSQL 和 Redis 实例。
这些旨在用于测试和开发,并且有一些限制。为了运行生产应用程序,请考虑升级到付费计划。
安装 Heroku 工具带
注册后,我们可以从 此处 下载适合我们系统的 Heroku 工具带的正确版本。
工具带的一部分 Heroku CLI 对创建 Heroku 应用程序、列出当前运行的应用程序 Dyno、尾随日志或运行一次性命令(例如 mix 任务)很有用。
创建和设置 Heroku 应用程序
在 Heroku 上部署 Phoenix 应用程序有两种不同的方法。我们可以使用 Heroku 构建包或其容器堆栈。这两种方法之间的区别在于我们告诉 Heroku 如何处理我们的构建。在构建包情况下,我们需要更新我们在 Heroku 上的应用程序配置以使用 Phoenix/Elixir 特定的构建包。在容器方法中,我们可以更全面地控制如何设置应用程序,并且可以使用 Dockerfile
和 heroku.yml
定义容器映像。本节将探讨构建包方法。为了使用 Dockerfile,通常建议将应用程序转换为使用版本,我们将在后面介绍。
创建应用程序
构建包 是一种方便的打包框架和/或运行时支持的方式。Phoenix 需要 2 个构建包才能在 Heroku 上运行,第一个添加了基本的 Elixir 支持,第二个添加了 Phoenix 特定的命令。
安装了工具带后,让我们创建 Heroku 应用程序。我们将使用最新版本的 Elixir 构建包 来实现。
$ heroku create --buildpack hashnuke/elixir
Creating app... done, ⬢ mysterious-meadow-6277
Setting buildpack to hashnuke/elixir... done
https://mysterious-meadow-6277.herokuapp.com/ | https://git.heroku.com/mysterious-meadow-6277.git
注意:我们第一次使用 Heroku 命令时,它可能会提示我们登录。如果发生这种情况,只需输入我们在注册时指定的电子邮件和密码即可。
注意:Heroku 应用程序的名称是上面输出中“Creating”之后的随机字符串(mysterious-meadow-6277)。这将是唯一的,因此预计会看到与“mysterious-meadow-6277”不同的名称。
注意:输出中的 URL 是我们应用程序的 URL。如果我们在浏览器中打开它,现在将看到默认的 Heroku 欢迎页面。
注意:如果我们在运行
heroku create
命令之前没有初始化 Git 仓库,那么此时我们的 Heroku 远程仓库将无法正确设置。我们可以通过运行以下命令手动设置它:heroku git:remote -a [our-app-name]。
构建包使用预定义的 Elixir 和 Erlang 版本,但为了避免在部署时出现意外,最好明确列出我们在生产环境中想要使用的 Elixir 和 Erlang 版本,使其与我们在开发环境中或持续集成服务器中使用的版本相同。这可以通过在项目的根目录中创建一个名为 elixir_buildpack.config
的配置文件来实现,该文件包含目标 Elixir 和 Erlang 版本。
# Elixir version
elixir_version=1.14.0
# Erlang version
# https://github.com/HashNuke/heroku-buildpack-elixir-otp-builds/blob/master/otp-versions
erlang_version=24.3
# Invoke assets.deploy defined in your mix.exs to deploy assets with esbuild
# Note we nuke the esbuild executable from the image
hook_post_compile="eval mix assets.deploy && rm -f _build/esbuild*"
最后,让我们告诉构建包如何启动 Web 服务器。在项目的根目录中创建一个名为 Procfile
的文件。
web: mix phx.server
可选:Node、npm 和 Phoenix Static 构建包
默认情况下,Phoenix 使用 esbuild
并为您管理所有资产。但是,如果您使用的是 node
和 npm
,则需要安装 Phoenix Static 构建包 来处理它们。
$ heroku buildpacks:add https://github.com/gjaldon/heroku-buildpack-phoenix-static.git
Buildpack added. Next release on mysterious-meadow-6277 will use:
1. https://github.com/HashNuke/heroku-buildpack-elixir.git
2. https://github.com/gjaldon/heroku-buildpack-phoenix-static.git
使用此构建包时,您需要将所有资产捆绑委托给 npm
。因此,您必须从 elixir_buildpack.config
中删除 hook_post_compile
配置,并将其移动到 assets/package.json
的部署脚本中。类似于以下内容:
{
...
"scripts": {
"deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild*"
}
...
}
Phoenix Static 构建包使用预定义的 Node.js 版本,但为了避免在部署时出现意外,最好明确列出我们在生产环境中想要使用的 Node.js 版本,使其与我们在开发环境中或持续集成服务器中使用的版本相同。这可以通过在项目的根目录中创建一个名为 phoenix_static_buildpack.config
的配置文件来实现,该文件包含目标 Node.js 版本。
# Node.js version
node_version=10.20.1
请参阅 配置部分 以了解完整详细信息。您可以创建自己的自定义构建脚本,但目前我们将使用 提供的默认脚本。
最后,请注意,由于我们使用的是多个构建包,因此您可能会遇到顺序错误的问题(Elixir 构建包需要在 Phoenix Static 构建包之前运行)。Heroku 的文档 对此进行了更好的解释,但您需要确保 Phoenix Static 构建包最后运行。
使项目准备好用于 Heroku
每个新的 Phoenix 项目都附带一个配置文件 config/runtime.exs
(以前为 config/prod.secret.exs
),它从 环境变量 加载配置和机密。这与 Heroku 最佳实践 (12 要素应用程序) 非常契合,因此我们唯一要做的工作就是配置 URL 和 SSL。
首先,让我们告诉 Phoenix 只使用网站的 SSL 版本。在 config/prod.exs
中找到端点配置。
config :scaffold, ScaffoldWeb.Endpoint,
url: [port: 443, scheme: "https"],
... 并添加 force_ssl
。
config :scaffold, ScaffoldWeb.Endpoint,
url: [port: 443, scheme: "https"],
force_ssl: [rewrite_on: [:x_forwarded_proto]],
force_ssl
需要在这里设置,因为它是一个编译时配置。在 runtime.exs
中设置它将无效。
然后在您的 config/runtime.exs
(以前为 config/prod.secret.exs
)中。
... 添加 host
。
config :scaffold, ScaffoldWeb.Endpoint,
url: [host: host, port: 443, scheme: "https"]
并在存储库配置中取消注释 # ssl: true,
行。它将如下所示:
config :hello, Hello.Repo,
ssl: true,
url: database_url,
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
最后,如果您计划使用 WebSockets,那么我们需要在 lib/hello_web/endpoint.ex
中降低 WebSocket 传输的超时时间。如果您不打算使用 WebSockets,那么将其设置为 false 就可以了。您可以在 文档 中找到有关可用选项的更多说明。
defmodule HelloWeb.Endpoint do
use Phoenix.Endpoint, otp_app: :hello
socket "/socket", HelloWeb.UserSocket,
websocket: [timeout: 45_000]
...
end
还在 Heroku 中设置主机。
$ heroku config:set PHX_HOST="mysterious-meadow-6277.herokuapp.com"
这确保了在达到 Heroku 的 55 秒超时窗口之前,Phoenix 会关闭所有闲置连接。
在 Heroku 中创建环境变量
当我们添加 Heroku Postgres 附加组件 时,Heroku 会自动创建 DATABASE_URL
配置变量。我们可以通过 Heroku 工具带创建数据库。
$ heroku addons:create heroku-postgresql:mini
现在我们设置 POOL_SIZE
配置变量。
$ heroku config:set POOL_SIZE=18
此值应略小于可用连接数,留出几个连接用于迁移和混合任务。小型数据库允许 20 个连接,因此我们将此数字设置为 18。如果其他 dyno 将共享数据库,请减少 POOL_SIZE
以便每个 dyno 拥有相同的份额。
稍后运行混合任务(在将项目推送到 Heroku 之后)时,您还需要像这样限制其池大小。
$ heroku run "POOL_SIZE=2 mix hello.task"
这样 Ecto 不会尝试打开超过可用连接数的连接。
我们仍然需要根据随机字符串创建 SECRET_KEY_BASE
配置。首先,使用 mix phx.gen.secret
获取新密钥。
$ mix phx.gen.secret
xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53
您的随机字符串将不同;请勿使用此示例值。
现在在 Heroku 中设置它。
$ heroku config:set SECRET_KEY_BASE="xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53"
Setting config vars and restarting mysterious-meadow-6277... done, v3
SECRET_KEY_BASE: xvafzY4y01jYuzLm3ecJqo008dVnU3CN4f+MamNd1Zue4pXvfvUjbiXT8akaIF53
部署时间!
我们的项目现在已准备好部署到 Heroku 上。
让我们提交所有更改。
$ git add elixir_buildpack.config
$ git commit -a -m "Use production config from Heroku ENV variables and decrease socket timeout"
然后部署。
$ git push heroku main
Counting objects: 55, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (49/49), done.
Writing objects: 100% (55/55), 48.48 KiB | 0 bytes/s, done.
Total 55 (delta 1), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote:
remote: -----> Multipack app detected
remote: -----> Fetching custom git buildpack... done
remote: -----> elixir app detected
remote: -----> Checking Erlang and Elixir versions
remote: WARNING: elixir_buildpack.config wasn't found in the app
remote: Using default config from Elixir buildpack
remote: Will use the following versions:
remote: * Stack cedar-14
remote: * Erlang 17.5
remote: * Elixir 1.0.4
remote: Will export the following config vars:
remote: * Config vars DATABASE_URL
remote: * MIX_ENV=prod
remote: -----> Stack changed, will rebuild
remote: -----> Fetching Erlang 17.5
remote: -----> Installing Erlang 17.5 (changed)
remote:
remote: -----> Fetching Elixir v1.0.4
remote: -----> Installing Elixir v1.0.4 (changed)
remote: -----> Installing Hex
remote: 2015-07-07 00:04:00 URL:https://s3.amazonaws.com/s3.hex.pm/installs/1.0.0/hex.ez [262010/262010] ->
"/app/.mix/archives/hex.ez" [1]
remote: * creating /app/.mix/archives/hex.ez
remote: -----> Installing rebar
remote: * creating /app/.mix/rebar
remote: -----> Fetching app dependencies with mix
remote: Running dependency resolution
remote: Dependency resolution completed successfully
remote: [...]
remote: -----> Compiling
remote: [...]
remote: Generated phoenix_heroku app
remote: [...]
remote: Consolidated protocols written to _build/prod/consolidated
remote: -----> Creating .profile.d with env vars
remote: -----> Fetching custom git buildpack... done
remote: -----> Phoenix app detected
remote:
remote: -----> Loading configuration and environment
remote: Loading config...
remote: [...]
remote: Will export the following config vars:
remote: * Config vars DATABASE_URL
remote: * MIX_ENV=prod
remote:
remote: -----> Compressing... done, 82.1MB
remote: -----> Launching... done, v5
remote: https://mysterious-meadow-6277.herokuapp.com/ deployed to Heroku
remote:
remote: Verifying deploy... done.
To https://git.heroku.com/mysterious-meadow-6277.git
* [new branch] master -> master
在终端中输入 heroku open
应会在浏览器中打开 Phoenix 欢迎页面。如果您正在使用 Ecto 访问数据库,您还需要在首次部署后运行迁移。
$ heroku run "POOL_SIZE=2 mix ecto.migrate"
就是这样!
使用容器堆栈部署到 Heroku
创建 Heroku 应用程序
将您的应用程序的堆栈设置为 container
,这使我们能够使用 Dockerfile
来定义我们的应用程序设置。
$ heroku create
Creating app... done, ⬢ mysterious-meadow-6277
$ heroku stack:set container
在根文件夹中添加一个新的 heroku.yml
文件。在此文件中,您可以定义应用程序使用的附加组件、如何构建镜像以及哪些配置传递给镜像。您可以在 此处 了解有关 Heroku 的 heroku.yml
选项的更多信息。这是一个示例。
setup:
addons:
- plan: heroku-postgresql
as: DATABASE
build:
docker:
web: Dockerfile
config:
MIX_ENV: prod
SECRET_KEY_BASE: $SECRET_KEY_BASE
DATABASE_URL: $DATABASE_URL
设置发布和 Dockerfile
现在我们需要在项目根文件夹中定义一个 Dockerfile
,其中包含您的应用程序。我们建议在这样做时使用发布,因为发布将使我们能够仅使用我们实际使用的 Erlang 和 Elixir 部分来构建容器。请遵循 发布文档。在本指南的末尾,有一个您可以使用的示例 Dockerfile 文件。
设置好镜像定义后,您可以将应用程序推送到 heroku,您会看到它开始构建镜像并部署它。
有用的 Heroku 命令
我们可以通过在项目目录中运行以下命令来查看应用程序的日志。
$ heroku logs # use --tail if you want to tail them
我们还可以启动一个连接到我们的终端的 IEx 会话,以便在应用程序的环境中进行实验。
$ heroku run "POOL_SIZE=2 iex -S mix"
事实上,我们可以使用 heroku run
命令运行任何内容,例如上面的 Ecto 迁移任务。
$ heroku run "POOL_SIZE=2 mix ecto.migrate"
连接到您的 dyno
Heroku 使您能够使用 IEx shell 连接到您的 dyno,它允许运行 Elixir 代码,例如数据库查询。
修改 Procfile 中的
web
进程以运行命名节点。web: elixir --sname server -S mix phx.server
重新部署到 Heroku。
使用
heroku ps:exec
连接到 dyno(如果您在同一个存储库中有多个应用程序,您需要使用--app APP_NAME
或--remote REMOTE_NAME
指定应用程序名称或远程名称)。使用
iex --sname console --remsh server
启动 iex 会话。
您拥有一个进入 dyno 的 iex 会话!
故障排除
编译错误
有时,应用程序会在本地编译,但在 Heroku 上不会。Heroku 上的编译错误将类似于以下内容。
remote: == Compilation error on file lib/postgrex/connection.ex ==
remote: could not compile dependency :postgrex, "mix compile" failed. You can recompile this dependency with "mix deps.compile postgrex", update it with "mix deps.update postgrex" or clean it with "mix deps.clean postgrex"
remote: ** (CompileError) lib/postgrex/connection.ex:207: Postgrex.Connection.__struct__/0 is undefined, cannot expand struct Postgrex.Connection
remote: (elixir) src/elixir_map.erl:58: :elixir_map.translate_struct/4
remote: (stdlib) lists.erl:1353: :lists.mapfoldl/3
remote: (stdlib) lists.erl:1354: :lists.mapfoldl/3
remote:
remote:
remote: ! Push rejected, failed to compile elixir app
remote:
remote: Verifying deploy...
remote:
remote: ! Push rejected to mysterious-meadow-6277.
remote:
To https://git.heroku.com/mysterious-meadow-6277.git
这与过时的依赖项有关,这些依赖项没有正确地重新编译。可以强制 Heroku 在每次部署时重新编译所有依赖项,这应该可以解决此问题。方法是在应用程序的根目录中添加一个名为 elixir_buildpack.config
的新文件。该文件应包含此行。
always_rebuild=true
将此文件提交到存储库,然后尝试再次推送到 Heroku。
连接超时错误
如果您在运行 heroku run
时不断收到连接超时,这可能意味着您的互联网提供商已阻止了端口号 5000。
heroku run "POOL_SIZE=2 mix myapp.task"
Running POOL_SIZE=2 mix myapp.task on mysterious-meadow-6277... !
ETIMEDOUT: connect ETIMEDOUT 50.19.103.36:5000
您可以通过在运行命令中添加 detached
选项来克服此问题。
heroku run:detached "POOL_SIZE=2 mix ecto.migrate"
Running POOL_SIZE=2 mix ecto.migrate on mysterious-meadow-6277... done, run.8089 (Free)