【AWS】ECSタスクイベントのログを取るLambdaを作成する
はじめに
Fargateはインスタンスのお世話をせずに、コンテナの運用ができて便利ですよね。
しかし、インスタンスのログを見られないので、ECSタスクの状態変化が追えず困ることがあります。
「コンテナを起動するAPIは呼び出されているはずなのに、アプリケーションが実行されていない…」なんてときには、何が起こっているのかは闇の中です。
公式ドキュメントによると、ECSタスクイベントをCloudWatch EventsでキャプチャしてLambdaに流すことで、タスクイベントのログを取ることができるようです。
今回は☝︎のドキュメントを参考に、AWS CDKで実際にリソースを作成してみます。
CDKはTypeScript、Lambdaの関数はGoを使って実装します
バージョン
- node 12.18.1
- go 1.17
- cdk 1.125.0
事前に準備しておくリソース
- ECS Cluster
ディレクトリ構成
.
├── lambda
│ ├── go.mod
│ ├── go.sum
│ └── main.go
├── bin
│ └── cdk-app.ts
├── lib
│ └── listen-ecs.ts
(略)
CDKソースコード
import * as cdk from '@aws-cdk/core';
import * as targets from '@aws-cdk/aws-events-targets';
import * as events from '@aws-cdk/aws-events';
import * as lambda from '@aws-cdk/aws-lambda-go';
export class ListenEcsStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
const clusterArn = this.node.tryGetContext('cluster-arn')
const fn = new lambda.GoFunction(this, 'listen-ecs-function', {
functionName: 'listen-ecs-function',
entry: 'lambda',
})
const targetFunction = new targets.LambdaFunction(fn)
new events.Rule(this, 'EcsTaskChangeRule', {
eventPattern: {
source: ['aws.ecs'],
detail: { 'clusterArn': [clusterArn] },
detailType: ['ECS Task State Change']
},
targets: [targetFunction],
});
}
}
事前に作成したECS ClusterのARNを、cdk.json
またはコマンドでコンストラクタ外部から渡してあげるようにします。
Goのソースコードのパスを指定してLambdaの関数を作成し、作成した関数をCloudWatch Eventsのルールのターゲットとすることで、イベントが発生するごとに関数にイベント情報が流れていきます。
ルールのソースにはaws.ecs
を指定し、特定のクラスターの、特定のタスクイベントのみマッチするパターンを設定します。
Lambdaソースコード
package main
import (
"context"
"encoding/json"
"fmt"
"github.com/aws/aws-lambda-go/events"
"github.com/aws/aws-lambda-go/lambda"
)
func handler(ctx context.Context, event events.CloudWatchEvent) error {
b, err := json.Marshal(event)
if err != nil {
return err
}
fmt.Println(string(b))
return nil
}
func main() {
lambda.Start(handler)
}
ルールのターゲットとなる関数は、発生したイベントを受け取り処理することができます。
イベントはCloudWatchEvent
という型の値として、流れてきます。
構造体のまま出力すると少し見辛いので、JSONに変換して出力しています。
type CloudWatchEvent struct {
Version string `json:"version"`
ID string `json:"id"`
DetailType string `json:"detail-type"`
Source string `json:"source"`
AccountID string `json:"account"`
Time time.Time `json:"time"`
Region string `json:"region"`
Resources []string `json:"resources"`
Detail json.RawMessage `json:"detail"`
}
ログ確認
適当にECSタスクを作成して、出力されたログを確認します。
{
"version": "0",
"id": "44f7e6da-de4d-7a11-e35c-c0155c8be065",
"detail-type": "ECS Task State Change",
"source": "aws.ecs",
"account": "xxxxxxxxxxx",
"time": "2021-10-02T06:58:20Z",
"region": "ap-northeast-1",
"resources": [
"arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:task/sample/bb462024fc644eefbe5495695e405414"
],
"detail": {
"attachments": [
{
"id": "663cfa45-2caa-42df-ac53-26ea91c20a54",
"type": "eni",
"status": "PRECREATED",
"details": [
{
"name": "subnetId",
"value": "subnet-6bae7040"
}
]
}
],
"availabilityZone": "ap-northeast-1d",
"clusterArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:cluster/sample",
"containers": [
{
"containerArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:container/sample/bb462024fc644eefbe5495695e405414/036b4462-e0c1-467b-9103-dec662299434",
"lastStatus": "PENDING",
"name": "sample",
"image": "xxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/repository:latest",
"taskArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:task/sample/bb462024fc644eefbe5495695e405414",
"networkInterfaces": [],
"cpu": "0"
}
],
"cpu": "256",
"createdAt": "2021-10-02T06:58:20.45Z",
"desiredStatus": "RUNNING",
"enableExecuteCommand": false,
"ephemeralStorage": {
"sizeInGiB": 20
},
"group": "family:sample",
"launchType": "FARGATE",
"lastStatus": "PROVISIONING",
"memory": "512",
"overrides": {
"containerOverrides": [
{
"name": "sample"
}
]
},
"platformVersion": "1.4.0",
"taskArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:task/sample/bb462024fc644eefbe5495695e405414",
"taskDefinitionArn": "arn:aws:ecs:ap-northeast-1:xxxxxxxxxxx:task-definition/sample:6",
"updatedAt": "2021-10-02T06:58:20.45Z",
"version": 1
}
}
構造体をそのままJSONに変換したので情報量が結構多いですね。
実際の運用の際には項目を精査する必要がありそうです。
本記事での記載は割愛しますが、開始から終了まで状態が変化するごとにログが出力されていることが確認できました。
さいごに
サーバーレスでインフラを構築している時こそ、ログやアラームの設定をより丁寧にしておくことが必要なのかなと思っています。
なにか不具合が起こった時にサーバーをチェックすることが難しいことを踏まえた上で、サーバーレスと付き合っていきたい所存であります。
参考
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/task-lifecycle.html
- https://dev.classmethod.jp/articles/ecs-state-change/
- https://docs.aws.amazon.com/ja_jp/AmazonECS/latest/developerguide/ecs_cwet.html
- https://docs.aws.amazon.com/cdk/api/latest/docs/aws-construct-library.html
- https://dev.classmethod.jp/articles/aws-lambda-go/
Discussion