0. 背景
ECS Fargateは、2018年に東京リージョンでの提供街開始され、サーバーやクラスターを管理することなくコンテナを実行することができる、いわゆるサーバーレスの実行環境です。Fargateの特徴の詳細はこの記事では述べませんが、使ってみたい技術ではあるものの、コンテナにログインしてデバッグするなどができず、本番環境での利用は躊躇してきました。コンテナに入ることができないというのはセキュリティ面ではメリットであるものの、本番で再現したバグのデバッグなどは直接入ってデバッグしたいという思いは出てくるものです。きちんとテストした上でデプロイし、またログを適切に吐かせることでコンテナに入らなくてもデバッグできるというポリシーかもしれませんが、実際の現場では往々にして本番環境で直接作業するということをゼロにすることは難しいのではないかと思っています。
しかし、2021年の3月にaws ecs execute-command
コマンドを利用することでSSMすることができるようになったことが発表され1、これまでの懸念が払拭されたことから、ECS Fargateを本格的に利用してみようと思い、この記事ではECS Fargateを利用してDockerアプリをデプロイする一連の流れを自分の備忘録も兼ねて紹介していきたいと思います。勉強中の身ですので、間違っている点、改善点あればぜひコメントをいただけたらと思います。
0-1. 本番で稼働するために確認しておくべきこと
本番で稼働させるための確認すべきことはプロジェクトによって様々だとは思いますが、個人的には以下の様なことを考えていけば良いのではないかと思っています。プライベートで構築する範囲ではおざなりで良い部分も仕事として行うときには抑えておかなければならないこともあり、先輩方と仕事をするたびにただ動かせるだけではエンジニアとは名乗れないというのを日々感じています。
- ロードバランサー経由でアクセスできる
ALBを利用した負荷分散だけでなく、Route53との連携やGlobalAccerelatorの利用による外部との連携が可能になるので、ALBを利用したアクセスは必須です。 - SSHしてサーバーに入って作業ができる
前述の様にコンテナ内に入って作業ができるということはセキュリティ面では良くないものの、実運用ではしばしば行われ、緊急時のデバッグには必要になります。今回、SSHではなく、SSMで入ることになりますが、実用上は代替できるでしょう。 - デプロイの仕方が簡便で堅牢であること
ElasticBeanstalkでは、eb deploy
コマンドを利用すれば簡単にアプリケーションをデプロイすることができました。ECS Fargateでのデプロイの場合どうなるのか、いくつかの方法はclassmethod様の記事2で挙げられていますが、今回は素朴にaws cliを用いた方法で行っていきたいと思います。
なおElasticBeanstalkとの簡単な比較はECS FargateとElasticBeastalk(Ruby Platform)を比較に記載していますので併せてご覧ください。 - ログ出力
ログは不具合調査や監査等で重要な存在になってきます。そのログをどの様にして確認できるかということも把握しておく必要があります。
なお、nginx
を用いた構成に関してはFastAPI + nginx on ECS Fargateをご覧ください。
0-2. 全体の流れ
まず冒頭で本記事の全体の流れについて紹介させていただきます。
- アプリケーションの構築
簡単なアプリケーションをPythonのWebフレームワークであるFastAPIをDockerで構築します。アプリケーションの中身は今回の主眼ではないのでFastAPIのサンプルで紹介されているシンプルなものを利用します。 - ECRへの登録
ECRへ登録します。この辺りは、AWS上でDocker imageを利用されている方であれば特筆すべきことは何もないので、読み飛ばしていただいで良いでしょう。 - ネットワーク関係(VPC, サブネット, ロードバランサー, IGW、ターゲットグループ)の作成
ECR Fargateといっても、ALBを利用するために基本的なネットワーク構成の作成は必要になります。ここでは、VPC、サブネット、ロードバランサー、インターネットゲートウェイ(IGW)、ターゲットグループを作成していきます。昨今は、現場によってはterraformなどやCloud Formation等でインフラもコードベースで管理するケースが多いとおもいますが、本記事はterraformの説明が主眼ではありませんので、AWSコンソール上から作成しています。 - ECS Execで利用するKMSキーの作成
ECS ExecでSSMする場合、暗号化のためのKMSキーが必要になってきます。この辺りは、Amazon ECS Exec による AWS Fargate, Amazon EC2上のコンテナへのアクセスというAmazon Web Services ブログを参考に(一部ほぼ転用)していますので、本家をご覧になった方が良いかもしれません。 - ECS Execのログ周りの設定
- IAMロールの作成
- ECS(クラスター、タスク定義、サービス)の設定
- デプロイの設定(Blue/Green)
- ログの確認
0-3. 全体の構成概要
今回のAWSでの構成は概ね以下の様になります。本来ならばこれらに加え、Route53でのドメイン管理およびKMSでの証明書管理やRDSなどが加わってくると思いますが、ドメイン周りはALBより先の設定でありECSとは切り離して考えれること、RDSに関してもセキュリティーグループおよび環境変数への設定となることがみえているため、本記事では割愛いたします。
1. アプリケーションの構築
FastAPIのサンプルではtiangolo/uvicorn-gunicorn-fastapi:python3.7というイメージをベースにDockerfileを構築していますが、AWS上でより汎用的に利用するために、amazonlinux:2をベースにしてDockerfileを作成しました。ただ、振り返ってみれば特にamazonlinux:2ベースにする必要はなかったかなと感じていますので、お好きな形で作成すると良いでしょう。
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ファイルを以下の様に記述します。
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は以下の様な内容です。
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という名前で作成することにします。
docker build -t ecs-fargate-demo .
ECRのリポジトリを作成します。以下ではAWSコマンドで行っていますが、AWSコンソール上で画面で行っても全く問題ないでしょう。また、東京リージョン(ap-northeast-1)は色々とアプリケーションがあることから、ほぼ使われていないus-west-1で構築していますが、お好みのリージョンに読み替えてください。
また、以降のコマンドではaws cliを用いていますが、IAMの利用方法によっては、--profile xxxxxxxxxx
のようにプロファイルを指定してあげる必要がある場合がありますので、必要に応じて付け加えてください。
AWS_REGION=us-west-1
aws ecr create-repository \
--repository-name ecs-fargate-demo \
--region ${AWS_REGION}
次に、ECRにログインします。お馴染みの以下のコマンドでログインしてください。
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
としたものをデプロイしていこうと思います。
# 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します。
# 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で良いでしょう。
3-2. サブネットの作成
サブネットも10.0.0.0/24などとしておきます。この辺りはシステムによって設計が変わってくるところかと思います。
この時作成されたVPC IDを変数にセットしておくこととします。
VPC_ID=vpc-xxxxxxxxxxxxxxxxx
PUBLIC_SUBNET1=subnet-xxxxxxxxxxxxxxxxx
PUBLIC_SUBNET2=subnet-xxxxxxxxxxxxxxxxx
3-3 セキュリティーグループの作成
コンテナの 80 番ポートへのリクエストを許可するためのセキュリティグループを作成します。
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デプロイを行うために、2つ作成しておきます。ターゲットタイプは、IP addresses
、ターゲットグループ名はなんでも良いですが、tg-ecs-fargate-demo-1
、tg-ecs-fargate-demo-2
としておきます。VPCは3-1で作成したVPCを設定しておきます。Register targetsは現時点では設定しないままにしておきます。(のちほどサービスの作成やCodeDeployにおいて設定していきます)
3-5. ロードバランサーの作成
ALB(Application Load Balancer)で作成していきます。名前は ecs-fargate-demo-alb
としておきます。リスナーはHTTPSを追加したい方は必要に応じて追加してください。ここでは80だけとしておきます。アベイラビリティーゾーンの箇所では、3-1で作成したVPCおよび、3-2で作成したサブネットを選択してください。
「手順 2: セキュリティ設定の構成」は、今回はHTTPのみなので、そのまま次へ、「手順 3: セキュリティグループの設定」では、ALB用のセキュリティーグループを用意してもいいですが、ここでは雑ですが、3-3で作成したセキュリティーグループを選択することとします。
「手順 4: ルーティングの設定」では、3-4で作成したターゲットグループを一つ選択してください。
「手順 5: ターゲットの登録」では、あとでターゲットを登録するので、そのまま次の手順に進んでください。
3-6. インターネットゲートウェイのアタッチ
VPCのインターネットゲートウェイを作成し、VPCにアタッチします。
また、ルートテーブルを作成し、ルートの編集でターゲットにインターネットゲートウェイを追加します。また、サブネットの関連付けで3-2で作成したサブネットを関連づけておきます。
4. ECS Execで利用するKMSキーの作成
ECS Execは、実行中のコンテナに乗り込んでコマンドを実行できる機能です。以前は、ECS Fargate上でエラーが起こった場合、ログを仕込んだりEC2上にデプロイしてからssmやsshをして、docker execを行ったりしていました。文字で見るからに面倒さが伝わってきます。2021年3月15日に投稿されたAWS記事で可能になったことが公表されています。
ECS Execを利用するために、データチャネルを暗号化するためのKMSキーを作成する必要があります。
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のログを出力するためです。
aws logs create-log-group --log-group-name /aws/ecs/ecs-fargate-demo --region $AWS_REGION
5-2. ECS Exec のログを出力するための S3 バケットの作成
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
という名前でファイルを作成し、以下の内容を記述してください。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Service": [
"ecs-tasks.amazonaws.com"
]
},
"Action": "sts:AssumeRole"
}
]
}
作成したファイルを利用して、ロールを作成します。以下のコマンドを実行してください。
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 をアタッチします。
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 (それぞれ上記で作成されたログストリーム、バケット) にログを出力することを許可するポリシーを作成、アタッチします。(元記事のままの説明ですいません。)
まず、ecs-fargate-demo-task-role-policy.jsonという名前でファイルを作成し、以下の内容を記述します。
このとき、以下の項目に関しては、これまでの作業の中で変数に格納してきているので、その値を代入してください。
<AWS_REGION>
<AWS_ACCOUNT_ID>
<ECS_FARGATE_BUCKET_NAME>
<KMS_KEY_ARN>
それぞれの値が不明な場合echo $KMS_KEY_ARN
などして確認してください。
{
"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ファイルを作成したら、ポリシーをアタッチするために次のコマンドを実行します。
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ロールを確認すると二つロールが確認され、クリックして詳細まで確認するとポリシーがアタッチされていることが確認できます。
ここまでの間で、ディレクトリ構成は以下の様になっているかと思います。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)の概念整理3が分かりやすいと思います。
7-1. ECS クラスターの作成
aws ecs
コマンドを用いてクラスターを作成します。ECS Execのログ取得のために、executeCommandConfiguration
のオプションが必要になります。(必ずしも必須というわけではありません)オプションの詳細は、AWS CLIのドキュメント4に委ねます。
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>
{
"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
コマンドを実行し、タスク定義を作成します。
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コンソール上のタスク定義からも確認できます。
loadBalancers
のtargetGroupArn
は3-4で作成したターゲットグループのArnを指定します。AWSコンソール上からでも確認することができます。
{
"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 ドキュメントに委ねますが、--enable-execute-command
をつけることで、ECS Execが使える様になります。また、platform-versionはLATESTを指定していますが、ECS Execを利用するためには、1.4.0以上である必要があります。
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は同じくタスクタブで確認できます。
タスクIDが把握できたら、以下のコマンドでSSMすることができます。下記コマンドの一番最後のオプション--task
にタスクIDを指定してください。container名は、タスク定義の詳細のコンテナの定義で確認することができます。
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名が設定されます。こちらをクリックすれば、ブラウザ上からのレスポンスを確認することができます。
うまく設定できていれば{"Hello": "World"}が確認できるはずです。
うまくいかない場合は、サービスのイベントやログの中身を確認してみてください。
8. デプロイの設定(Blue/Green)
次にデプロイするための設定を行なっていきます。サービスは、AWSコンソールから作成する際、ローリングアップデートとBlue/Greenデプロイメント(AWS CodeDeploy を使用)を選ぶことができますが、今回、サービス作成時のJSONで、CODE_DEPLOYを指定してますので、Blue/Greenデプロイメントの方になります。
"deploymentController": {
"type": "CODE_DEPLOY"
},
8-1. BlueGreenデプロイをするためのCodeDeployの設定
CodeDeploy > アプリケーション から 「アプリケーションの作成」をクリックしアプリケーションを作成します。
名前はなんでも良いですが、 AppECS-ecs-fargate-demo-cluster-ecs-fargate-demo
とでも入れておけばいいかなと思います。AWSコンソール上からサービスを作成する場合は、このような命名規則で作成されるのでそれに合わせた形になります。コンピューティングプラットフォームは Amazon ECS
にします。
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デプロイを行うので、片方のターゲットグループは現行のアプリケーション、もう片方の方に新しいアプリケーションをデプロイした上でターゲットグループをつけかえるので、2つとも設定が必要になります。
デプロイ設定のところは、状況に応じて設定してください。今回は、「すぐにトラフィックを再ルーティング」のままで進めます。
8-2. デプロイの流れ
ここまででデプロイするための準備が整いました。ここでは、aws cliを用いたデプロイの全体の流れについて順を追って説明していきます。
まず、app/main.py
の{"Hello": "World"}
を{"Hello": "World!!!"}
とでも変えておき、これをデプロイすることにします。
@app.get("/")
def read_root():
- return {"Hello": "World"}
+ return {"Hello": "World!!!"}
8-2-1. Docker imageの作成〜ECRへの登録
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でつけたタグに変更します。
{
"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インクリメントされます。最初にタスク定義を作成するときと同じコマンドであることに気持ち悪さを感じつつも、進めていきます。
aws ecs register-task-definition \
--cli-input-json file://ecs-fargate-demo.json \
--region $AWS_REGION
この時、コマンド結果で出力されたJSONを見て、タスク定義のリビジョンを確認しておきます。ここで確認を忘れても、AWSコンソールのタスク定義(Task definitions)から確認することができますので慌てる必要はありません。
8-2-4. Codedeloyのデプロイメント作成
以下のコマンドを実行することでデプロイすることができます。TaskDefinitionのところでecs-fargate-demo:2
とありますが、タスク定義の登録の際に確認したリビジョンを指定してあげてください。
また、application-name
やdeployment-group-name
については、CodeDeployで設定した値となります。--revision
のオプションの値のJSONが分かりにくいので、もう少しいい方法がないか検討中です。
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からデプロイの進捗状況を確認することができます。
7-2の最後に確認したALBのDNS名にアクセスして確認してみると{"Hello": "World!!!"}が表示されているのが家訓できるかと思います。
なお、サービスがBlueGreenではなく、ローリング更新の場合、aws ecs update-service
コマンドで実行できます。
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で割ると発生するエラーを意図的に生じさせてみます。
@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
というエラーが表示されていることが確認できます。
-
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/ ↩
-
https://dev.classmethod.jp/articles/awsdevday2020-deploy-fargate-easily/ ↩ ↩2
-
https://docs.aws.amazon.com/cli/latest/reference/ecs/create-cluster.html ↩