本番稼働を意識したECS Fargateを用いたFastAPIの環境構築（ALB、SSM、BlueGreenデプロイ）

# 0. 背景
ECS Fargateは、2018年に東京リージョンでの提供街開始され、サーバーやクラスターを管理することなくコンテナを実行することができる、いわゆるサーバーレスの実行環境です。Fargateの特徴の詳細はこの記事では述べませんが、使ってみたい技術ではあるものの、コンテナにログインしてデバッグするなどができず、本番環境での利用は躊躇してきました。コンテナに入ることができないというのはセキュリティ面ではメリットであるものの、本番で再現したバグのデバッグなどは直接入ってデバッグしたいという思いは出てくるものです。きちんとテストした上でデプロイし、またログを適切に吐かせることでコンテナに入らなくてもデバッグできるというポリシーかもしれませんが、実際の現場では往々にして本番環境で直接作業するということをゼロにすることは難しいのではないかと思っています。
しかし、2021年の3月に`aws ecs execute-command`コマンドを利用することでSSMすることができるようになったことが発表され[^1]、これまでの懸念が払拭されたことから、ECS Fargateを本格的に利用してみようと思い、この記事ではECS Fargateを利用してDockerアプリをデプロイする一連の流れを自分の備忘録も兼ねて紹介していきたいと思います。勉強中の身ですので、間違っている点、改善点あればぜひコメントをいただけたらと思います。
[^1]: https://aws.amazon.com/jp/about-aws/whats-new/2021/03/amazon-ecs-now-allows-you-to-execute-commands-in-a-container-running-on-amazon-ec2-or-aws-fargate/

## 0-1. 本番で稼働するために確認しておくべきこと
本番で稼働させるための確認すべきことはプロジェクトによって様々だとは思いますが、個人的には以下の様なことを考えていけば良いのではないかと思っています。プライベートで構築する範囲ではおざなりで良い部分も仕事として行うときには抑えておかなければならないこともあり、先輩方と仕事をするたびにただ動かせるだけではエンジニアとは名乗れないというのを日々感じています。

1) ロードバランサー経由でアクセスできる
 ALBを利用した負荷分散だけでなく、Route53との連携やGlobalAccerelatorの利用による外部との連携が可能になるので、ALBを利用したアクセスは必須です。
2) SSHしてサーバーに入って作業ができる
 前述の様にコンテナ内に入って作業ができるということはセキュリティ面では良くないものの、実運用ではしばしば行われ、緊急時のデバッグには必要になります。今回、SSHではなく、SSMで入ることになりますが、実用上は代替できるでしょう。
3) デプロイの仕方が簡便で堅牢であること
 ElasticBeanstalkでは、`eb deploy`コマンドを利用すれば簡単にアプリケーションをデプロイすることができました。ECS Fargateでのデプロイの場合どうなるのか、いくつかの方法はclassmethod様の記事[^2]で挙げられていますが、今回は素朴にaws cliを用いた方法で行っていきたいと思います。
　なおElasticBeanstalkとの簡単な比較は[ECS FargateとElasticBeastalk（Ruby Platform）を比較](https://qiita.com/yuyasat/items/155e5ac7f8e1fd60e6f5)に記載していますので併せてご覧ください。
4) ログ出力
　ログは不具合調査や監査等で重要な存在になってきます。そのログをどの様にして確認できるかということも把握しておく必要があります。

なお、`nginx`を用いた構成に関しては[FastAPI + nginx on ECS Fargate](https://qiita.com/yuyasat/items/e3c5e9b9cfcbafce6c56)をご覧ください。

[^2]: https://dev.classmethod.jp/articles/awsdevday2020-deploy-fargate-easily/

## 0-2. 全体の流れ
まず冒頭で本記事の全体の流れについて紹介させていただきます。
1. アプリケーションの構築
　簡単なアプリケーションをPythonのWebフレームワークであるFastAPIをDockerで構築します。アプリケーションの中身は今回の主眼ではないので[FastAPIのサンプル](https://fastapi.tiangolo.com/ja/deployment/docker/)で紹介されているシンプルなものを利用します。
2. ECRへの登録
　ECRへ登録します。この辺りは、AWS上でDocker imageを利用されている方であれば特筆すべきことは何もないので、読み飛ばしていただいで良いでしょう。
3. ネットワーク関係（VPC, サブネット, ロードバランサー, IGW、ターゲットグループ）の作成
　ECR Fargateといっても、ALBを利用するために基本的なネットワーク構成の作成は必要になります。ここでは、VPC、サブネット、ロードバランサー、インターネットゲートウェイ（IGW）、ターゲットグループを作成していきます。昨今は、現場によってはterraformなどやCloud Formation等でインフラもコードベースで管理するケースが多いとおもいますが、本記事はterraformの説明が主眼ではありませんので、AWSコンソール上から作成しています。
4. ECS Execで利用するKMSキーの作成
　ECS ExecでSSMする場合、暗号化のためのKMSキーが必要になってきます。この辺りは、[Amazon ECS Exec による AWS Fargate, Amazon EC2上のコンテナへのアクセス](https://aws.amazon.com/jp/blogs/news/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/)というAmazon Web Services ブログを参考に（一部ほぼ転用）していますので、本家をご覧になった方が良いかもしれません。
5. ECS Execのログ周りの設定
6. IAMロールの作成
7. ECS（クラスター、タスク定義、サービス）の設定
8. デプロイの設定（Blue/Green）
9. ログの確認

## 0-3. 全体の構成概要
今回のAWSでの構成は概ね以下の様になります。本来ならばこれらに加え、Route53でのドメイン管理およびKMSでの証明書管理やRDSなどが加わってくると思いますが、ドメイン周りはALBより先の設定でありECSとは切り離して考えれること、RDSに関してもセキュリティーグループおよび環境変数への設定となることがみえているため、本記事では割愛いたします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/9ebd239c-9d4f-d43a-854c-f08bc4dfb37a.png)


# 1. アプリケーションの構築
[FastAPIのサンプル](https://fastapi.tiangolo.com/ja/deployment/docker/)ではtiangolo/uvicorn-gunicorn-fastapi:python3.7というイメージをベースにDockerfileを構築していますが、AWS上でより汎用的に利用するために、amazonlinux:2をベースにしてDockerfileを作成しました。ただ、振り返ってみれば特にamazonlinux:2ベースにする必要はなかったかなと感じていますので、お好きな形で作成すると良いでしょう。

```dockerfile
FROM amazonlinux:2

WORKDIR /app

SHELL ["/bin/bash", "-c"]
RUN yum update -y
RUN yum install -y gcc bzip2 bzip2-devel openssl openssl-devel readline readline-devel git libffi-devel wget gcc-c++ unixODBC-devel tar.x86_64

RUN echo 'export PYENV_ROOT="/usr/local/pyenv"' | tee -a /etc/profile.d/pyenv.sh
RUN echo 'export PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"' | tee -a /etc/profile.d/pyenv.sh
RUN echo 'eval "$(pyenv init -)"' | tee -a /etc/profile.d/pyenv.sh

RUN git clone https://github.com/pyenv/pyenv.git /usr/local/pyenv
COPY app/ /app

ENV PYENV_ROOT "/usr/local/pyenv"
ENV PATH "${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"
RUN echo 'eval "$(pyenv init -)"' >> ~/.bashrc
RUN pyenv install -v 3.7.6 && pyenv global 3.7.6 && pip install awscli && pip install -r requirements.txt

EXPOSE 80

CMD ["uvicorn", "main:app", "--reload", "--host", "0.0.0.0", "--port", "80"]
```
appディレクトリを作成し、appディレクトリないのmain.pyファイルを以下の様に記述します。

```py
from typing import Optional
from fastapi import FastAPI

app = FastAPI()

@app.get("/")
def read_root():
    return {"Hello": "World"}

@app.get("/items/{item_id}")
def read_item(item_id: int, q: Optional[str] = None):
    return {"item_id": item_id, "q": q}
```
また、appディレクトリ内のrequirements.txtは以下の様な内容です。

```text
asgiref==3.4.1
click==8.0.1
fastapi==0.68.0
h11==0.12.0
importlib-metadata==4.6.3
pydantic==1.8.2
starlette==0.14.2
typing-extensions==3.10.0.0
uvicorn==0.14.0
zipp==3.5.0
```
ここまでの作業でディレクトリとファイル構成は以下の様になります。シンプルです。

```
.
├── Dockerfile
└── app
    ├── main.py
    └── requirements.txt
```

# 2. ECRへの登録
Docker imageの作成 → ECRへのdocker login → docker imageのタグ付け → ECRヘpushの順に説明します。

まず、Docker imageを作成します。今回は、ecs-fargate-demoという名前で作成することにします。

```bash
docker build -t ecs-fargate-demo .
```
ECRのリポジトリを作成します。以下ではAWSコマンドで行っていますが、AWSコンソール上で画面で行っても全く問題ないでしょう。また、東京リージョン（ap-northeast-1）は色々とアプリケーションがあることから、ほぼ使われていないus-west-1で構築していますが、お好みのリージョンに読み替えてください。
また、以降のコマンドではaws cliを用いていますが、IAMの利用方法によっては、`--profile xxxxxxxxxx`のようにプロファイルを指定してあげる必要がある場合がありますので、必要に応じて付け加えてください。

```bash
AWS_REGION=us-west-1
aws ecr create-repository \
  --repository-name ecs-fargate-demo \
  --region ${AWS_REGION}
```
次に、ECRにログインします。お馴染みの以下のコマンドでログインしてください。

```bash
AWS_ACCOUNT_ID=xxxxxxxxxxxx # 数字12桁のもの
# ECRにログイン
aws ecr get-login-password --region ${AWS_REGION} | docker login --username AWS --password-stdin ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com
```
次に、Docker Imageですが、タグにlatestをつける運用もありますが、個人的にはきちんとバージョン管理をするためにも、デプロイごとに別のタグをつけていく方が良いと考えております。classmethodさんの記事[^2]にもlatest運用はやめましょうとあり、latest運用についての考察は勝手ながらそちらにお任せさせていただきます。今回は素朴にまずは`1.0.0`とつけておき、[デプロイ]()では、`1.0.1`としたものをデプロイしていこうと思います。

[^2]: https://dev.classmethod.jp/articles/ecs-deploy-all/
```bash
# docker imageのタグ付け
docker tag ecs-fargate-demo ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/ecs-fargate-demo:1.0.0
```
最後にDocker ImageをECRにpushします。

```bash
# Docker ImageをECRにpush
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/ecs-fargate-demo:1.0.0
```

# 3. ネットワーク関係（VPC, サブネット, ロードバランサー, IGW、ターゲットグループ）の作成
この章はネットワーク関係の設定ですので、すでにあるVPCやサブネットに構築する場合は、スキップして構いません。また、ECS Fargateに固有の設定ではありませんので、AWSの基本的なネットワーク構成に詳しい方はご自身で設定するのがいいかと思います。

## 3-1. VPCの作成
VPCの名前は、ecs-fargate-demo-vpcなどで適宜つければ良いでしょう。。CIDRブロックもデモなので、10.0.0.0/16で良いでしょう。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/9d1a4e79-9b80-fc8e-2f25-18d249619eb1.png)

## 3-2. サブネットの作成
サブネットも10.0.0.0/24などとしておきます。この辺りはシステムによって設計が変わってくるところかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/fac536dc-d155-2adc-a122-d020c788db34.png)

この時作成されたVPC IDを変数にセットしておくこととします。

```bash
VPC_ID=vpc-xxxxxxxxxxxxxxxxx
PUBLIC_SUBNET1=subnet-xxxxxxxxxxxxxxxxx
PUBLIC_SUBNET2=subnet-xxxxxxxxxxxxxxxxx
```

## 3-3 セキュリティーグループの作成
コンテナの 80 番ポートへのリクエストを許可するためのセキュリティグループを作成します。

```bash
ECS_FARGATE_DEMO_SG=$(aws ec2 create-security-group --group-name ecs-fargate-demo-SG --description "ECS fargate demo SG" --vpc-id $VPC_ID --region $AWS_REGION)
ECS_FARGATE_DEMO_SG_ID=$(echo $ECS_FARGATE_DEMO_SG | jq --raw-output .GroupId)
aws ec2 authorize-security-group-ingress --group-id $ECS_FARGATE_DEMO_SG_ID --protocol tcp --port 80 --cidr 0.0.0.0/0 --region $AWS_REGION
```

## 3-4. ターゲットグループの作成
　ロードバランサーを作成する前にターゲットグループを作成しておきます。BlueGreenデプロイを行うために、２つ作成しておきます。ターゲットタイプは、`IP addresses`、ターゲットグループ名はなんでも良いですが、`tg-ecs-fargate-demo-1`、`tg-ecs-fargate-demo-2`としておきます。VPCは3-1で作成したVPCを設定しておきます。Register targetsは現時点では設定しないままにしておきます。（のちほどサービスの作成やCodeDeployにおいて設定していきます）
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/35fd8656-cbaa-a566-99e2-6e02de4f6768.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/10e17022-13ec-e1fc-f9a5-984422a7e0b3.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/8f455fba-0ed9-9136-a871-1735af6699ca.png)

## 3-5. ロードバランサーの作成
　ALB（Application Load Balancer）で作成していきます。名前は `ecs-fargate-demo-alb`としておきます。リスナーはHTTPSを追加したい方は必要に応じて追加してください。ここでは80だけとしておきます。アベイラビリティーゾーンの箇所では、3-1で作成したVPCおよび、3-2で作成したサブネットを選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/eb360da1-812b-0304-f247-22475eeb28c5.png)

　「手順 2: セキュリティ設定の構成」は、今回はHTTPのみなので、そのまま次へ、「手順 3: セキュリティグループの設定」では、ALB用のセキュリティーグループを用意してもいいですが、ここでは雑ですが、3-3で作成したセキュリティーグループを選択することとします。
　「手順 4: ルーティングの設定」では、3-4で作成したターゲットグループを一つ選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/be9caae7-a0f1-bec4-2418-de1b2bc1d20b.png)
　「手順 5: ターゲットの登録」では、あとでターゲットを登録するので、そのまま次の手順に進んでください。

## 3-6. インターネットゲートウェイのアタッチ
　VPCのインターネットゲートウェイを作成し、VPCにアタッチします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/b2715303-f7a7-a62b-5539-4df4846bd2b8.png)
　また、ルートテーブルを作成し、ルートの編集でターゲットにインターネットゲートウェイを追加します。また、サブネットの関連付けで3-2で作成したサブネットを関連づけておきます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/7361ce4e-b597-d125-f64d-533e1bb60414.png)

# 4. ECS Execで利用するKMSキーの作成
　ECS Execは、実行中のコンテナに乗り込んでコマンドを実行できる機能です。以前は、ECS Fargate上でエラーが起こった場合、ログを仕込んだりEC2上にデプロイしてからssmやsshをして、docker execを行ったりしていました。文字で見るからに面倒さが伝わってきます。2021年3月15日に投稿された[AWS記事](https://aws.amazon.com/jp/about-aws/whats-new/2021/03/amazon-ecs-now-allows-you-to-execute-commands-in-a-container-running-on-amazon-ec2-or-aws-fargate/)で可能になったことが公表されています。
ECS Execを利用するために、データチャネルを暗号化するためのKMSキーを作成する必要があります。

```bash
KMS_KEY=$(aws kms create-key --region $AWS_REGION)
KMS_KEY_ARN=$(echo $KMS_KEY | jq --raw-output .KeyMetadata.Arn)

aws kms create-alias --alias-name alias/ecs-fargate-demo --target-key-id $KMS_KEY_ARN --region $AWS_REGION
echo "The KMS Key ARN is: "$KMS_KEY_ARN
```

# 5. ECS Execのログ周りの設定
## 5-1. CloudWatchロググループの作成
このロググループは2つのログストリームを有し、 1つはコンテナのstdoutのため、もう一つはECS Execのログを出力するためです。

```bash
aws logs create-log-group --log-group-name /aws/ecs/ecs-fargate-demo --region $AWS_REGION
```

## 5-2. ECS Exec のログを出力するための S3 バケットの作成
```bash
ECS_FARGATE_BUCKET_NAME=ecs-fargate-demo-xxxxxxxxxx # xxxxxxxxxxのところはバケット名が重複しない様に適当な数字を当ててください。
aws s3api create-bucket --bucket $ECS_FARGATE_BUCKET_NAME --region $AWS_REGION --create-bucket-configuration LocationConstraint=$AWS_REGION
```


# 6. IAMロールの作成
ECS タスクロールと ECS タスク実行ロールのための 2 つの IAM ロールを作成します。`ecs-tasks-trust-policy.json`という名前でファイルを作成し、以下の内容を記述してください。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Service": [
          "ecs-tasks.amazonaws.com"
        ]
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```
作成したファイルを利用して、ロールを作成します。以下のコマンドを実行してください。

```bash
aws iam create-role --role-name ecs-fargate-demo-task-execution-role --assume-role-policy-document file://ecs-tasks-trust-policy.json --region $AWS_REGION
aws iam create-role --role-name ecs-fargate-demo-task-role --assume-role-policy-document file://ecs-tasks-trust-policy.json --region $AWS_REGION
```
この時点ではロールにポリシーはアタッチされていないので、アタッチしていきます。`ecs-fargate-demo-task-execution-role`には、すでに AWS 管理ポリシーとして存在する AmazonECSTaskExecutionRolePolicy をアタッチします。

```bash
aws iam attach-role-policy \
    --role-name ecs-fargate-demo-task-execution-role \
    --policy-arn "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
```

`ecs-fargate-demo-task-role`には、コンテナがSSMを経由してセキュリティ保護済みのセッションをオープンし、 ECS Exec が CloudWatch もしくは S3 (それぞれ上記で作成されたログストリーム、バケット) にログを出力することを許可するポリシーを作成、アタッチします。（[元記事](https://aws.amazon.com/jp/blogs/news/new-using-amazon-ecs-exec-access-your-containers-fargate-ec2/)のままの説明ですいません。）
まず、ecs-fargate-demo-task-role-policy.jsonという名前でファイルを作成し、以下の内容を記述します。
このとき、以下の項目に関しては、これまでの作業の中で変数に格納してきているので、その値を代入してください。

```
<AWS_REGION>
<AWS_ACCOUNT_ID>
<ECS_FARGATE_BUCKET_NAME>
<KMS_KEY_ARN>
```
それぞれの値が不明な場合`echo $KMS_KEY_ARN`などして確認してください。

```json
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ssmmessages:CreateControlChannel",
        "ssmmessages:CreateDataChannel",
        "ssmmessages:OpenControlChannel",
        "ssmmessages:OpenDataChannel"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:DescribeLogGroups"
      ],
      "Resource": "*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "logs:CreateLogStream",
        "logs:DescribeLogStreams",
        "logs:PutLogEvents"
      ],
      "Resource": "arn:aws:logs:<AWS_REGION>:<AWS_ACCOUNT_ID>:log-group:/aws/ecs/ecs-fargate-demo:*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:PutObject"
      ],
      "Resource": "arn:aws:s3:::<ECS_FARGATE_BUCKET_NAME>/*"
    },
    {
      "Effect": "Allow",
      "Action": [
        "s3:GetEncryptionConfiguration"
      ],
      "Resource": "arn:aws:s3:::<ECS_FARGATE_BUCKET_NAME>"
    },
    {
      "Effect": "Allow",
      "Action": [
        "kms:Decrypt"
      ],
      "Resource": "<KMS_KEY_ARN>"
    }
  ]
}
```
JSONファイルを作成したら、ポリシーをアタッチするために次のコマンドを実行します。

```bash
aws iam put-role-policy \
    --role-name ecs-fargate-demo-task-role \
    --policy-name ecs-fargate-demo-task-role-policy \
    --policy-document file://ecs-fargate-demo-task-role-policy.json
```
この時点でIAMロールを確認すると二つロールが確認され、クリックして詳細まで確認するとポリシーがアタッチされていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/aee65e5a-c47a-80ee-5093-3d761ff3f285.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/8f70b6c8-fc82-89de-1c1c-bea98e99ed8d.png)

ここまでの間で、ディレクトリ構成は以下の様になっているかと思います。IAMロールの設定に必要な `ecs-fargate-demo-task-role-policy.json`と `ecs-tasks-trust-policy.json`が追加された形ですね。

```
.
├── Dockerfile
├── app
│   ├── main.py
│   └── requirements.txt
├── ecs-fargate-demo-task-role-policy.json
└── ecs-tasks-trust-policy.json
```
以降の作業では、タスク定義のための`ecs-fargate-demo-definition.json`、サービス作成のための`ecs-fargate-demo-service.json`を追加していきます。

# 7. ECS関連の設定
　ここでは、ECS関連の設定を行います。ECS クラスターの作成、タスク定義の作成、サービスの作成を行っていきます。ECSの各サービスの概念については、EC2のものでFargateのものではないですが[Amazon EC2 Container Service(ECS)の概念整理](https://qiita.com/NewGyu/items/9597ed2eda763bd504d7)[^3]が分かりやすいと思います。

[^3]: https://qiita.com/NewGyu/items/9597ed2eda763bd504d7

## 7-1. ECS クラスターの作成
　`aws ecs`コマンドを用いてクラスターを作成します。ECS Execのログ取得のために、`executeCommandConfiguration`のオプションが必要になります。（必ずしも必須というわけではありません）オプションの詳細は、[AWS CLIのドキュメント](https://docs.aws.amazon.com/cli/latest/reference/ecs/create-cluster.html)[^4]に委ねます。

[^4]: https://docs.aws.amazon.com/cli/latest/reference/ecs/create-cluster.html


```bash
aws ecs create-cluster \
    --cluster-name ecs-fargate-demo-cluster \
    --region $AWS_REGION \
    --configuration executeCommandConfiguration="{logging=OVERRIDE,\
                                                kmsKeyId=$KMS_KEY_ARN,\
                                                logConfiguration={cloudWatchLogGroupName="/aws/ecs/ecs-fargate-demo",\
                                                                　　　s3BucketName=$ECS_FARGATE_BUCKET_NAME,\
                                                               　　 　s3KeyPrefix=exec-output}}"
```


## 7-2. タスク定義の作成
ECSのタスク定義を作成していきます。`ecs-fargate-demo-definition.json`というファイルを作成し、以下のJSONを記述してください。このとき、ここでも以下の項目に関しては、これまでの作業の中で変数に格納してきているので、その値を代入してください。

```
<AWS_REGION>
<AWS_ACCOUNT_ID>
```

```json
{
  "family": "ecs-faragte-demo",
  "networkMode": "awsvpc",
  "executionRoleArn": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/ecs-fargate-demo-task-execution-role",
  "taskRoleArn": "arn:aws:iam::<AWS_ACCOUNT_ID>:role/ecs-fargate-demo-task-role",
  "containerDefinitions": [
    {
      "name": "fargate-demo",
      "image": "<AWS_ACCOUNT_ID>.ecr.<AWS_REGION>.amazonaws.com/ecs-fargate-demo:1.0.0",
      "linuxParameters": {
        "initProcessEnabled": true
      },
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "/aws/ecs/ecs-fargate-demo",
          "awslogs-region": "<AWS_REGION>",
          "awslogs-stream-prefix": "container-stdout"
        }
      }
    }
  ],
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "cpu": "256",
  "memory": "512"
}
```
JSONファイルを作成したら、`aws ecs register-task-definition` コマンドを実行し、タスク定義を作成します。

```bash
aws ecs register-task-definition \
    --cli-input-json file://ecs-fargate-demo-definition.json \
    --region $AWS_REGION
```
このコマンドを実行すると実行結果としてJSONが出力されます。また、AWSコンソール上でもタスク定義のところに追加がされます。

## 7-3. サービスの作成
`ecs-fargate-demo-service.json`ファイルを作成し、以下のJSONを記述します。taskDefinitionは`aws ecs register-task-definition`　コマンドの実行結果からも確認できますし、AWSコンソール上のタスク定義からも確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/634d9540-e030-0e13-9f7d-717ec2d4d85b.png)
`loadBalancers`の`targetGroupArn`は3-4で作成したターゲットグループのArnを指定します。AWSコンソール上からでも確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/b7b75a00-c196-e306-2dc6-7c4ff68d1807.png)

```json
{
  "taskDefinition": "ecs-fargate-demo:1",
  "loadBalancers": [
    {
      "targetGroupArn": "arn:aws:elasticloadbalancing:us-west-1:<AWS_ACCOUNT_ID>:targetgroup/tg-ecs-fa-ecs-fargate-demo-1/7672b26fea125dfd",
      "containerName": "fargate-demo",
      "containerPort": 80
    }
  ],
  "deploymentController": {
    "type": "CODE_DEPLOY"
  },
  "desiredCount": 1
}

```
JSONファイルを作成したら、`aws ecs create-service`コマンドでサービスを作成します。コマンドのオプションの詳細は[AWS CLI ドキュメント](https://docs.aws.amazon.com/cli/latest/reference/ecs/create-service.html#create-service)に委ねますが、`--enable-execute-command`をつけることで、ECS Execが使える様になります。また、platform-versionはLATESTを指定していますが、ECS Execを利用するためには、1.4.0以上である必要があります。

```bash
aws ecs create-service \
    --cluster ecs-fargate-demo-cluster \
    --service-name ecs-fargate-demo \
    --enable-execute-command \
    --launch-type FARGATE \
    --platform-version LATEST \
    --region $AWS_REGION \
    --desired-count 1 \
    --network-configuration awsvpcConfiguration="{subnets=[$PUBLIC_SUBNET1, $PUBLIC_SUBNET2],securityGroups=[$ECS_FARGATE_DEMO_SG_ID],assignPublicIp=ENABLED}" \
    --tags key=environment,value=production \
    --cli-input-json file://ecs-fargate-demo-service.json
```
　クラスター > ecs-fargate-demo-cluster > サービス: ecs-fargate-demo のタスクタブで「前回のステータス」というところが「RUNNING」となるまで待ちます。
RUNNINGになれば、コンテナ内に入ることができます。そのためには、まず、タスクIDを確認しましょう。タスクIDは同じくタスクタブで確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/4fbf7f6c-c0bf-3a04-a71a-db530e7e8fb1.png)
　タスクIDが把握できたら、以下のコマンドでSSMすることができます。下記コマンドの一番最後のオプション`--task`にタスクIDを指定してください。container名は、タスク定義の詳細のコンテナの定義で確認することができます。

```bash
aws ecs execute-command  \
    --region $AWS_REGION \
    --cluster ecs-fargate-demo-cluster \
    --container fargate-demo \
    --command "/bin/bash" \
    --interactive \
    --task 7816f2xxxxxxxxxxxxxxxxxxxxxxxxxx
```
ここまででECS Fargateでのアプリケーションを公開し、コンテナないにSSMすることができました。
では、最後にブラウザ上でも確認してみましょう。ALBを作成すると自動的にDNS名が設定されます。こちらをクリックすれば、ブラウザ上からのレスポンスを確認することができます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/cf2e6582-fe5f-2567-0827-9a763f9289fe.png)
うまく設定できていれば{"Hello": "World"}が確認できるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/dc54dac0-5481-44f2-5216-aa6b3e4cba75.png)
うまくいかない場合は、サービスのイベントやログの中身を確認してみてください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/9ff0b86a-dd4a-f766-f6fc-0909f2dde6dc.png)



# 8. デプロイの設定（Blue/Green）
　次にデプロイするための設定を行なっていきます。サービスは、AWSコンソールから作成する際、ローリングアップデートとBlue/Greenデプロイメント（AWS CodeDeploy を使用）を選ぶことができますが、今回、サービス作成時のJSONで、CODE_DEPLOYを指定してますので、Blue/Greenデプロイメントの方になります。

```json
  "deploymentController": {
    "type": "CODE_DEPLOY"
  },
```

## 8-1. BlueGreenデプロイをするためのCodeDeployの設定
　CodeDeploy > アプリケーション から 「アプリケーションの作成」をクリックしアプリケーションを作成します。
　名前はなんでも良いですが、 `AppECS-ecs-fargate-demo-cluster-ecs-fargate-demo` とでも入れておけばいいかなと思います。AWSコンソール上からサービスを作成する場合は、このような命名規則で作成されるのでそれに合わせた形になります。コンピューティングプラットフォームは `Amazon ECS` にします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/d32e97e0-d4a2-bf42-08ec-96c3795996e2.png)
CodeDeployでアプリケーションを作成したら、デプロイグループを作成します。
　デプロイグループ名は、`DgpECS-ecs-fargate-demo-cluster-ecs-fargate-demo` とでもしておきましょう（こちらもAWSコンソール上から自動で作成した場合の命名と同じ）。
　サービスロールは`ecsCodeDeployRole`を選択。
　環境設定でのECSクラスターとECSサービス名は、7-1と7-3で作成したものを選んでください。
　Load Balancerは、3-5で作成したものを選び、ターゲットグループ1と2は、3-4で作成した2つをそれぞれ選択してください。BlueGreenデプロイを行うので、片方のターゲットグループは現行のアプリケーション、もう片方の方に新しいアプリケーションをデプロイした上でターゲットグループをつけかえるので、２つとも設定が必要になります。
　デプロイ設定のところは、状況に応じて設定してください。今回は、「すぐにトラフィックを再ルーティング」のままで進めます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/3d0be57d-b318-f74a-73f0-1b92b09d0a8f.png)

## 8-2. デプロイの流れ
　ここまででデプロイするための準備が整いました。ここでは、aws cliを用いたデプロイの全体の流れについて順を追って説明していきます。
まず、`app/main.py` の`{"Hello": "World"}`を`{"Hello": "World!!!"}`とでも変えておき、これをデプロイすることにします。

```diff_python
 @app.get("/")
 def read_root():
-    return {"Hello": "World"}
+    return {"Hello": "World!!!"}
```

### 8-2-1. Docker imageの作成〜ECRへの登録
```bash
docker build -t ecs-fargate-demo .
docker tag ecs-fargate-demo ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/ecs-fargate-demo:1.0.1 # タグ付け。インクリメントするなど、versionがわかる様にしておく。
docker push ${AWS_ACCOUNT_ID}.dkr.ecr.${AWS_REGION}.amazonaws.com/ecs-fargate-demo:1.0.1
```
### 8-2-2. タスク定義のJSON更新
　タスク定義を作成する際に利用したJSONファイル（`ecs-fargate-demo-definition.json`）のimageのタグ部分を8-2-1でつけたタグに変更します。

```diff_json
     {
       "name": "fargate-demo",
-      "image": "<AWS_ACCOUNT_ID>.dkr.ecr.us-west-1.amazonaws.com/ecs-fargate-demo:1.0.0",
+      "image": "<AWS_ACCOUNT_ID>.dkr.ecr.us-west-1.amazonaws.com/ecs-fargate-demo:1.0.1",
       "linuxParameters": {
         "initProcessEnabled": true
       }
```

### 8-2-3. タスク定義の登録
　`aws ecs register-task-definition`コマンドでタスク定義を登録します。同じタスク定義名で`register-task-definition`を行うと、自動的にリビジョンが+1インクリメントされます。最初にタスク定義を作成するときと同じコマンドであることに気持ち悪さを感じつつも、進めていきます。

```bash
aws ecs register-task-definition \
    --cli-input-json file://ecs-fargate-demo.json \
    --region $AWS_REGION
```
この時、コマンド結果で出力されたJSONを見て、タスク定義のリビジョンを確認しておきます。ここで確認を忘れても、AWSコンソールのタスク定義（Task definitions）から確認することができますので慌てる必要はありません。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/86e7ad1b-9512-3eec-db57-a235dabdb707.png)

### 8-2-4. Codedeloyのデプロイメント作成
　以下のコマンドを実行することでデプロイすることができます。TaskDefinitionのところで`ecs-fargate-demo:2`とありますが、タスク定義の登録の際に確認したリビジョンを指定してあげてください。
また、`application-name`や`deployment-group-name`については、CodeDeployで設定した値となります。`--revision`のオプションの値のJSONが分かりにくいので、もう少しいい方法がないか検討中です。

```bash
aws deploy create-deployment \
  --application-name AppECS-ecs-fargate-demo-cluster-ecs-fargate-demo \
  --deployment-group-name DgpECS-ecs-fargate-demo-cluster-ecs-fargate-demo \
  --region $AWS_REGION \
  --revision '{"revisionType": "AppSpecContent", "appSpecContent": {"content": "{\"version\": 1, \"Resources\": [{\"TargetService\": {\"Type\": \"AWS::ECS::Service\", \"Properties\": {\"TaskDefinition\": \"arn:aws:ecs:$AWS_REGION:$AWS_ACCOUNT_ID:task-definition/ecs-fargate-demo:2\", \"LoadBalancerInfo\": {\"ContainerName\": \"fargate-demo\", \"ContainerPort\": 80}}}}]}"}}'
```
　コマンドを実行したら、AWSコンソールのCodeDeployからデプロイの進捗状況を確認することができます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/68d7dead-612d-eec5-6c45-c5a62ba97a76.png)
　7-2の最後に確認したALBのDNS名にアクセスして確認してみると{"Hello": "World!!!"}が表示されているのが家訓できるかと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/95528f05-a9d6-2c6d-5ebf-0a94c3c792d2.png)

なお、サービスがBlueGreenではなく、ローリング更新の場合、`aws ecs update-service`コマンドで実行できます。

```bash
aws ecs update-service \
    --cluster ecs-fargate-demo-cluster \
    --service ecs-fargate-demo \
    --task-definition "ecs-fargate-demo:3"
```

# 9. ログの確認
　まず、サーバーサイドのエラーがどの様にログに表現されるかを確認するため、`app/main.py` の `read_item`を次の様に変更し、0で割ると発生するエラーを意図的に生じさせてみます。

```diff_python
 @app.get("/items/{item_id}")
 def read_item(item_id: int, q: Optional[str] = None):
+    1 / 0
     return {"item_id": item_id, "q": q}
```
　この修正をしたのち、Docker Imageの作成から8のデプロイの一連の流れを実施します。その後、 `/items/1` などにアクセスしてみると、画面上は、 `Internal Server Error` と表示されます。この辺りはエラーハンドリングを行っていないのでこうなります。
　次に、AWSコンソールからタスクのログを確認します。ログのタブでをクリックするとログが表示され、少しわかりにくいですが、`ZeroDivisionError: division by zero`というエラーが表示されていることが確認できます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/121304/f6e31854-09fd-14c6-df80-92f67265f75a.png)