S3イベントをEventBridgeで受けてECSタスクで処理する(環境構築)
はじめに
S3イベントをトリガーとしてなんらかの処理を行いたい場合に最初に考えるのはLambdaでやる方法かと思います。
しかし、Lambdaだと実行時間15分の制限があり、最初はそれでも良いかもしれないけどデータが増えたら危ないかな?なんてことがあります。
そんな時にふと "s3 event ecs" と検索してみたところLambdaをかませずにEventBridgeでルール設定すればできそうだとわかりました。
それならば割とシンプルなシステム構成で実現できそうなので軽い気持ちでこれでやってみようと思ったら意外と情報が少なくて大変だったのでここに書き残しておこうと思います。
やりたいこと
今回はAWS環境の構築にCDKを利用します。構築する環境は以下の構成です。
S3にファイルがアップロードされたらそれをトリガーとしてECSタスクを起動して、アップロードされたファイルを読み込んでなんらかの処理をしたいです。
S3
まずはS3のバケットを用意します。
export class S3Stack extends cdk.Stack {
// Create Bucket
public bulkFilesBucket : s3.Bucket
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
this.bulkFilesBucket = new s3.Bucket(this, "TestBucket", {
bucketName: "test-bucket",
blockPublicAccess: s3.BlockPublicAccess.BLOCK_ALL,
eventBridgeEnabled: true,
});
};
}
ここでのポイントは eventBridgeEnabled
です。
trueにするとEventBridgeに各種イベントが飛ぶようになります。
ECS
VPC
ECSタスクを起動するVPCを用意します。
サブネットのところは一般的によくありそうな構成で書いてみましたが必要な環境に合わせて変えてください。
export class Vpcs {
public vpc: Vpc
constructor(scope: Construct, id: string) {
this.vpc = new Vpc(scope, id, {
ipAddresses: IpAddresses.cidr('10.0.0.0/16'),
defaultInstanceTenancy: DefaultInstanceTenancy.DEFAULT,
enableDnsSupport: true,
enableDnsHostnames: true,
subnetConfiguration: [
{
cidrMask: 24,
name: "public",
subnetType: ec2.SubnetType.PUBLIC,
},
{
cidrMask: 24,
name: "App",
subnetType: ec2.SubnetType.PRIVATE_WITH_EGRESS,
},
{
cidrMask: 28,
name: "RDS",
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
},
],
natGateways: 1,
// maxAzs: 2,
});
this.vpc.applyRemovalPolicy(cdk.RemovalPolicy.DESTROY)
}
}
SecurityGroup
ECSタスク用のセキュリティグループを用意します。
interface SecurityGroupProps extends StackProps {
vpc: Vpc;
}
export class SecurityGroups {
public ecsSecurityGroup: SecurityGroup;
constructor(scope: Construct, id: string, props: SecurityGroupProps){
this.ecsSecurityGroup = new SecurityGroup(scope, 'EcsSg', {
vpc: props.vpc,
})
}
}
ECS & EventBridge
次にECSのタスク定義を書いていきます。
今回はEventBridgeのルールもEcsStack内に一緒に定義します。
export class EcsStack extends cdk.Stack {
constructor(scope: Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props)
// VPC
const vpc = new Vpcs(this, id + 'Vpc').vpc
// SecurityGroup
const sg = new SecurityGroups(this, id, {
vpc: vpc
});
// ECS Cluseter
const cluster = new Cluster(this, id + 'Cluster', {
clusterName: 'EventHandlerCluster',
vpc: vpc,
});
// EventBridgeから起動するタスク定義
const s3EventHandleTask = new FargateTaskDefinition(this, 's3EventHandleTask', {
memoryLimitMiB: 512,
});
s3EventHandleTask.addContainer('handlerTaskContainer', {
containerName: "s3EventHandleTaskContainer",
image: ContainerImage.fromAsset('../../', {
platform: Platform.LINUX_AMD64,
}),
command:[
"/app",
],
logging: LogDrivers.awsLogs({streamPrefix: 's3EventHandleTask'})
});
s3EventHandleTask.addToTaskRolePolicy(
new PolicyStatement({
actions: [
's3:*',
],
resources: ["*"]
})
)
// EventBrigeのルール定義とターゲットの設定
const rule = new Rule(this, 's3EventRule', {
ruleName: 's3EventRule',
enabled: true,
eventPattern: {
"source": ["aws.s3"],
"detailType": ["Object Created"],
"detail": {
"bucket": {
"name": ["test-bucket"]
}
}
},
targets: [new EcsTask({
cluster: cluster,
taskDefinition: s3EventHandleTask,
taskCount: 1,
securityGroups: [sg.ecsSecurityGroup],
containerOverrides: [{
containerName: "s3EventHandleTaskContainer",
environment: [
{
name: "bucketName",
value: EventField.fromPath("$.detail.bucket.name")
},
{
name: "objectKey",
value: EventField.fromPath("$.detail.object.key")
},
]
}],
})]
});
}
}
ここでのポイントは containerOverrides
の部分です。
ここの environment
の記述をすることでEventBrigeが受信したイベントの必要な箇所を環境変数に入れてコンテナを起動することができます。
これのやり方があまりサンプルが見つけられなくて試行錯誤しました。
EventField.fromPath
のパスの指定はS3からのイベントについては以下のJsonを想定して記載します。
上記の例ではbucket name と object keyしか設定していませんがイベント内の他の項目取得できます。
{
"version": "0",
"id": "436cee3f-1218-b9b1-293d-cc0506eeba5f",
"detail-type": "Object Created",
"source": "aws.s3",
"account": "XXXXXXXXXXXX",
"time": "2021-11-30T03:56:14Z",
"region": "us-east-1",
"resources": ["arn:aws:s3:::test-bucket"],
"detail": {
"version": "0",
"bucket": { "name": "test-bucket" },
"object": {
"key": "test.png",
"size": 93617,
"etag": "035bf1fc4b0a420b23c170769f6b7dfe",
"sequencer": "0061A5A0DE6C9F3646"
},
"request-id": "DMGFBG77TRA4ZWAJ",
"requester": "XXXXXXXXXXXX",
"source-ip-address": "14.12.5.225",
"reason": "PutObject"
}
}
参考にした記事
イベントを環境変数にセットしてECSタスクを起動するにはinput transformerをすれば良いというのはわかったのですがそれをどう書くのか?も色々探りました。
この記事がEventBrigeの先がLambdaだったのが自分のやりたいことに対してはおしかった
終わりに
いくらか情報が少ないところがありこんなことできるのかな?それはどうやって書くのかな?と試行錯誤したところがありましたのでまとめてみました。
これから同じようなことをする方に少しでも参考になったら嬉しいです。
動作確認に使った全ソースコードは以下に置きました。
Discussion