Petal Pro を Fly.io にデプロイして超簡単 Web アプリケーション公開

## はじめに

前回の記事で Petal Pro をローカル実行しました

https://qiita.com/RyoWakabayashi/items/64caebfba579a7114e9c

今回はこのアプリを Fly.io にデプロイしてみます

https://fly.io/

ただし、以下の点については開発用のままデプロイします

- [Faker](https://github.com/elixirs/faker) で生成した出鱈目な文字列
- メール送信機能

## Fly.io とは

Fly.io の公式ドキュメントに説明があります

https://fly.io/docs/

> Fly.io: A New Public Cloud

Fly.io はパブリッククラウドサービスで、 Web アプリケーションをデプロイすることでインターネット上に公開、つまり本番運用することが可能です

大きな特長は以下のものが挙げられます

- Docker コンテナで簡単にアプリケーション、環境を定義できる
- Web アプリケーションを最大３つまで無料で運用できる
- PostgreSQL をクラウド上で簡単に安く使える
- CLI で簡単に操作できる

~~今の所、私の不満は以下の点です~~

~~- GPU が使えない（有償でも）~~

GPU が使えるようになりました

https://qiita.com/RyoWakabayashi/items/fd0734234d5d3d30ce0f

### Docker コンテナによるアプリケーション定義

AWS を使う場合、様々なサービスを組み合わせてコストパフォーマンス最適なシステム構成を設計、運用します

そのため、システム構築には AWS 自体への深い理解が必要になります

VPC の設定や、 RDS Proxy を通した DB 接続や、 IAM　の詳細な権限設定など、知らないといけないことが大量にあります

:::note info
[AWS Copilot](https://aws.github.io/copilot-cli/ja/) などを使うことでかなり簡単にはなります
:::

Fly.io では、 `fly launch` コマンドを実行するだけで Dockerfile を自動生成してくれる上に、 DB との接続などなどもよしなにやってくれます

`fly deploy` コマンドだけでデプロイでき、コンテナのビルドもリモートで実行するため開発者のマシンに Docker がなくても問題ありません

開発者は AWS Lambda 以上にインフラを意識しないでシステム構築可能です

### 無償利用枠

Fly.io には無償利用枠があり、これを使うことで無料で運用可能です

https://fly.io/docs/about/pricing/#free-allowances

- ３つまでの shared-cpu-1x 256MB VM （アプリケーションと PostgreSQL などのマシン合わせて）
- 合計 3 GB までの永続ストレージ
- 160 GB までのアウトバウンド（インターネットへの）データ転送（インバウンドは完全無料）

データベースが不要のアプリケーション（単純な Web サイトなど）であれば３つ運用できます

データベースを使うアプリケーションの場合、アプリケーション１つ、PostgreSQL1つで運用可能です

### PostgreSQL

これが AWS と比べて非常に魅力的です

データベース不要のアプリケーションであれば、 AWS でも S3 + API Gateway + Lambda などで実質無料運用可能です

データベースが必要な場合も Amazon DynamoDB を使えばかなり低コストで運用できますが、 NoSQL なので要件の適合性や特有の知識、設計が必要になります

RDB = リレーショナルデータベースを安く使いたいとなると Aurora Serverless が選択肢に入ってきますが、それでもそれなりに費用が発生します

Fly.io であれば、 PostgreSQL が簡単に使える上、前述したように1つだけなら無料です

技術検証や開発期間中ならかなりの低コスト（実質無料）で運用できるわけです

### CLI による操作

flyctl を使うことで、かなり簡単に操作できます

https://fly.io/docs/flyctl/

詳細は後述します

## アカウント作成からデプロイまで

公式ドキュメントに Speedrun! Deploying to Fly.io! があります

https://fly.io/docs/speedrun/

これが一番速いと思います

以降の操作は Petal Pro のプロジェクトディレクトリー直下で実行します

### flyctl のインストール

https://fly.io/docs/hands-on/install-flyctl/

macOS であれば Homebrew でインストール可能です

```bash
brew install flyctl
```

Linux の場合

```bash
curl -L https://fly.io/install.sh | sh
```

Windows の場合

```bash
powershell -Command "iwr https://fly.io/install.ps1 -useb | iex"
```

## アカウント作成、ログイン

以下のコマンドを実行します

```bash
fly auth login
```

ブラウザで以下のような画面が開くので、 GitHub でサインインするか Need an Account? のリンクからアカウントを作ってください

![fly_login.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/6b34e07c-313f-7892-43e4-e529118a625e.png)

:::note warn
ログインまでに時間がかかりすぎるとエラーになるので、その場合は再実行してください
:::

## アプリケーションの作成

以下のコマンドを実行します

```bash
fly launch
```

すると、しばらくして、このプロジェクトが Phoenix フレームワークであることを察してくれます

```bash
...
All dependencies are up to date
Detected a Phoenix app
? Choose an app name (leave blank to generate one): 
```

今回は適当に決めてもらうので、そのまま ENTER を押します

続いて地域を聞かれます

```bash
? Choose a region for deployment:  [Use arrows to move, type to filter]
  Ashburn, Virginia (US) (iad)
  Johannesburg, South Africa (jnb)
  Los Angeles, California (US) (lax)
  London, United Kingdom (lhr)
  Chennai (Madras), India (maa)
  Madrid, Spain (mad)
  Miami, Florida (US) (mia)
> Tokyo, Japan (nrt)
  Chicago, Illinois (US) (ord)
  Bucharest, Romania (otp)
  Querétaro, Mexico (qro)
  Santiago, Chile (scl)
  Seattle, Washington (US) (sea)
  Singapore, Singapore (sin)
  San Jose, California (US) (sjc)
```

Tokyo, Japan (nrt) を選択して ENTER を押します

適当な名前でアプリケーションが作られ、データベースを作るか聞かれます

```bash
Created app snowy-pine-3701 in organization personal
Admin URL: https://fly.io/apps/hoge-hoge-1234
Hostname: hoge-hoge-1234.fly.dev
Set secrets on hoge-hoge-1234: SECRET_KEY_BASE
? Would you like to set up a Postgresql database now? (y/N) 
```

使うので y を入れてから ENTER を押します

次はコンテナを動かす VM の性能を選択します

```bash
? Select configuration:  [Use arrows to move, type to filter]
> Development - Single node, 1x shared CPU, 256MB RAM, 1GB disk
  Production - Highly available, 2x shared CPUs, 4GB RAM, 40GB disk
  Production - Highly available, 4x shared CPUs, 8GB RAM, 80GB disk
  Specify custom configuration
```

お試しなので Development を選択して ENTER を押します

PostgreSQL　のマシンが作成されます

続いて Upstash Redis を作るか聞いてきますが、これは不要なのでそのまま ENTER を押します

```bash
? Would you like to set up an Upstash Redis database now? (y/N)
```

最後にそのままデプロイするか聞いてきます

```bash
? Would you like to deploy now? (y/N) 
```

少し準備が必要なので、ここではそのまま ENTER を押します

この時点で Dockerfile が自動的に生成されています

```Dockerfile
# 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-20230109-slim - for the release image
#   - https://pkgs.org/ - resource for finding needed packages
#   - Ex: hexpm/elixir:1.14.3-erlang-25.2.2-debian-bullseye-20230109-slim
#
ARG ELIXIR_VERSION=1.14.3
ARG OTP_VERSION=25.2.2
ARG DEBIAN_VERSION=bullseye-20230109-slim

ARG BUILDER_IMAGE="hexpm/elixir:${ELIXIR_VERSION}-erlang-${OTP_VERSION}-debian-${DEBIAN_VERSION}"
ARG RUNNER_IMAGE="debian:${DEBIAN_VERSION}"

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

COPY lib lib

COPY assets assets

# compile assets
RUN mix assets.deploy

# Compile the release
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

# set runner ENV
ENV MIX_ENV="prod"

# Only copy the final release from the build stage
COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/my_app ./

USER nobody

CMD ["/app/bin/server"]

# Appended by flyctl
ENV ECTO_IPV6 true
ENV ERL_AFLAGS "-proto_dist inet6_tcp"
```

Dockerfile 内で `mix release` などが動くようになっています

Dockerfile すら自分で考えなくて良いとは恐れ入りました

## コードの修正

開発環境用のままデプロイしたいので、少し修正を加えます

### Faker のインストール

今回は Faker　の出鱈目文字をそのままにしておくので、本番環境でも Faker をインストールするようにします

mix.exs

```diff_elixir
-      {:faker, "~> 0.17", only: [:test, :dev]},
+      {:faker, "~> 0.17"},
```

### メールボックスのルーティング設定

ダミーのメールボックスを使うため、ルーティング設定を変更します

/Users/oec/dev/petal_pro/lib/my_app_web/routes/dev_routes.ex

```diff_elixir
-      if Mix.env() in [:dev, :test] do
-        scope "/" do
-          forward "/dev/mailbox", Plug.Swoosh.MailboxPreview
-        end
-
+      scope "/" do
+        forward "/dev/mailbox", Plug.Swoosh.MailboxPreview
+      end
+
+      if Mix.env() in [:dev, :test] do
```

### SES の無効化

本番環境では Amazon SES でメール送信するようになっていますが、今回は無効化します

config/runtime.exs

```diff_elixir
-
-  config :my_app, MyApp.Mailer,
-    adapter: Swoosh.Adapters.AmazonSES,
-    region: System.get_env("AWS_REGION"),
-    access_key: System.get_env("AWS_ACCESS_KEY"),
-    secret: System.get_env("AWS_SECRET")
```

### 開発環境用表示

メールボックスへのリンクを表示するようにします

lib/my_app_web/live/auth/user_confirmation_instructions_live.ex

```diff_elixir

-      <%= if MyApp.config(:env) == :dev do %>
        <div class="fixed mt-10 left-10 bottom-10">
          <.alert color="warning">
            DEV ONLY:
            <.link href="/dev/mailbox" class="underline">Go to mailbox</.link>
          </.alert>
        </div>
-      <% end %>
    </.auth_layout>
```

### データベースユーザーの権限変更

:::note info
2023/03/02

マイグレーションが動かない問題は解消されていたので、この操作は不要になりました
:::

おそらくこれはどこかのバグだと思いますが、そのままデプロイするとマイグレーション（データベースの初期化）で権限不足によるエラーが発生します

そこで、データベースユーザーの権限を変更してエラーを回避します

以下のコマンドで PostgreSQL に接続します

```bash
fly postgres connect -a hoge-hoge-1234-db
```

接続されたら `\du+` でユーザー一覧を確認します

```
postgres=# \du+
                                                           List of roles
    Role name    |                         Attributes                         |              Member of               | Description 
-----------------+------------------------------------------------------------+--------------------------------------+-------------
 flypgadmin      | Superuser                                                  | {}                                   | 
 postgres        | Superuser, Create role, Create DB, Replication, Bypass RLS | {}                                   | 
 repmgr          | Superuser                                                  | {}                                   | 
 hoge_hoge_1234  |                                                            | {pg_read_all_data,pg_write_all_data} | 
```

hoge_hoge_1234（例） がシステムで使うユーザーです

これを Superuser に変更してしまいます（実際の運用ではこの辺りを適切に変更してください）

```bash
postgres=# ALTER USER hoge_hoge_1234 WITH SUPERUSER;
ALTER ROLE
```

PostgreSQL から切断します

```bash
postgres=# exit
```

## デプロイ

以下のコマンドでデプロイします

```bash
fly deploy
```

しばらくして、以下のような文言が表示されればデプロイ成功です

```bash
 1 desired, 1 placed, 1 healthy, 0 unhealthy [health checks: 1 total, 1 passing]
--> v0 deployed successfully
```

## アプリケーションの表示

以下のコマンドを実行すると、ブラウザでアプリケーションを開きます

```bash
fly open
```

ローカル実行で動いていたのと同じ画面が表示され、ユーザー登録やログインができるはずです

![petal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1485835/e13ee908-a44a-9c25-900f-51b47d6d10c8.png)

## アプリケーションのログ監視

以下のコマンドでアプリケーションのログを表示可能です

```bash
fly logs
```

## 独自ドメインの利用

独自ドメインを使うのも簡単にできます

https://fly.io/blog/how-to-custom-domains-with-fly/

独自ドメインを使う場合、以下の Petal Pro 公式ドキュメントを参考にドメイン関係の設定を変更してください

https://docs.petal.build/petal-pro-documentation/fundamentals/deployment

## まとめ

開発環境用のままデプロイするため色々修正しましたが、それを除けばかなり簡単でした

以前、 AWS に Phoenix アプリケーションをデプロイしようとして色々苦労していましたが、 Fly.io だとほとんど何も考えずにできました

https://qiita.com/RyoWakabayashi/items/9ea90df3970ec6e9d272

上記の記事は認証に Auth0 を使ったりしたのもありますが、 CloudFront で WebSocket 通信するための設定など、結構考えなければならないことがありました

Petal Pro + Fly.io であれば、このような基盤部分にかける時間もお金もかなり節約できます

SES によるメール送信や Route53 による DNS 、あるいは SageMaker による AI 推論など、まさにマイクロサービスとして必要な機能だけを AWS に任せて、基盤は Fly.io を使う、という構成はかなり有用な気がします

少なくとも、 Phoenix を使うのであれば Fly.io は最適な選択肢の一つと言えます

ただし、 Petal Pro 自体がまだまだ成長中なので、いくつか上げてきたような問題（GitHub認証など）がまだ発生している、という点だけは注意が必要です