AWS Fargate で、Deno を動かす

Deno の動作環境として、Amazon Linux 2(EC2 環境)で動かすことをしてみました。
今度はサーバーレスコンピューティング試したくなりました。
AWS では、コンテナ向けサーバーレスコンピューティングサービスとして Fargate があるので、これを試します。

参考

環境構築 1

Fargate を触るのが初めてなので、調べつつまずは良くサンプルとして見かける nginx をまず動かしてみます。

IAM の設定 1

ECR に Github Actions を使ってコンテナイメージを登録するに当たり、アクセス権の設定が必要です。
IAM の設定をします。

  • 任意の名称でユーザー名を設定(今回は、deploy-user とした。)
  • アクセスキー・プログラムのによるアクセスにチェックを入れる
  • 既存のポリシーを直接アタッチ
    • AmazonEC2ContainerRegistryPowerUser を付与
  • タグの設定は不要
  • アクセスキーと、シークレットアクセスキーが表示されるので控える(Github Actions 設定に使用する)

ECR リポジトリの作成

AWS ECR(Elastic Container Registry)は、Docker イメージをホストしてくれるコンテナレジストリ。

  • パブリックリポジトリにする理由は無いのでプライベートに
  • 任意の名前でリポジトリ名を設定(今回は、nginx-container-image とした。)(Github Actions 設定に使用する)

github リポジトリの設定

  • 任意の名称でリポジトリを作成する。
  • Settings を開く
  • Secrets に値を以下の名称で登録していく。
    • AWS_ACCESS_KEY_ID:IAM の設定で作成したユーザーのアクセスキー
    • AWS_SECRET_ACCESS_KEY:IAM の設定で作成した湯ユーザーのシークレットアクセスキー
    • AWS_ECR_REPO_NAME:ECR リポジトリの作成で設定したリポジトリ名

Github Actions での ECR へのイメージ登録

ECR に Fargate で起動させる Docker イメージを登録する。

ディレクトリの構成は次の通り。

1
2
3
4
5
6
7
$ tree .
|-- .github
| `-- workflows
| `-- deploy.yml
|-- dockerfile
|-- index.html
`-- README.md

個別のファイルの内容を見ていきます。
dockerfile は、次の通り。

dockerfile
1
2
3
4
5
FROM nginx:latest

COPY index.html /usr/share/nginx/html

EXPOSE 80

標準のものと差し替えるページは次の通り。

1
2
3
4
5
6
7
8
9
10
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>INDEX Page</title>
</head>
<body>
<h1>INDEX Page</h1>
</body>
</html>

ここまでが起動するイメージに関連するもの。
以下から、github ワークフローーの設定。 .github/workflows/deploy.yml は以下の通り。

.github/workflows/deploy.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
name: nginx-test
on:
push:
tags:
- deploy-*

jobs:
build-and-push:
runs-on: ubuntu-18.04
timeout-minutes: 300

steps:
- uses: actions/checkout@v1

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1

- name: Build, tag, and push image to Amazon ECR
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
run: |
IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

(Github Actions への知見が全然ないので、後日その他のものも試しておきたい。)

次のコマンドで、タグの push まで行う。

1
2
3
4
5
$ git add -A
$ git commit -m "[任意のメッセージ]"
$ git push
$ git tag deploy-hogehoge # <= deploy- に続く文字列でタグが振られていればECRへのpushが行われるようになっている
$ git push --tags

github リポジトリの actions のページを見ると、workflows の一覧で動作が確認できる。
(実はここでリージョンの記載が間違っていたので 2 週間くらい悩み続けた。)

ECR リポジトリの作成 で作ったリポジトリを確認すると、deploy-hogehoge のタグを振られたイメージが確認できる。

ALB を作成する

Fargate で管理しているコンテナ群へのアクセスする口になる ALB を作る。

  • EC2 -> Load Balancers からロードバランサーの作成
  • Application Load Balancer を選ぶ
  • 基本設定
    • 任意の名称を設定(今回は、alb-for-fargate とした。)
    • Schema:Internet-facing
    • IP addresss type:IPv4
  • ネットワーク設定
    • VPC:デフォルト
    • Mappings:任意(今回は、a と c にした)
  • セキュリティグループ
    • 新規に作成して割り当て
      • セキュリティグループ名:任意(今回は、alb-for-fargate-sg とした)
      • 説明:任意
      • インバウンドルール:HTTP 0.0.0.0/0 を追加
  • ルーティング
    • 空で設定をしておけないので、仮で新規にターゲットグループを作成する
      • Basic configuration
        • Choose a target type:Instances
        • Target group name:TMP
        • Health checks:HTTP /
      • 特にインスタンスを登録せずに保存
    • 仮設定をして保存

Fargate 互換形式で、ECS タスクとコンテナ定義

  • 「新しいタスク定義の作成」から作成
  • FARGATE を選ぶ
  • タスクとコンテナの定義の設定
    • タスク定義名:任意(今回は、nginx-task とした)
    • タスクロール:なし
    • オペレーティングシステムファミリー:Linux
    • タスクメモリ:最小
    • タスク CPU:最小
    • コンテナの定義
      • 「コンテナの追加」から追加
        • コンテナ名:任意(今回は、nginx-container とした。)
        • イメージ:ECR リポジトリの作成で作った 「レジストリ URL/リポジトリ名:タグ」を設定
        • ポートマッピング:80

ECS クラスターを作成

  • 「クラスターの作成」から作成
  • ネットワーキングのみを選ぶ
  • クラスター名:任意(今回は、cluster-for-fargate とした)

ECS クラスターにサービスを作成

  • 「クラスターを作成」で作ったクラスタのサービスから作成
  • サービスの設定
    • 起動タイプ:FARGATE
    • タスク定義:「Fargate 互換形式で、ECS タスクとコンテナ定義」で作成したタスクを設定
    • サービス名:任意(今回は、cluster-for-fargate-service とした。)
    • タスク数:任意(今回は、2 にした)
  • ネットワーク構成
    • VPC とセキュリティグループ
      • クラスター VPC:デフォルト作成済みのものを使った
      • サブネット:「ALB を作成する」で a と c を設定しているので、同じ方針
      • セキュリティグループ:新しいセキュリティグループの作成
        • 「ALB を作成する」で作成したセキュリティグループをソースグループに割り当て
      • パブリック IP の自動割り当て:ENABLED(DISABLED にすると、コンテナ起動時の pull に問題が発生した)
    • ロードバランシング
      • ロードバランサーの種類*:Application Load Balancer
      • ロードバランサー名:「ALB を作成する」で作成した ALB
    • ロードバランス用のコンテナ
      • ロードバランサーに追加
        • プロダクションリスナーポート:80:HTTP
        • ターゲットグループ名:任意(今回は、cluster-for-fargate-tg とした。)
        • パスパターン:/*
        • 評価順:1
        • ヘルスチェックパス:/

ALB に割り当てるターゲットグループ修正

  • 仮設定したターゲットグループを削除
  • 「ECS クラスターにサービスを作成」で作成したターゲットグループを割り当て

動作確認 1

  • ALB の「DNS 名」でアクセスすると、イメージに含めたページがレスポンスで返ってくる
  • クラスターの個別のタスクに振られているパブリック IP ではアクセスできない

の 2 つが確認できました。
無事 AWS Fargate で nginx のイメージを登録してデプロイできました。

デプロイを自動化

github へのイメージの push はできたものの、デプロイするイメージを更新する「新しいリビジョンの追加」が自動化できていません。
イメージのタグを書き換える操作が毎度面倒なのでデプロイまで自動化してみます。

IAM の設定 2

タスクを展開する為の権限として、AmazonECS_FullAccess を IAM ユーザーに付与した。
最小権限を維持するためいろいろ試したが埒が明かないので今回はこの通り。

タスクをデプロイする Github Actions を設定

追加で 2 つの github action を使用する。

前者が設定ファイルになる task-definition.json とデプロイの中で確定する設定などをマージし、新しい設定を返すもの。
後者がデプロイ処理本体。

これらを取り扱うため、.github/workflows/deploy.yml は次のように書き換える。

.github/workflows/deploy.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
name: nginx-test
on:
push:
tags:
- deploy-*

jobs:
build-and-push:
runs-on: ubuntu-18.04
timeout-minutes: 300

steps:
- uses: actions/checkout@v1

- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1

- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1

- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: ${{ secrets.AWS_ECR_REPO_NAME }}
run: |
IMAGE_TAG=$(echo ${{ github.ref }} | sed -e "s#refs/tags/##g")
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
# 以下が追記部分
- name: Fill in the new image ID in the Amazon ECS task definition
id: task-def
uses: aws-actions/amazon-ecs-render-task-definition@v1
with:
task-definition: task-definition.json
container-name: nginx-container # <= secretsに定義にしといてイイと思う
image: ${{ steps.build-image.outputs.image }}

- name: Deploy Amazon ECS task definition
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
with:
task-definition: ${{ steps.task-def.outputs.task-definition }}
service: cluster-for-fargate-service # <= secretsに定義にしといてイイと思う
cluster: cluster-for-fargate # <= secretsに定義にしといてイイと思う
wait-for-service-stability: true

記述については、Dockerfile とタスク定義の変更を既存の Fargate に自動デプロイする Github Actions を設定したを参考にさせていただいた。

続けて、ベースの設定になる task-definition.json は以下の通り。

task-definition.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
{
"ipcMode": null,
"executionRoleArn": "arn:aws:iam::XXXXXXXXXXXXX:role/ecsTaskExecutionRole",
"containerDefinitions": [
{
"logConfiguration": {
"logDriver": "awslogs",
"options": {
"awslogs-group": "/ecs/nginx-task",
"awslogs-region": "ap-northeast-1",
"awslogs-stream-prefix": "ecs"
}
},
"portMappings": [
{
"hostPort": 80,
"protocol": "tcp",
"containerPort": 80
}
],
"cpu": 0,
"name": "nginx-container"
}
],
"memory": "512",
"family": "nginx-task",
"requiresCompatibilities": ["FARGATE"],
"networkMode": "awsvpc",
"cpu": "256"
}

記載については、既存のタスク定義を開くと JSON を参照できるので、そこからコピーさせていただく。
null の箇所などは、すべて削った。

動作確認 2

改めて、同じ方法で github へ push する。
actions の動作状況を見ると、定義したタスクが実行されているのが見える。
ECR へのイメージ登録だけだった時より各段に時間がかかるようになっていた。
(当たり前のことですが。)
時間はかかっているがアクセスしての反映としては、手作業での更新より各段に短時間で反映が行われているように感じます。

本題 Deno が動作するコンテナをデプロイしたい

AWS Fargate での動作を確認できたので、ここから本来の目的「Deno を Fargate で動かす」に取り組みます。

dockerfile を修正

denoland/deno_docker に倣い、修正していく。

dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
FROM denoland/deno:1.15.3

EXPOSE 80

WORKDIR /app

# 今回は、deps.ts を使わないので、コメントアウト
# COPY deps.ts .
# RUN deno cache deps.ts

ADD . .

RUN deno cache main.ts

CMD ["run", "--allow-net", "main.ts"]

アプリケーションを用意

dockerfile に実行の対象として記載された、main.ts は以下の通りです。
(deno.land に記載のサンプルほとんどそのままです)

main.ts
1
2
3
4
import { listenAndServe } from "https://deno.land/std@0.113.0/http/server.ts";

console.log("http://localhost:80/");
listenAndServe(":80", (req) => new Response("Hello Deno!!"));

動作確認 3

改めてデプロイし、アクセスを確認すると、Hello Deno!! のメッセージを確認できます。
これで、AWS Fargate で deno を動作できました。

AWS Fargate で Ultra を動かしてみる

引き続き今度は Ultraを Fargate で動かしていきます。

ディレクトリに、Ultra のアプリケーションを展開

ultra-init など使って環境を整備。

dockerfile を Ultra 用に修正

dockerfile を編集し、Ultra 用に調整。

dockerfile
1
2
3
4
5
6
7
8
9
10
11
12
13
FROM denoland/deno:1.15.3

EXPOSE 80

WORKDIR /app

ADD . .

# Ultra のリッスンするポートを変える場合には環境変数で設定
ENV port 80

# makefile に書かれているUltraの起動コマンドを記載する
CMD ["run", "--no-check", "--allow-net", "--allow-read", "--allow-env", "--allow-run", "--allow-write", "--import-map=importmap.json", "--unstable", "server.js"]

動作確認 4

改めてデプロイし、アクセスを確認すると Ultra のページを確認できます。
これで、AWS Fargate で Ultra を動作できました。


今回は、AWS Fargate で Deno を動作させるところまで確認できました。
ECS、Fargate と知見を持たないことが多く、DevelopersIO の記載をそのまま使わせてもらった部分が多数あります。

現時点で次の事柄については、はっきり認識できていなかったりするので引き続き調べる予定です。

  • Github Actions(他にもできることたくさんあるようなので調べたい。これを機にテストとかも)
  • Fargate で立ち上げたコンテナから DB に接続(これは比較的簡単にできそう。)
  • コンテナが使う環境変数の為に AWS Secrets Manager(task-definition.json でもできるようだが、マネージドな方法で)
  • 自分で使いまわす用の deploy.yml を作っておく

ではでは。