20
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

ElixirAdvent Calendar 2022

Day 12

AWS Copilot で Elixir + Phoenix Framework の Web サイトを公開する

Last updated at Posted at 2022-05-13

はじめに

Phoenix Framework で作った Web サイトをとりあえず AWS 上で公開したい!

でも、 EC2 でサーバーを立てたり SSH 接続して環境構築とかしたくない!

ちょっと動かしたいだけなので Ansible まで使いたくない!

AWS コンソールから VPC の設定とかもしたくない!

かといって CloudFormationTerraform も面倒臭い!

という人向けの記事です

AWS Copilot を使えば何も考えずに Phoenix が AWS 上で動きます

※本番環境用となるとクラスタリングだったり、 mix release したりとかあるので、
あくまでも簡単に立ち上げるだけです

※ある程度 AWS や Docker の知識があることを前提としています

AWS Copilot 公式はこちら

本記事で実装したコードはこちら

AWS Copilot とは

Copilot = 副操縦士の名前の通り、 AWS 上のシステム構築を補助してくれるツールです

GitHub Copilot も優秀ですが、この副操縦士も超優秀です

「こんな感じでやっといて」で1から10まで全部やってしまいます

本記事で言うと、 Docker コンテナさえ用意しておけば、
ECSVPCALB などをいい感じに設定してくれます

AWS Copulot CLI のインストール方法はこちら

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

こちらのリポジトリーに沿って手順を説明します

まず、公開したい Web サイトを作ります

ローカル環境構築

私は普段から asdf を愛用しているので、 asdf で Elixir をインストールします

asdf を使わない場合は Elixir 1.13.4 OTP 24 を何らかの手段でインストールしてください

asdf plugin add elixir
asdf install

これで .too-versions に記載されている Elixir 1.13.4 OTP 24 がインストールできます

次に Phoenix を使うための準備をします

mix local.hex --force \
  && mix archive.install hex phx_new --force \
  && mix local.rebar --force

Phoenix プロジェクトの作成

上記リポジトリーではすでに作成済なので、同じプロジェクトを使う場合はこの手順を飛ばしてください

DB は使わないので、

mix phx.new sample_app --no-ecto

で Phoenix のプロジェクトを作成します

また、外部からアクセスしたいので sample_app/dev.exs の 12行目を変更します

変更前

  http: [ip: {127, 0, 0, 1}, port: 4000],

変更後

  http: [ip: {0, 0, 0, 0}, port: 4000],

ローカルでの Phoenix 起動

sample_app ディレクトリーに移動して、 Phoenix を起動します

cd sample_app \
  && mix setup \
  && mix phx.server

以下のような表示が出たら http://localhost:4000 にアクセスしてください

[info] Running SampleAppWeb.Endpoint with cowboy 2.9.0 at 0.0.0.0:4000 (http)
[info] Access SampleAppWeb.Endpoint at http://localhost:4000
[debug] Downloading esbuild from https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.29.tgz
[watch] build finished, watching for changes...

このような画面が表示されれば OK です

スクリーンショット 2022-05-12 13.26.19.png

control + c で中断し、 a を入力して Phoenix を終了させます

Docker での起動

次に Docker 上で動かしてみます

ローカルでインストールした依存パッケージやビルドしたファイルを削除してから、ルートディレクトリーに戻ります

rm -rf deps _build \
  && cd ..

コンテナは Dockerfile に以下のように定義しています

FROM elixir:1.13.4

RUN mix local.hex --force \
  && mix archive.install hex phx_new --force \
  && mix local.rebar --force

COPY ./sample_app /app

WORKDIR /app

RUN mix deps.get

RUN mix compile.phoenix

EXPOSE 4000

CMD ["mix", "phx.server"]

デプロイ後、ヘルスチェックにすぐ応えられるよう
コンテナビルド時に mix compile.phoenix まで実行しています

また、 EXPOSE 4000 としておけば、 Copilot が 4000 番ポートを使うことを察してくれます

docker-compose.yml は以下のように定義しています

---

version: '3.2'
services:
  web:
    container_name: phoenix
    build: .
    ports:
      - '4000:4000'
    command: mix phx.server
    environment:
      - MIX_ENV=dev

では、 docker compose で動かしてみましょう

docker compose up

以下のような表示が出たら再び http://localhost:4000 にアクセスしてください

phoenix  | [info] Running SampleAppWeb.Endpoint with cowboy 2.9.0 at 0.0.0.0:4000 (http)
phoenix  | [info] Access SampleAppWeb.Endpoint at http://localhost:4000
phoenix  | [watch] build finished, watching for changes...

このような画面が表示されれば OK です(ローカル実行と同じ)

スクリーンショット 2022-05-12 13.26.19.png

control + c でコンテナを停止させます

AWS Copilot による AWS へのデプロイ

さあ、いよいよ AWS Copilot の出番です

少なくとも AWS アカウントと、操作を実行するための IAM ユーザーが必要になります

IAM ユーザーのパーミッション

管理者権限を持っていて何でもできる IAM ユーザーであれば気にする必要はありませんが、
そうでない場合、以下のようなパーミッションが必要になります

※App Runner でのデプロイ時だけ使うパーミッションも含んでいます
※過剰になっている箇所があるかもしれません

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "Fulls",
            "Effect": "Allow",
            "Action": [
                "cloudformation:*"
                "cloudtrail:LookupEvents",
                "codebuild:ListProjects",
                "codestar-connections:CreateConnection",
                "codestar-connections:ListConnections",
                "codestar-connections:PassConnection",
                "codestar-connections:TagResource",
                "codestar-connections:UntagResource"
                "iam:*",
                "ec2:*",
                "ecr:*",
                "ecs:CreateCluster",
                "s3:*",
                "secretsmanager:DescribeSecret",
                "servicediscovery:*"
            ],
            "Resource": "*"
        },
        {
            "Sid": "ECRCreateRole",
            "Effect": "Allow",
            "Action": [
                "iam:CreateServiceLinkedRole"
            ],
            "Resource": "*",
            "Condition": {
                "StringEquals": {
                    "iam:AWSServiceName": [
                        "replication.ecr.amazonaws.com"
                    ]
                }
            }
        },
        {
            "Sid": "CodeStarConnections",
            "Effect": "Allow",
            "Action": [
                "codestar-connections:DeleteConnection",
                "codestar-connections:GetConnection"
            ],
            "Resource": [
                "arn:aws:codestar-connections:*:<アカウントID>:connection/*"
            ]
        },
        {
            "Sid": "CodeBuild",
            "Effect": "Allow",
            "Action": [
                "codebuild:CreateProject",
                "codebuild:DeleteProject",
                "codebuild:UpdateProject"
            ],
            "Resource": [
                "arn:aws:codebuild:*:<アカウントID>:project/*"
            ]
        },
        {
            "Sid": "ECS",
            "Effect": "Allow",
            "Action": [
                "ecs:DeleteCluster",
                "ecs:DescribeClusters"
            ],
            "Resource": "arn:aws:ecs:*:<アカウントID>:cluster/*"
        },
        {
            "Sid": "SSM",
            "Effect": "Allow",
            "Action": [
                "ssm:DeleteParameter",
                "ssm:DeleteParameters",
                "ssm:GetParameter",
                "ssm:GetParameters",
                "ssm:GetParametersByPath",
                "ssm:PutParameter"
            ],
            "Resource": "arn:aws:ssm:*:<アカウントID>:parameter/*"
        },
        {
            "Sid": "STS",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "arn:aws:iam::<アカウントID>:role/*"
        }
    ]
}

アプリケーションの初期化

アプリケーションとサービスを作成します

※作成しても、まだデプロイはしていない状態です

$ copilot init \
    --app sample-app \
    --name lb-svc \
    --type "Load Balanced Web Service"
Welcome to the Copilot CLI! We're going to walk you through some questions
to help you get set up with a containerized application on AWS. An application is a collection of
containerized services that operate together.

Manifest file for service lb-svc already exists. Skipping configuration.
Ok great, we'll set up a Load Balanced Web Service named lb-svc in application sample-app.

⠴ Creating the infrastructure to manage services and jobs under application sample-app.

sample-app がアプリケーション名、 lb-svc がサービス名です

--type には以下のものを指定可能です

  • "Request-Driven Web Service"
  • "Load Balanced Web Service"
  • "Backend Service"
  • "Worker Service"
  • "Scheduled Job"

"Request-Driven Web Service" は AWS App Runner ですが、
Web Socket に未対応なので使えません

"Load Balanced Web Service" が ALB の後ろにコンテナを動かす Web サービスなので、
これを選択しています
(今回は1台なのでロードバランサーの意味はないですが、、、)

しばらく待つと以下のように質問されます

All right, you're all set for local development.

  Would you like to deploy a test environment? [? for help] (y/N)

CloudWatchContainer Insights を使いたいので、ここでは N を入力してください

次に test 環境を作成します

<プロファイル名> の部分だけ自分のプロファイルに変更して実行してください

copilot env init \
  --name test \
  --profile <プロファイル名> \
  --default-config \
  --container-insights

作成に数分かかるため、しばらく待ちます

以下のような表示が出れば test 環境が作成できています

✔ Proposing infrastructure changes for the sample-app-test environment.
- Creating the infrastructure for the sample-app-test environment.            [create complete]  [93.7s]
  - An IAM Role for AWS CloudFormation to manage resources                    [create complete]  [30.3s]
  - An ECS cluster to group your services                                     [create complete]  [9.4s]
  - An IAM Role to describe resources in your environment                     [create complete]  [28.5s]
  - A security group to allow your containers to talk to each other           [create complete]  [6.1s]
  - An Internet Gateway to connect to the public internet                     [create complete]  [18.2s]
  - Private subnet 1 for resources with no internet access                    [create complete]  [4.3s]
  - Private subnet 2 for resources with no internet access                    [create complete]  [4.3s]
  - A custom route table that directs network traffic for the public subnets  [create complete]  [15.3s]
  - Public subnet 1 for resources that can access the internet                [create complete]  [5.7s]
  - Public subnet 2 for resources that can access the internet                [create complete]  [5.7s]
  - A private DNS namespace for discovering services within the environment   [create complete]  [50.2s]
  - A Virtual Private Cloud to control networking of your AWS resources       [create complete]  [12.8s]
✔ Created environment test in region ap-northeast-1 under application sample-app.

例えば、 ECS が以下のようにクラスターが作成されています

まだタスクは1つも実行されていない状態です

スクリーンショット 2022-05-12 14.14.58.png

アプリケーションのデプロイ

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

copilot svc deploy \
  --name lb-svc \
  --env test

途中で以下のような表示が出ます

  - An ECS service to run and maintain your tasks in the environment cluster      [create in progress]  [17.3s]
    Deployments                                                                                         
               Revision  Rollout        Desired  Running  Failed  Pending                                       
      PRIMARY  5         [in progress]  1        0        0       1                                             

これは保留中のタスク(コンテナ)が1台、と言う意味で、コンテナが起動してヘルスチェックに成功すれは以下のような表示に切り替わります

  - An ECS service to run and maintain your tasks in the environment cluster      [create complete]  [100.5s]
    Deployments                                                                                       
               Revision  Rollout        Desired  Running  Failed  Pending                                     
      PRIMARY  5         [in progress]  1        1        0       0                                           

その後、以下のように表示されるので、 http://sampl- から始まる URL にアクセスしてください

✔ Deployed service lb-svc.
Recommended follow-up action:
  - You can access your service at http://sampl-Publi-xxx-1316580721.ap-northeast-1.elb.amazonaws.com over the internet.

このような画面が表示されれば OK です(ローカル実行と同じ)

スクリーンショット 2022-05-12 13.26.19.png

ついでに LiveDashboard も見てみましょう

スクリーンショット 2022-05-12 14.29.05.png

開発者ツールを使ってみれば、 WebSocket 通信できていることが確認できます

スクリーンショット 2022-05-12 14.30.24.png

Container Insights を有効にしたため、 Cloud Watch からもコンテナの状態を確認できます

スクリーンショット 2022-05-12 15.04.09.png

アプリケーションの更新

では、少し変更してデプロイしてみましょう

sample_app/lib/sample_app_web/templates/page/index.html.heex の先頭を以下のように変えてみます

name: "Phoenix" の部分を name: "AWS" に変更しています

<section class="phx-hero">
  <h1><%= gettext "Welcome to %{name}!", name: "AWS" %></h1>
  <p>Peace of mind from prototype to production</p>
</section>

この状態で、もう一度デプロイコマンドを実行します

copilot svc deploy \
  --name lb-svc \
  --env test

コンテナのビルドが走り、再びデプロイが開始されます

今度は以下のような表示になります

  - An ECS service to run and maintain your tasks in the environment cluster  [update in progress]  [7.8s]
    Deployments                                                                                     
               Revision  Rollout        Desired  Running  Failed  Pending                                   
      PRIMARY  6         [in progress]  1        0        0       1                                         
      ACTIVE   5         [completed]    1        1        0       0                                         

現在1台コンテナが動いていて、裏で新しい1台が起動しようとしています

しばらくすると以下のようになり、

  - An ECS service to run and maintain your tasks in the environment cluster  [update in progress]  [115.7s]
    Deployments                                                                                     
               Revision  Rollout        Desired  Running  Failed  Pending                                   
      PRIMARY  6         [in progress]  1        1        0       0                                         
      ACTIVE   5         [completed]    1        1        0       0                                         

こうなって、

  - An ECS service to run and maintain your tasks in the environment cluster  [update in progress]  [210.3s]
    Deployments                                                                                     
               Revision  Rollout        Desired  Running  Failed  Pending                                   
      PRIMARY  6         [in progress]  1        1        0       0                                         
      ACTIVE   5         [completed]    1        0        0       0                                         

こうなります

  - An ECS service to run and maintain your tasks in the environment cluster  [update in progress]  [258.0s]
    Deployments                                                                                     
               Revision  Rollout        Desired  Running  Failed  Pending                                   
      PRIMARY  6         [in progress]  1        1        0       0                                         

つまり、旧バージョンが動いている裏で新バージョンを起動させ、起動し終わってから新バージョンに切り替えているわけです

これによって無停止でのデプロイができるようになっています

最終的に以下のように表示されれば更新完了です

  - An ECS service to run and maintain your tasks in the environment cluster  [update complete]  [258.9s]
    Deployments                                                                                   
               Revision  Rollout      Desired  Running  Failed  Pending                                   
      PRIMARY  6         [completed]  1        1        0       0                                         
  - An ECS task definition to group your containers and run them on ECS       [delete complete]  [0.0s]
✔ Deployed service lb-svc.
Recommended follow-up action:
  - You can access your service at http://sampl-Publi-xxx-1316580721.ap-northeast-1.elb.amazonaws.com over the internet.

URL にアクセスすると表示が更新されています

スクリーンショット 2022-05-12 14.45.26.png

以降は copilot svc deploy ... を実行するだけでデプロイできるので、非常に楽ですね

リソースの変更

copilot/lb-svc/manifest.yml の cpumemory を変更することで、
コンテナに割り当てるリソースを調整することができます

ただし、設定できる値の組み合わせには制限があります

cpu: 256       # Number of CPU units for the task.
memory: 512    # Amount of memory in MiB used by the task.
count: 1       # Number of tasks that should be running in your service.

アプリケーションの削除

以下のコマンドでアプリケーション、環境、サービスを全て削除できます

アプリケーションを削除しないと課金され続けるので注意してください

> copilot app delete
...
✔ Deleted service lb-svc from environment test.
✔ Deleted resources of service lb-svc from application sample-app.
✔ Deleted service lb-svc from application sample-app.
✔ Deleted environment test from application sample-app.
✔ Cleaned up deployment resources.
✔ Deleted application resources.
✔ Deleted application configuration.
✔ Deleted local .workspace file.

おわりに

Elixir と言えば、昔は edeliver を使ってデプロイしていて、
これがややこしかったり、途中でエラーになったりでリリース作業が毎回嫌でした

その後 mix release ができて改善されたものの、
この記事この記事で書いたようなエラーが度々発生しているので、
未だに、特に久しぶりのリリース作業だと嫌になります

AWS Copilot を使って ECS で動かすようにしてしまえば、
リリース関連の不安からはキレイサッパリ解放されるので、
この方法を本番運用に使えるように試行錯誤していきたいです

20
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?