AWS SDK for JS で ECSのタスク起動完了を確認したい
はじめに
こんにちは、スペースマーケットでバックエンドエンジニアをしています。
今回は、AWS SDK for JavaScript を利用して ECS のタスク起動完了を確認する方法について紹介します。
現状課題
弊社では、AWS の ECS を利用してバックエンドサービス(API)を運用しています。
日々、新しい機能の追加やバグの修正などを行っていて、その度に ECS のタスクを起動しているのですが、時折開発環境では発現しなかったバグが本番環境で発生することがあります。
新規の ECS のタスク起動時にこういったバグが発生した場合、起動 → 停止 → 起動 → 停止...を繰り返して永遠に新規タスクが立ち上がらないという状況に陥ってしまいます。
万が一このような状況にあっても既存の ECS タスクが正常起動した状態を維持するため、サービス自体が停止してしまうという状況に追い込まれることは少ないのですが、このような状況が発生していることを知る術が AWS のコンソールを確認する以外になかったので、AWS SDK を利用して ECS のタスク起動完了を確認する仕組みを構築するに至りました。
起動確認の処理の流れ
それでは具体的な実装について紹介していきたいのですが、今回の起動確認の仕組みを簡単にフローチャートで表すと以下のようになります。(※今回はタスク更新のための ECS サービス更新とタスク起動確認を同時に行なっています。)
上記の図を少し解説しますと、
今回タスクの起動完了にあたって確認すべき点については以下の 2 点となります。
- 既存のタスクが全て停止しているかどうか
- 新規のタスクが全て起動しているかどうか
上記 2 点を満たさない場合は、例外処理として Slack 通知等を行うようにして、タスクの起動完了を確認する仕組みとしています。
特に 1 点目の要件についてなのですが、実はこちらの要件に辿り着く前はサービスが安定状態(serviceStable
)に入った状態もしくは新規のタスクが起動状態(tasksRunning
)になるまでforWait
するという実装で進めていたのですが、実際に動かしてみるとサービス更新を実行しても、サービスが Unstable→Stable
という状態遷移をしていなかったり、新規のタスクがRunning
状態になっても、後のヘルスチェックで失敗してしまうことがあり、本当の意味でタスクの正常起動を確認することができませんでした。
そのため、既存のタスクが全て停止していること = 新規のタスクが全て起動していることと見做して こちらの確認方法に落ち着きました。
2 つ目の要件については、新規のタスクが本当に起動しているかを念押しで確認するために実装しています。
それでは実際の実装を見ていきましょう。
実装
import ECS from "aws-sdk/clients/ecs";
const ecsClient = new ECS({region: "ap-northeast-1"});
try {
// 既存タスク一覧取得
const oldTasks = await ecsClient
.listTasks({
family: 'sample-task',
cluster: 'sample-cluster'
serviceName: 'sample-service',
launchType: "FARGATE",
})
.promise();
// 新規タスクの起動命令(サービス更新)
await ecsClient
.updateService({
cluster: 'sample-cluster'
service: 'sample-service',
forceNewDeployment: true,
}).promise();
// 既存タスクの停止待ち
const data = await ecsClient
.waitFor("tasksStopped", {
cluster: 'sample-cluster'
tasks: oldTasks.taskArns,
$waiter: {
delay: 30,
maxAttempts: 20,
},
})
.promise()
.catch((err) => {
throw new Error("既存タスクの停止確認ができませんでした💥");
});
// 新規タスク一覧取得
const newTasks = await ecsClient
.listTasks({
family: 'sample-task',
cluster: 'sample-cluster'
serviceName: 'sample-service',
launchType: "FARGATE",
})
.promise();
// 新規タスク詳細取得
const describeTasksResult = await ecsClient
.describeTasks({
cluster: 'sample-cluster'
tasks: newTasks.taskArns,
})
.promise();
// 新規タスクRUNNING確認
const runningTasks = describeTasksResult.tasks.filter(
(task) => task.lastStatus === "RUNNING"
);
if (runningTasks.length === 0) throw new Error("新規タスクの起動が確認できませんでした💥");
console.log("サービスの更新が完了しました🚀");
} catch (err) {
// Slack通知等の例外処理
console.error(err);
}
そんなにわかりづらい点はない気がしますが、 waitFor
の$waiter
オプションについて少し補足しておきます。
$waiter
オプションは、waitFor
メソッドの第 2 引数に指定することで、指定した状態になるまでの待ち時間やリトライ回数を指定することができます。この実装の場合は、既存のタスクが停止するまでの待ち時間を 30 秒、最大リトライ回数を 20 回としています。もし、この待ち時間以内に既存のタスクが停止しなかった場合は、先ほど述べた通り、新規のタスクの起動時に何らかの問題が発生していると見做して、waitFor
メソッドのcatch
節で例外処理を行うようにしています。
どハマりしたポイント
本投稿の主旨とはちょっと外れてしまいますが、今回の実装で特にどハマりしたのが、listTasks
メソッドです。こちらでlaunchType: Fargate
を指定しているのですが、初めはこのパラメータを未設定にしていたため、EC2
タイプとFargate
タイプ両方のタスクを取得しようとして、EC2 の方の取得権限を設定しておらず、AccessDeniedException
が発生してしまいました。
上記の実装は Lambda 上で動かしていたので、500 回くらい Lambda のロールを見に行ったのですが、launchType: Fargate
を指定してみたところ、あっさりと解消しました。エラー文も ECS へのアクセス権限に問題がありそうな記述だったので分かりづらくハマりました 😇
ECS の EC2 タイプには ECS のアクセス権限だけではなく、EC2 のアクセス権限も必要 ということを胸に刻みます。
まとめ
いかがでしたでしょうか。今回は、AWS SDK を利用して ECS のタスク起動完了を確認する方法について紹介しました。
より詳しい SDK の使い方については、公式ドキュメントを参照していただければと思います。
AWS の SDK は、うまく扱えるようになるととっても便利ですし、他のサービスの SDK も積極的に利用できたらいいなと思いました!
ここまで読んでいただきありがとうございました。
最後に
弊社では、インフラ環境の構築や運用についても積極的に取り組んでいます。興味のある方はぜひ弊社のエンジニア採用ページをご覧ください。
スペースを簡単に貸し借りできるサービス「スペースマーケット」のエンジニアによる公式ブログです。 弊社採用技術スタックはこちら -> whatweuse.dev/company/spacemarket
Discussion