Compute OptimizerのレコメンデーションをEventBridge Scheduler+Lambdaで定期通知してみた

2023.06.27

こんにちは、ゲームソリューショングループのsoraです。
今回は、Compute OptimizerのレコメンデーションをEventBridge Scheduler+Lambdaで定期通知してみたことについて書いていきます。

構成

EventBridge Schedulerで指定した間隔でLambdaを呼び出して、Compute Optimizerのレコメンデーションを取得する構成です。
取得した後は、SNS経由でSlack通知するようにします。

取得する情報について、EC2で過剰なプロビジョニングとなっているサーバに関してのサマリーを取得します。

AWSマネジメントコンソールで表示されているようなサーバごとの推奨事項を通知しようと思ったのですが、単純に取得できずに処理が複雑化しそうだったため、サマリーとしています。
サーバごとの推奨事項を取得するには、CSVエクスポートを絡めればおそらく可能だと思います。

Terraformでの作成

Terraformを使って作成します。
Compute Optimizerのチェック結果はバージニア北部から取得する必要があります。

main.tf

terraform {
    #AWSプロバイダーのバージョン指定
    required_providers {
        aws = {
            source  = "hashicorp/aws"
            version = "~> 4.51.0"
        }
    }
}
#AWSプロバイダーの定義
provider aws {
    # Compute Optimizerのチェック結果はバージニア北部から取得する必要がある
    region = "us-east-1"
}
#AWSアカウントIDの取得
data aws_caller_identity current {}


#============ IAMロール作成 start ============
##EventBridge Sheduler 
data aws_iam_policy_document events_assume_role {
    statement {
        effect = "Allow"
        principals {
            type = "Service"
            identifiers = ["scheduler.amazonaws.com"]
        }
        actions = ["sts:AssumeRole"]
    }
}
resource aws_iam_role iam_for_events {
    name               = "EventBridge_ComputeOptimizer_Notification_Role"
    assume_role_policy = data.aws_iam_policy_document.events_assume_role.json
    managed_policy_arns = ["arn:aws:iam::aws:policy/service-role/AWSLambdaRole"]
}

##Lambda(Compute Optimizerからの情報取得用)
data aws_iam_policy_document lambda_assume_role {
    statement {
        effect = "Allow"
        principals {
            type = "Service"
            identifiers = ["lambda.amazonaws.com"]
        }
        actions = ["sts:AssumeRole"]
    }
}
resource aws_iam_role iam_for_lambda {
    name               = "Lambda_ComputeOptimizer_Notification_Role"
    assume_role_policy = data.aws_iam_policy_document.lambda_assume_role.json
    inline_policy {
        name = "my_inline_policy"
        policy = jsonencode({
            Version = "2012-10-17"
            Statement = [
                {
                    Action   = ["sns:Publish"]
                    Effect   = "Allow"
                    Resource = "*"
                },
            ]
        })
    }
    managed_policy_arns = [
        "arn:aws:iam::aws:policy/ComputeOptimizerReadOnlyAccess"
    ]
}
#============ IAMロール作成 end ============


#============ リソース作成 start ============
##SNS(トピック)
###トピックの作成
resource aws_sns_topic compute_optimizer_topic {
    name = "ComputeOptimizer-notification-topic"
}
data aws_iam_policy_document sns_assume_role {
    statement {
        effect = "Allow"
        principals {
            type = "Service"
            identifiers = ["lambda.amazonaws.com"]
        }
        actions = ["sns:Publish"]
        resources = [aws_sns_topic.compute_optimizer_topic.arn]
    }
}
resource aws_sns_topic_policy sns_topic_policy {
    arn    = aws_sns_topic.compute_optimizer_topic.arn
    policy = data.aws_iam_policy_document.sns_assume_role.json
}

##Lambda(Compute Optimizerからの情報取得用)
data archive_file lambda_computeoptimizer {
    type        = "zip"
    source_file = "lambda/lambda_computeoptimizer.py"
    output_path = "lambda_computeoptimizer.zip"
}
resource aws_lambda_function lambda_computeoptimizer {
    filename      = "lambda_computeoptimizer.zip"
    function_name = "compute-optimizer-notification"
    role          = aws_iam_role.iam_for_lambda.arn
    handler       = "lambda_computeoptimizer.lambda_handler"
    source_code_hash = data.archive_file.lambda_computeoptimizer.output_base64sha256
    runtime = "python3.9"
    environment {
        variables = {
            SNSTopicArn = aws_sns_topic.compute_optimizer_topic.arn,
            AccountId = data.aws_caller_identity.current.account_id
        }
    }
}


##Lambda(Slack通知用)
data archive_file lambda_slack {
    type        = "zip"
    source_file = "lambda/lambda_slack.py"
    output_path = "lambda_slack.zip"
}
resource aws_lambda_function lambda_slack {
    filename      = "lambda_slack.zip"
    function_name = "compute-optimizer-to-slack"
    role          = aws_iam_role.iam_for_lambda.arn
    handler       = "lambda_slack.lambda_handler"
    source_code_hash = data.archive_file.lambda_slack.output_base64sha256
    runtime = "python3.9"
    #slack通知先
    environment {
        variables = {
            HookURL = "[SlackのWebHookURL]"
            ChannelName = "[チャンネル名]"
        }
    }
}
resource aws_lambda_permission lambda_slack_permission {
    action        = "lambda:InvokeFunction"
    function_name = aws_lambda_function.lambda_slack.function_name
    principal     = "sns.amazonaws.com"
    source_arn    = aws_sns_topic.compute_optimizer_topic.arn
}

##EventBridge Sheduler
resource aws_scheduler_schedule eventbridge {
    name = "compute-optimizer-notification-schedule"
    description = "Compute Optimizer Notification"
    flexible_time_window {
        mode = "OFF"
    }
    schedule_expression = "cron(0 0 1 * ? *)"
    schedule_expression_timezone = "Asia/Tokyo"
    target {
        arn = aws_lambda_function.lambda_computeoptimizer.arn
        role_arn = aws_iam_role.iam_for_events.arn
    }
}

##SNS(サブスクリプション)
resource aws_sns_topic_subscription slack_subscription {
    topic_arn = aws_sns_topic.compute_optimizer_topic.arn
    protocol  = "lambda"
    endpoint  = aws_lambda_function.lambda_slack.arn
}

Lambdaコード

今回の構成では、Compute Optimizerから情報取得する用とSlack通知用の2つがあります。
Compute Optimizerから取得する用のLambda(Python)で使っているSDKについては、以下をご参照ください。
ComputeOptimizer — Boto3 Docs 1.26.87 documentation

lambda_computeoptimizer.py

import boto3
import json
import os

AccountID = os.environ['AccountId']
SNSTopicArn = os.environ['SNSTopicArn']

def lambda_handler(event, context):
    # Compute Optimizerから推奨事項を取得
    # クライアント作成
    client = boto3.client('compute-optimizer')
    # リクエスト発信
    response = client.get_recommendation_summaries(
        accountIds=[AccountID],
        nextToken="",
        maxResults=10
    )

    # メッセージ生成に必要な情報の抽出
    message = []
    message.append("Compute OptimizerのEC2に関するコスト最適化の推奨事項を通知")

    # メッセージ作成
    for flag in response['recommendationSummaries']:
        if flag['recommendationResourceType'] == "Ec2Instance":
            for flag_summary in flag['summaries']:
                if flag_summary['name'] == "OVER_PROVISIONED":
                    message.append("EC2インスタンス")
                    message.append(f"ステータス: {flag_summary['name']} ,アカウントID: {flag['accountId']} 推定削減額: {flag_summary['value']}")

    # メッセージをSNSに送信
    # 通知を送信するSNSトピックのARNを指定
    sns_client = boto3.client('sns')
    sns_topic_arn = SNSTopicArn
    sns_client.publish(
        TopicArn=sns_topic_arn,
        Message=json.dumps(message, indent=3, ensure_ascii=False)
    )

Slack通知用のLambdaは割愛します。

実行結果

EventBridge Schedulerで指定したタイミングで通知がきました。
推定削減額は0ですが、正常に動作して通知できています。

最後に

今回は、Compute OptimizerのレコメンデーションをEventBridge Scheduler+Lambdaで定期通知してみたことを記事にしました。
どなたかの参考になると幸いです。