Cost Optimization Hubの推奨事項をSlackに定期的に通知する方法
はじめに
re:Invent2023で発表されたCost Optimization Hubは、コスト最適化サービスの推奨事項を集約してダッシュボードに表示してくれる便利なサービスです。
ただ、推奨事項を通知してくれる機能は無く(2025年2月現在だと)、毎度AWSコンソールからCost Optimization Hubのダッシュボードを確認する必要があります。
毎月、Optimization Hubのダッシュボードを確認するといったような、人力の運用もなくはないですが、流石に泥臭いので、推奨事項を定期的にSlackに通知するEventBridgeとLambdaを、Terraformで設定してみました。
設定
Cost Optimization Hubの有効化
デフォルトでは、Cost Optimization Hubは無効化されているので、有効化を行います(無料)。
Terraformのaws providerでは、Cost Optimization Hubの有効化を行うリソースが無いので(2025年2月現在だと)、local-execとaws cliを組み合わせて、有効化を行います。
リージョンはus-east-1
を指定します(Cost Optimization Hubはグローバルリソースなので)。
resource "terraform_data" "enable_cost_optimization_hub" {
provisioner "local-exec" {
command = <<-EOT
aws cost-optimization-hub update-enrollment-status --status Active --region us-east-1
EOT
}
}
Compute Optimizerの有効化
Cost Optimization Hubは、Compute Optimizerの推奨事項を集約することができるので、コンピュートリソースを使用しているリージョンのCompute Optimizerの有効化も行います(無料)。
resource "aws_computeoptimizer_enrollment_status" "main" {
status = "Active"
}
SlackのWebhook URLの取得
推奨事項をSlackに通知するのに使用するWebhook URLを取得します。
Webhook URLの取得方法は以下ブログ記事が参考になります。
Lambdaの作成
Lambdaを作成するTerraformコードのディレクトリツリーは以下の通りです。
ランタイムにはPythonを使用します。
├ python
├ layer
├ python
├ requirements.txt
├ src
├ lambda_function.py
├ main.tf
Lambdaレイヤーの作成
SlackのSDKを使用するので、パッケージを含んだLambdaレイヤーを作成します。
まず、requirements.txt
を以下のように設定し、python/layer
配下でpip install -t ./python -r requirements.txt
を実行します。
slack_sdk
python/layer
配下にpython
といった名前でパッケージを含んだディレクトリが作成されるので、その後、Lambdaレイヤーを作成します。
module "lambda_layer" {
# Module source
source = "terraform-aws-modules/lambda/aws"
version = "7.8.1"
# Module arguments
layer_name = "lambda-layer-cost-optimization-hub-recommendations"
description = null
create_layer = true
create_package = true
recreate_missing_package = false
source_path = "/python/layer"
compatible_runtimes = ["python3.12"]
compatible_architectures = ["arm64"]
}
Pythonコードの作成
lambda_function.py
を以下のようにコーディングします。
import boto3
import json
import os
from slack_sdk.webhook import WebhookClient
def lambda_handler(event, context):
cost_optimization_hub = boto3.client('cost-optimization-hub', region_name='us-east-1')
slack_webhook = WebhookClient(os.environ["SLACK_WEBHOOK_URL"])
try:
response = cost_optimization_hub.list_recommendations()
items = response['items']
except Exception as e:
error_message = f"Error: {str(e)}"
print(error_message)
if not items:
print("No recommendations found.")
return
attachments = format_attachments(items)
try:
slack_mention_id = os.environ.get("SLACK_MENTION_ID")
mention_text = f"{slack_mention_id}" if slack_mention_id else ""
response = slack_webhook.send(
text=mention_text,
attachments=attachments
)
print("Notify optimization hub recommendations successfully")
except Exception as e:
error_message = f"Error: {str(e)}"
print(error_message)
def format_attachments(items):
attachments = []
for item in items:
attachment = {
"title": f"CostOptimizationHub Recommendation ",
"fields": [
{
"title": "アカウントID",
"value": item['accountId'],
"short": True
},
{
"title": "リージョン",
"value": item['region'],
"short": True
},
{
"title": "リソースタイプ",
"value": item['recommendedResourceType'],
"short": True
},
{
"title": "毎月の推定削減額",
"value": f"{item['estimatedMonthlySavings']} {item['currencyCode']}",
"short": True
},
{
"title": "推定削減率",
"value": f"{item['estimatedSavingsPercentage']}%",
"short": True
},
{
"title": "推定月額コスト",
"value": f"{item['estimatedMonthlyCost']} {item['currencyCode']}",
"short": True
},
{
"title": "実装作業",
"value": item['implementationEffort'],
"short": True
},
{
"title": "推奨リソースの概要",
"value": item['recommendedResourceSummary'],
"short": False
},
],
"color": "#36a64f" # 緑色
}
attachments.append(attachment)
return attachments
いくつかポイントを抜粋して解説します。
- リージョンは
us-east-1
- Cost Optimization Hubはグローバルリソースなので
- SlackのWebhook URLは環境変数経由で設定
- センシティブなパラメーターなので
- bot3でCost Optimization Hubの推奨事項の一覧を取得
- 推奨事項が空の場合は、終了
- format_attachments関数で取得した推奨事項をリストから取り出し、Slack通知に適した形式に整形
- SlackのメンションIDは環境変数経由で設定
- メンション先の変更を容易にするため、ハードコーディングを避ける
- Slack SDKを使って、推奨事項をSlackに通知
Lambda関数の作成
先ほど作成したLambdaレイヤーとPythonコードを含める形で、Lambda関数を作成します。
data "aws_iam_policy_document" "lambda" {
statement {
effect = "Allow"
actions = ["cost-optimization-hub:ListRecommendations"]
resources = ["*"]
}
}
module "lambda_function" {
# Module source
source = "terraform-aws-modules/lambda/aws"
version = "7.8.1"
# Module arguments
## Basic information
function_name = "lambda-cost-optimization-hub-recommendations"
description = ""
package_type = "Zip"
create_package = true
recreate_missing_package = false
create_current_version_allowed_triggers = false
runtime = "python3.12"
handler = "lambda_function.lambda_handler"
source_path = "/python/src"
layers = [
module.lambda_layer.lambda_layer_arn
]
architectures = ["arm64"]
create_role = true
role_name = "lambda-role-cost-optimization-hub-recommendation"
policy_name = "lambda-policy-cost-optimization-hub-recommendation"
attach_policy_json = true
policy_json = data.aws_iam_policy_document.lambda.json
## Advanced Setting
tags = {
Name = "lambda-cost-optimization-hub-recommendations"
}
cloudwatch_logs_retention_in_days = var.lambda_logs_retention_in_days
## General Settings
timeout = 60
## Permission
# allowed_triggers = {}
## Environment Variables
environment_variables = {
SLACK_WEBHOOK_URL = var.slack_webhook_url
SLACK_MENTION_ID = var.notify_slack_mention_id
}
}
EventBridgeの作成
先ほど作成したLambdaを定期的に実行するEventBridgeを作成します。
以下の例では、毎月1日の12時にLambdaが実行されます。
resource "aws_iam_role" "scheduler" {
name = "optimization-hub-recommendation-schedule-role"
assume_role_policy = data.aws_iam_policy_document.assume_role["scheduler"].json
tags = {
Name = "optimization-hub-recommendation-schedule-role"
}
}
data "aws_iam_policy_document" "scheduler" {
statement {
effect = "Allow"
actions = ["lambda:InvokeFunction"]
resources = [module.lambda_function.lambda_function_arn]
}
}
resource "aws_iam_policy" "scheduler" {
name = "optimization-hub-recommendation-schedule-policy"
policy = data.aws_iam_policy_document.scheduler.json
tags = {
Name = "optimization-hub-recommendation-schedule-policy"
}
}
resource "aws_iam_role_policy_attachment" "scheduler" {
role = aws_iam_role.scheduler.name
policy_arn = aws_iam_policy.scheduler.arn
}
resource "aws_scheduler_schedule" "lambda" {
# Resource arguments
name = "optimization-hub-recommendation-schedule"
description = "Notify optimization hub recommendations"
group_name = "default"
state = "ENABLED"
flexible_time_window {
mode = "OFF"
}
schedule_expression = "cron(0 12 1 * ? *)"
schedule_expression_timezone = "Asia/Tokyo"
target {
arn = module.lambda_function.lambda_function_arn
role_arn = aws_iam_role.scheduler.arn
retry_policy {
maximum_event_age_in_seconds = "60"
maximum_retry_attempts = "3"
}
}
}
通知例
さいごに
Cost Optimization Hubの推奨事項の定期通知は、コスト削減対応の足掛かりになるかもなので、是非試して頂けたらと!
Discussion