AWS CDKに入門し、AWS費用をSlack通知する処理を書き直した
かなり昔にAWS費用をSlack通知する処理を組んでいたのですが、いつの間にか止めており、最近一応入れておきたくなったのでこれを機会にAWS CDKに入門しました。
何度かServerless Frameworkを使ってLambdaとS3と何かしらを組み合わせるような構成は作っていたので、そういう感じかなと思っていましたが、JavaScriptで書くというのもあってCDKの方が分かりやすいなと思いました。
導入
ドキュメントに従い、インストール。ブートストラップも実行しておきます。
$ npm install -g aws-cdk
$ cdk bootstrap aws://ACCOUNT-NUMBER/REGION
プロジェクトの生成
$ cdk init app --language typescript
TypeScript 指定できるのはうれしいですね。
スタックの構築
全部書くと長いので一部のみ
import { NodejsFunction } from 'aws-cdk-lib/aws-lambda-nodejs';
const checkCost = new NodejsFunction(this, 'checkAWSCost', {
entry: 'lib/lambda/check-cost.ts',
handler: 'handler',
runtime: Runtime.NODEJS_18_X,
timeout: cdk.Duration.seconds(30),
bundling: {
forceDockerBundling: false, // use local esbuild
},
initialPolicy: [
new cdk.aws_iam.PolicyStatement({
actions: ['ce:GetCostAndUsage'],
resources: ['*']
})
]
});
AWS CDK v2からは aws-cdk-lib/aws-lambda-nodejs
を使うようになったみたいです。なんとなく探しやすい気がします。
NodejsFunction
を使うことで、TypeScriptのコードもデプロイ時にコンパイルできるようです。ただ、デフォルトだとDockerの専用イメージでのビルドになるようで、そこまで大掛かりにはしたくないのでオプションでその動作を止め、別途esbuildを入れてそちらを使うようにしています。
// スケジュールの設定
new cdk.aws_events.Rule(this, "cost check handler schedule", {
// JST で月曜 AM9:00 に定期実行
// see https://docs.aws.amazon.com/ja_jp/AmazonCloudWatch/latest/events/ScheduledEvents.html#CronExpressions
ruleName: 'weekly-cost-check',
schedule: cdk.aws_events.Schedule.cron({minute: "0", hour: "0", weekDay: 'mon'}),
targets: [new cdk.aws_events_targets.LambdaFunction(checkCost, { retryAttempts: 2 })],
});
スケジュール実行はCloudwatch Scheduleを使うのが推奨されているようですが、書き方がイマイチ分からなかったので、一旦Ruleで書いています。
scheduleパラメーターはcron記法で書くとどうにも分かりづらいんですが、Scheduleのcron関数で書くとだいぶ分かりやすくて良いですね。
Lambda関数の実装
LambdaからはCost Explorerから価格情報を取得し、フォーマットを加工した上でSlackに投げています。
Cost Explorer
Cost Explorerからはトータルとサービス別で取得しています。
import { GetCostAndUsageCommand } from "@aws-sdk/client-cost-explorer";
export function totalCostCommand(period: { start: string; end: string }) {
return new GetCostAndUsageCommand({
TimePeriod: {
Start: period.start,
End: period.end,
},
Granularity: 'MONTHLY',
Metrics: [
'UnblendedCost'
],
});
}
export function monthlyCostByServiceCommand(period: { start: string; end: string }) {
return new GetCostAndUsageCommand({
TimePeriod: {
Start: period.start,
End: period.end,
},
Granularity: 'MONTHLY',
Metrics: [
'UnblendedCost'
],
GroupBy: [
{
'Type': 'DIMENSION',
'Key': 'SERVICE'
}
]
});
}
このコマンドをそれぞれ投げて取得しています。
const command = monthlyCostByServiceCommand(period)
const costs = await CostExplorer.send(command);
Slack通知
@slack/web-apiを使うのがいいかなと思ったのですが、どうにも上手く動かず、結局 chat.postMessageへのPOSTリクエストになりました。
メモ
途中API GatewayではなくLambdaの関数URLをSlackのEvent Subscriptionで登録する方法を試みましたが、verify後もスケジューラーで実行した際に上手く動かなかったりで原因が掴めなかった。
AWS Lambda で Slack Bot イベントハンドラを作る - げっとシステムログ
やってみて
Slackへの通知でドハマりしたんですが、それ以外は割りとすんなりいけました。
CDKで書いてみて、そこまで変わったことをしていないというのはあると思いますが、やはり書きやすいというところと、各種サービス周りが @aws-cdk-lib/
辺りにあるので探しやすかったり扱いやすいのがいいですね。CDK v1もあると思うので注意しないとですが…
また、構成をコードで管理できるので書き慣れると同じようなものをさっと作って構築できるようになるんだろうなと思うとだいぶ便利そうだなと思いました。
その他参考にしたもの
AWS LambdaでSlackアプリを動かす - cockscomblog? プライベートな用事でサーバサイドで何かやりたい場合、サーバレスな構成が第一選択になる。規模が十分に小さい場合、サーバレスにした方が安い。常にインスタンスが立ち上がっているような構成は(たとえ冗長構成を取らなくても)プライベートな用事程度では大げさになる。またサーバレスな構成は放置しやすいのも魅力である。 Lambdaで動くcockscombot 最近、サーバサイドで何かしたあとの通知先としてSlackを使っている。Slackはちょっとしたユーザーインターフェースの代わりになる。その延長線上でSlackアプリを作ってみようと考えた。Slackが提供しているBoltというのを使うと、Slackアプ… cockscomblog? Lambda 関数の環境変数の設定に AWS Secrets Manager を使ってみた話 | DevelopersIO Lambda 関数の環境変数の設定に AWS Secrets Manager を使ってみた話 | DevelopersIO aws-cdk-examples/typescript/lambda-cron at main · aws-samples/aws-cdk-examples Example projects using the AWS CDK. Contribute to aws-samples/aws-cdk-examples development by creating an account on GitHub. GitHub