🚀

phx.gen.releaseを試して爆速でfly.ioにデプロイしてみよう

2021/12/11に公開1

この記事はelixir Advent Calendar 2021の11日目です🎄

10日目は@Gsannさんの【Elixir】ElixirでGoogleAPIを実行してYoutubeの動画情報を取得する でした。

今回のテーマ

fukuoka.ex Elixir/Phoenixアドベントカレンダー 初日の記事のラストで phx.gen.docker なるコマンドが生えそうと書いていたのですが、それがすでにmergeされ、Phoenixのv1.6.3でリリースされているようでした。

https://github.com/phoenixframework/phoenix/pull/4609/files

https://github.com/phoenixframework/phoenix/blob/v1.6/CHANGELOG.md#163-2021-12-07

また、それを使ったデモ動画もtweetされています。

https://twitter.com/chris_mccord/status/1468998944009166849?s=20

今回はそんなホットな phx.gen.release を使ってみて、プロジェクトの作成からfly.ioまでのデプロイをやってみたいと思います。

前提

今回の記事ではflyのCLIのインストールやサインアップは済んでいる前提で記述します。

CLIのインストールやサインアップに関してはガイドを参考にしてみてください。

https://zenn.dev/koga1020/books/phoenix-guide-ja-1-6/viewer/fly

余談

ドキュメントを見てると flyflyctl というコマンドが混在している様ですが、

  • flyctl: 実行ファイル
  • fly: flyctl へのシンボリックリンク

となっており、どちらを叩いても同じなようです。ゆくゆくは fly だけになっていきそうな気がしています。

https://community.fly.io/t/whats-the-difference-between-fly-and-flyctl/1377/2

動作環境

$ fly version
fly v0.0.269 darwin/amd64 Commit: 1b5490b BuildDate: 2021-12-10T19:02:43Z
$ mix phx.new --version
Phoenix installer v1.6.4

実践

構成を簡単にするため、今回は --no-ecto で試してみます

$ mix phx.new fly_deploy_demo --no-ecto
* creating fly_deploy_demo/config/config.exs
* creating fly_deploy_demo/config/dev.exs
* creating fly_deploy_demo/config/prod.exs
...中略
* creating fly_deploy_demo/priv/static/robots.txt
* creating fly_deploy_demo/priv/static/images/phoenix.png
* creating fly_deploy_demo/priv/static/favicon.ico

Fetch and install dependencies? [Yn] y
* running mix deps.get
* running mix deps.compile

We are almost there! The following steps are missing:

    $ cd fly_deploy_demo

Start your Phoenix app with:

    $ mix phx.server

You can also run your app inside IEx (Interactive Elixir) as:

    $ iex -S mix phx.server

言われた通り、プロジェクト作成後はディレクトリを移動しましょう。

$ cd fly_deploy_demo

ここで本命の phx.gen.release コマンドを --docker オプション付きで実行します。

$ mix phx.gen.release --docker
* creating rel/overlays/bin/server
* creating rel/overlays/bin/server.bat
* creating Dockerfile
* creating .dockerignore

Your application is ready to be deployed in a release!

See https://hexdocs.pm/mix/Mix.Tasks.Release.html for more information about Elixir releases.

Using the generated Dockerfile, your release will be bundled into
a Docker image, ready for deployment on platforms that support Docker.

For more information about deploying with Docker see
https://hexdocs.pm/phoenix/releases.html#containers

Here are some useful release commands you can run in any release environment:

    # To build a release
    mix release

    # To start your system with the Phoenix server running
    _build/dev/rel/fly_deploy_demo/bin/server

Once the release is running you can connect to it remotely:

    _build/dev/rel/fly_deploy_demo/bin/fly_deploy_demo remote

To list all commands:

    _build/dev/rel/fly_deploy_demo/bin/fly_deploy_demo

リリースに使用するDockerfileを含む、以下のファイルが生成されました。

  • .dockerignore
  • Dockerfile
  • rel/overlays/bin/server
  • rel/overlays/bin/server.bat
diffはこんな感じ
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 0000000..a03cecf
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,18 @@
+.dockerignore
+# there are valid reasons to keep the .git, namely so that you can get the
+# current commit hash
+#.git
+.log
+tmp
+
+# Mix artifacts
+_build
+deps
+*.ez
+releases
+
+# Generate on crash by the VM
+erl_crash.dump
+
+# Static artifacts
+node_modules
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..5812ccc
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,89 @@
+# 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-20210902-slim - for the release image
+#   - https://pkgs.org/ - resource for finding needed packages
+#   - Ex: hexpm/elixir:1.13.0-erlang-24.0.1-debian-bullseye-20210902-slim
+#
+ARG BUILDER_IMAGE="hexpm/elixir:1.13.0-erlang-24.0.1-debian-bullseye-20210902-slim"
+ARG RUNNER_IMAGE="debian:bullseye-20210902-slim"
+
+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
+
+# note: if your project uses a tool like https://purgecss.com/,
+# which customizes asset compilation based on what it finds in
+# your Elixir templates, you will need to move the asset compilation
+# step down so that `lib` is available.
+COPY assets assets
+
+# compile assets
+RUN mix assets.deploy
+
+# Compile the release
+COPY lib lib
+
+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 \
+  && 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
+
+# Only copy the final release from the build stage
+COPY --from=builder --chown=nobody:root /app/_build/prod/rel/fly_deploy_demo ./
+
+USER nobody
+
+CMD /app/bin/server
\ No newline at end of file
diff --git a/rel/overlays/bin/server b/rel/overlays/bin/server
new file mode 100755
index 0000000..e58a784
--- /dev/null
+++ b/rel/overlays/bin/server
@@ -0,0 +1,3 @@
+#!/bin/sh
+cd -P -- "$(dirname -- "$0")"
+PHX_SERVER=true exec ./fly_deploy_demo start
diff --git a/rel/overlays/bin/server.bat b/rel/overlays/bin/server.bat
new file mode 100755
index 0000000..6a07875
--- /dev/null
+++ b/rel/overlays/bin/server.bat
@@ -0,0 +1,2 @@
+set PHX_SERVER=true
+call "%~dp0\fly_deploy_demo" start
\ No newline at end of file

flyのセットアップ

flyの設定を進めます。 fly launch を実行して設定ファイルを生成します。

App Nameを空でEnterを押して進めるとランダムな値が生成されます。 ? Would you like to deploy now? と聞かれますが、このタイミングでは N を押してskipしましょう。

$ fly launch
Creating app in /Users/koga/ghq/github.com/koga1020/fly_deploy_demo
Scanning source code
Detected a Dockerfile app
? App Name (leave blank to use an auto-generated name):
Automatically selected personal organization: Koga Shozo
? Select region: nrt (Tokyo, Japan)
Created app frosty-feather-5742 in organization personal
Wrote config file fly.toml
? Would you like to deploy now? No
Your app is ready. Deploy with `flyctl deploy`

続いて、ガイドと同じように fly.toml を修正します。今回は --no-ecto で実行しているため、 [deploy] の設定は省略しています。

https://zenn.dev/koga1020/books/phoenix-guide-ja-1-6/viewer/fly#fly.toml-のカスタマイズ

# fly.toml file generated for frosty-feather-5742 on 2021-12-11T19:27:06+09:00

# ここは各自設定したApp Nameで読み替えてください
app = "frosty-feather-5742"

kill_signal = "SIGTERM"
kill_timeout = 5

[env]

[[services]]
  internal_port = 4000
  protocol = "tcp"

  [services.concurrency]
    hard_limit = 25
    soft_limit = 20

  [[services.ports]]
    handlers = ["http"]
    port = 80

  [[services.ports]]
    handlers = ["tls", "http"]
    port = 443

  [[services.tcp_checks]]
    grace_period = "30s" # allow some time for startup
    interval = "15s"
    restart_limit = 6
    timeout = "2s"

環境変数の設定

fly secrets コマンドを利用して

  • PHX_HOST
  • SECRET_KEY_BASE

の環境変数を設定します。

# <APP NAME>.fly.dev で読み替えてください
$ fly secrets set PHX_HOST=frosty-feather-5742.fly.dev
Secrets are staged for the first deployment

$ fly secrets set SECRET_KEY_BASE=`mix phx.gen.secret`
Secrets are staged for the first deployment

デプロイの実行

やることはこれだけで、あとはdeployするだけです! fly deploy でコンテナイメージのbuild, pushを行います。

$ fly deploy

1,2分そこら待って、fly status でヘルスチェックを通ったのを確認しましょう。

$ fly status
App
  Name     = frosty-feather-5742
  Owner    = personal
  Version  = 0
  Status   = running
  Hostname = frosty-feather-5742.fly.dev

Deployment Status
  ID          = e621ce72-ba5d-305b-bb79-e9b4d5686503
  Version     = v0
  Status      = successful
  Description = Deployment completed successfully
  Instances   = 1 desired, 1 placed, 1 healthy, 0 unhealthy

Instances
ID       PROCESS VERSION REGION DESIRED STATUS  HEALTH CHECKS      RESTARTS CREATED
e8f35aca app     0       nrt    run     running 1 total, 1 passing 0        1m53s ago

ヘルスチェックが通ったら、 fly open でアプリを開きます。

$ fly open

いつもの画面が見れたらOKです!お疲れ様でした🍵

Image from Gyazo

まとめ

phx.gen.release で生成されたDockerfileを利用してfly.ioへのデプロイまで行なってみました。まっさらなプロジェクトを作成してから、修正したのはtomlファイルのみというのが驚きです!このスピード感は楽しいですね!

生成されたDockerfileをベースに、localeを変更するなり必要に応じてカスタマイズすれば十分使える気がします。

明日は @the_haigo さんの記事です!お楽しみに!

Discussion