Working with AWS is exciting and enriching, but one aspect that can easily be overlooked in the day-to-day hustle is managing costs. Keeping track of your AWS spending often involves logging into your AWS Management Console and manually checking the cost management page, which can be a hassle.
That's why I built an AWS cost notification bot using AWS CDK (Cloud Development Kit) that sends daily updates about your AWS costs directly to your Slack workspace. The completed project is available on my GitHub repository, but in this post, I'll take you through the steps of creating your own.
❯ cdk --version
2.81.0 (build bd920f2)
❯ aws --version
aws-cli/2.11.2 Python/3.11.2
mkdir aws_billing_lambda
cd aws_billing_lambda
cdk init app --language typescript
yarn add aws-cdk-lib aws-sdk axios @slack/webhook
aws ssm put-parameter \
--name "/billing_lambda_app/slackWebhookUrl" \
--value "[Your SlackWebhookUrl]" --type "SecureString"
setup handler
const billingLambda = new NodejsFunction(this, "BillingLambda", {
entry: "./lambda/index.ts",
handler: "handler",
timeout: cdk.Duration.minutes(5),
memorySize: 1024,
});
set ssm policy to load slackWebhookUrl
const ssmPolicy = new iam.PolicyStatement({
actions: ["ssm:GetParameter"],
resources: ["arn:aws:ssm:*:*:parameter/billing_lambda_app/*"],
});
billingLambda.addToRolePolicy(ssmPolicy);
set kms policy, this is necessary for decrypt
const kmsPolicy = new iam.PolicyStatement({
actions: ["kms:Decrypt"],
resources: ["*"],
});
billingLambda.addToRolePolicy(kmsPolicy);
attach GetCostAndUsage policy
const cePolicy = new iam.PolicyStatement({
actions: ["ce:GetCostAndUsage"],
resources: ["*"],
});
billingLambda.addToRolePolicy(cePolicy);
set rule
const rule = new events.Rule(this, "BillingLambdaRule", {
schedule: events.Schedule.cron({ minute: "0", hour: "4" }),
});
const ssm = new AWS.SSM();
const slackWebhookUrlParam = await ssm
.getParameter({
Name: "/billing_lambda_app/slackWebhookUrl",
WithDecryption: true,
})
.promise();
const slackWebhookUrl = slackWebhookUrlParam.Parameter?.Value || "";
const params: AWS.CostExplorer.Types.GetCostAndUsageRequest = {
TimePeriod: {
Start: firstDayOfMonth.toISOString().substring(0, 10),
End: today.toISOString().substring(0, 10),
},
Granularity: "MONTHLY",
Metrics: ["UnblendedCost"],
};
const data = await costExplorer.getCostAndUsage(params).promise();
if (data && data.ResultsByTime && data.ResultsByTime[0]) {
const amount = data.ResultsByTime[0].Total?.UnblendedCost.Amount;
console.log(`Total cost for the month so far: $${amount}`);
const message = `Total cost for the month so far: $${amount}`;
await webhook.send({
text: message,
});
} else {
console.log("No cost data available");
}
cdk deploy