ECRへのレプリケーションをトリガーにECSのサービス更新を実行する
はじめに
こんにちは、SHIFT にて自動化アーキテクトとしてテスト自動化・DevOps 導入支援などをしていますKatayamaです。
バックエンドの Build 成果物(docker image)が Deploy 環境にレプリケートされた事をトリガーに、ECS のサービス更新を行う CD の構築を行ったので、それに関する Tips のようなものを書き残しておこうと思います。
(前回は Cloud Front のキャッシュ削除についての記事だったが、この記事は ECS のサービス更新についての記事になる。)
※マルチベンダーなどの場合、開発資材のやり取り(コピー・複製=レプリケーション)は必要になるので今回もその要望から始まった。
構成イメージ
何らかのトリガーで Code Build が Start すると、ECR に Build 成果物である docker image が push され、それをトリガーにレプリケートが走る。レプリケーション先の環境でレプリケート後に自動で Deploy(ECS のサービス更新)を実行させる。
図で示すと以下のようになる。
ECS のサービス更新
今回の Deploy は、Blue/Green Deploy ではなく、単純に ECS のサービス更新を行う方式で行った(検証環境という事でダウンタイム発生が許容されていたので)。
Deploy 自体は、レプリケーションで ECR に新規で image が push されるのでそれを Cloud Watch Events で検知してそれをトリガーに Lambda 関数を実行し、以下の AWS CLI コマンドで行っている事と同じ事を AWS SDK(今回は JavaScript)で実行させる、という感じで行う。
aws ecs update-service --cluster {cluster名} --service {service名}
※ --cluster は省略可能だがその場合デフォルトのクラスターになるので、UpdateService の API を実行する ARN が arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:service/default/{service 名} になるので注意。
※Cloud Watch Event にAmazon Elastic Container Registry イベントがあるので、設定する際はこれに思えるが、レプリケーションで ECR に image が push される場合は、 ReplicateImage という Cloud Trail に記録されるイベントになるので、これを Cloud Watch Event の AWS API Call via CloudTrail で捕捉させて Deploy を実行する必要がある。
IAM
Lambda 関数を作成するために必要な IAM( lambda: や iam: )については、ここでは省略する(S3 へのレプリケーションをトリガーに Cloud Front のキャッシュ削除を実行するの方を参照)。ここでは Cloud Watch Event のルールを作成するための IAM について見ていく。
今回構築してみて分かった事だが、Management Console から設定する場合と、AWS CLI で設定する場合とでは、必要になる権限に大きな違いがあったのでそれぞれの設定方法で必要な権限について見ていく。
※Cloud Watch Events は cloudwatch: ではなく events: (EventBridge)なので注意。
Management Console からの設定
Management console からのルール作成の場合、参考に示したCloudWatch Events API オペレーションおよびアクションに必要な許可の権限全てがないと、以下のように「今すぐ始める」ボタンが非活性で作成できない。
したがって以下のような権限(インラインポリシー)が必要になる。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"events:PutEvents",
"events:DeleteRule",
"events:PutTargets",
"events:DescribeRule",
"events:EnableRule",
"events:PutRule",
"events:RemoveTargets",
"events:ListTargetsByRule",
"events:DisableRule"
],
"Resource": [
"arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/replication-deploy-by-console",
"arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:event-bus/*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"events:ListRuleNamesByTarget",
"events:ListRules",
"events:TestEventPattern"
],
"Resource": "*"
}
]
}
・参考:CloudWatch Events API オペレーションおよびアクションに必要な許可
AWS CLI コマンドからの設定
AWS CLI コマンドでルールを作成する場合には、以下の IAM のみで OK で、Management Console から設定する時よりも最小権限で済む。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": ["events:PutTargets", "events:PutRule"],
"Resource": "arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/replication-deploy-by-cli"
}
]
}
Lambda 関数のトリガーを設定する
ここでも Management Console から設定する場合と、AWS CLI コマンドで設定する場合の 2 パターンを見ていく。
Management Console からの設定
Management Console 上で設定すると以下のような感じ。
{
"source": ["aws.ecr"],
"detail-type": ["AWS API Call via CloudTrail"],
"detail": {
"eventSource": ["ecr.amazonaws.com"],
"eventName": ["ReplicateImage"]
}
}
設定が完了すると Lambda のトリガーに以下のように表示される。
AWS CLI コマンドからの設定
以下のコマンドを順番に実行していけば設定できる。
aws events put-rule \
--name "replication-deploy-by-cli" \
--event-pattern file://event-json.json \
--description "AWS CLIから作成したreplication-deployのルール"
aws lambda add-permission \
--function-name replication-deploy-lambda \
--statement-id replication-deploy-by-cli \
--action 'lambda:InvokeFunction' \
--principal events.amazonaws.com --source-arn arn:aws:events:ap-northeast-1:xxxxxxxxxxxx:rule/replication-deploy-by-cli
aws events put-targets \
--rule replication-deploy-by-cli \
--targets "Id"="1","Arn"="arn:aws:lambda:ap-northeast-1:xxxxxxxxxxxx:function:replication-deploy-lambda"
コマンドについて少し補足をすると、
1, aws events put-rule を実行すると、ターゲットのないイベントルールが作成される
2, aws lambda add-permission を実行すると、以下の画像のように EventBridge からのアクセスを信頼し Lambda のトリガーに EventBridge(Cloud Watch Event)を設定できるようになる(ルールのターゲットが未設定なので赤字のエラーが出ている)
3, aws events put-targets を実行すると、ルールにターゲットが設定されトリガーとしての設定が完了する(設定完了後の Lambda のトリガーの状況は上記のManagement console からの画像を参照)
※event-json.json の中身はManagement Console からの設定の JSON と同じ。
※add permission コマンドで events:(EventBridge)からの Lambda へのアクセス・設定を信頼するのがポイントで、これをしないとちゃんと設定できない。
・参考:ステップ 2: ルールを作成する
Lambda 関数の実装
イベントトリガーの場合には Lambda 関数に Event オブジェクトが渡ってくるが、まずは ECR へのレプリケーションで image が push される時に Cloud Trail に記録され、Cloud Watch Event で検知するイベントの Event オブジェクトがどのようなものか?を見てみる。
その後、その Event オブジェクトの中身から Deploy(ECS UpdateService)を行うのに必要な情報を抜き出し実際にそれを実行するコードを考える。
Lambda に渡ってくる Event オブジェクトの中身
まず、Cloud Trail に記録されるオブジェクトだが、以下のようなオブジェクトが記録される(関係のある部分のみに省略して書いている)。
{
"eventVersion": "1.04",
"eventSource": "ecr.amazonaws.com",
"eventName": "ReplicateImage",
"awsRegion": "us-east-2",
"requestParameters": {
...
},
"responseElements": {
...
},
"requestID": "cb8c167e-EXAMPLE",
"eventID": "e3c6f4ce-EXAMPLE",
"resources": [
{
"ARN": "arn:aws:ecr:us-east-2:123456789012:repository/testrepo",
"accountId": "123456789012"
}
],
"eventType": "AwsApiCall",
}
そしてこの Cloud Trail に記録されるイベントを Cloud Watch Events の AWS API Call via CloudTrail で捕捉するので、Lambda 関数に渡ってくる Event オブジェクトの中身としては以下のような JSON になる(一部省略している)。
ポイントは、 Cloud Trail に記録されるイベントを Cloud Watch Events で捕捉する場合、CloudWatch イベントのイベントパターンで書かれている JSON の detail-type が AWS API Call via CloudTrail になり、detail が Cloud Trail に記録されるイベントのオブジェクト(上記)になるという事。
{
"version": "0",
"id": "6a7e8feb-b491-4cf7-a9f1-bf3703467718",
"detail-type": "AWS API Call via CloudTrail",
"source": "aws.ecr",
"region": "us-east-2",
"resources": [],
"detail": {
"eventVersion": "1.04",
"eventSource": "ecr.amazonaws.com",
"eventName": "ReplicateImage",
"awsRegion": "us-east-2",
"requestParameters": {
...
},
"responseElements": {
...
},
"requestID": "cb8c167e-EXAMPLE",
"eventID": "e3c6f4ce-EXAMPLE",
"resources": [
{
"ARN": "arn:aws:ecr:us-east-2:123456789012:repository/testrepo",
"accountId": "123456789012"
}
],
"eventType": "AwsApiCall",
}
}
・参考:Amazon ECR ログファイルエントリの概要
・参考:CloudTrail ログイベントリファレンス
・参考:CloudWatch イベントのイベントパターン
Lambda 関数(index.handler)の実装
今回は Node.js の Lambda 関数で Deploy を実行させるので、実装としては以下のようした。
const { ECSClient, UpdateServiceCommand } = require("@aws-sdk/client-ecs");
const client = new ECSClient({ region: process.env.REGION });
const serviceRegex = new RegExp(process.env.ECR_SERVICE_REGEX);
const clustreRegex = new RegExp(process.env.ECR_CLUSTER_REGEX);
exports.handler = async (event) => {
try {
const service = event.detail.resources[0].ARN.match(serviceRegex)
? event.detail.resources[0].ARN.match(serviceRegex)[1]
: undefined;
const cluster = event.detail.resources[0].ARN.match(clustreRegex)
? event.detail.resources[0].ARN.match(clustreRegex)[1]
: undefined;
if (!service && !cluster) {
console.log("not match regex", event.detail.resources[0].ARN);
return "";
}
const input = {
service, // service: service の ES6省略形
cluster, // cluster: cluster の ES6省略形
forceNewDeployment: true,
};
console.log("input", input);
const command = new UpdateServiceCommand(input);
const response = await client.send(command);
console.log("status", response.$metadata.httpStatusCode);
console.log("serviceArn", response.service.serviceArn);
return "";
} catch (error) {
return errorHandler(error);
}
};
const errorHandler = (error) => {
const obj = {};
obj["status"] = 500;
obj["message"] = error.message;
obj["stack"] = error.stack;
obj["result"] = "ng";
if (error.$metadata) {
obj["status"] = error.$metadata.httpStatusCode;
}
console.log("errorHandler", obj);
return "";
};
※実装について何点か補足すると、
・if (!service && !cluster)
三項演算子で undefined にせず、if()の中に event.detail.resources[0].ARN.match(serviceRegex) を書く事もできるが長いのであえて上記のような実装にした
・console.log()
今回は Cloud Watch Events(EventBridge)が Lambda 関数のトリガーであり、return された結果を人が見る事ができないので、何が起きているか分かるように敢えて console.log()で log に出力させるようにした
・return "";
これも今回 S3 がトリガーで return された結果を人が見れないので""(空文字)を返すようにした(何か JSON を返しても意味ないと判断)。
・参考:ECS Client - AWS SDK for JavaScript v3
・参考:Class UpdateServiceCommand
Lambda 関数の実行ロールの設定
Lambda 関数が他の AWS サービスに対して何らかの操作を行うには、実行ロールでその権限を設定する必要がある。今回は ECS の UpdateService が実行できればいいので以下のようなインラインポリシーを実行ロールに追加でアタッチするれば OK。
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "ecs:UpdateService",
"Resource": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxxx:service/{cluster名}/{service名}"
}
]
}
まとめとして
前回は Cloud Front のキャッシュ削除について扱ったが、ECS についても同じような考え方でレプリケーション後にそれをトリガーにして Deploy を実行させる事ができた。
■このライターの他の記事を読む
__________________________________
お問合せはお気軽に
https://service.shiftinc.jp/contact/
SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/
SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/
SHIFTの導入事例
https://service.shiftinc.jp/case/
お役立ち資料はこちら
https://service.shiftinc.jp/resources/
SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/