StackSetsで予算設定・通知機能を作ってみた!
はじめに
みなさんこんにちは。最近椎茸チップスにどハマりしているhiropyです。
早速ですが、現在社内のサンドボックス環境を整備しており、その中で「アカウントごとに予算を設定して、予算を超えそうになったら通知する機能が欲しい」という要望がありました。要望を叶えるべく、CloudFormationのStackSetsを使って実装しましたのでその方法を記事にしたいと思います。
作成するリソース
今回は以下のアーキテクチャで実装していこうと思います。
CloudFormationを使用して、以下のリソースをデプロイします。
- 予算を設定するためのAWS Budgets
- BudgetsからのアラートをLambdaに送るためのAmazon SNS
- Slackに通知するためのLambda
前提
以下のリソースの準備ができていることが前提となります。
- ControlTowerのセットアップが完了していること
- デプロイ先のOUが存在していること
- 通知用チャンネルのWebhookが設定されていること
やってみる
それでは早速やっていきましょう!
まずはyamlファイルの内容に書かれているコードをコピーし、手元にyamlファイルを作成してください。
- yamlファイルの内容
-
AWSTemplateFormatVersion: '2010-09-09' Description: Stack that creates an AWS budget, notifications, and a Lambda function that will shut down EC2 instances Parameters: BudgetAmount: Type: Number Description: Maximum permissible spend for the month Email: Type: String Description: Email address to deliver notifications to WarningThreshold: Type: Number Description: Percentage of forecast monthly spend for the warning notification Default: 80 WebhookUrl: Type: String Description: Webhook URL for send message to slack SlackUser: Type: String Description: Name for mension for slack Outputs: BudgetId: Value: !Ref Budget Resources: WarningTopic: Type: AWS::SNS::Topic WarningTopicPolicy: Type: AWS::SNS::TopicPolicy Properties: PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: sns:Publish Resource: "*" Principal: Service: budgets.amazonaws.com Topics: - !Ref WarningTopic Budget: Type: AWS::Budgets::Budget Properties: Budget: BudgetLimit: Amount: !Ref BudgetAmount Unit: USD TimeUnit: MONTHLY BudgetType: COST NotificationsWithSubscribers: - Notification: NotificationType: ACTUAL ComparisonOperator: GREATER_THAN Threshold: !Ref WarningThreshold Subscribers: - SubscriptionType: EMAIL Address: !Ref Email - SubscriptionType: SNS Address: !Ref WarningTopic BudgetLambdaExecutionRole: Type: AWS::IAM::Role Properties: AssumeRolePolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Principal: Service: - lambda.amazonaws.com Action: - sts:AssumeRole Path: / Policies: - PolicyName: BudgetLambdaExecutionRolePolicy PolicyDocument: Version: 2012-10-17 Statement: - Effect: Allow Action: - logs:CreateLogGroup - logs:CreateLogStream - logs:PutLogEvents Resource: arn:aws:logs:*:*:log-group:/aws/lambda/*-BudgetLambdaFunction-*:* BudgetLambdaFunction: Type: AWS::Lambda::Function Properties: Description: Lambda function to be called after a critical budget threshold has been exceeded Handler: index.lambda_handler Role: !GetAtt BudgetLambdaExecutionRole.Arn Runtime: python3.9 Timeout: 20 Environment: Variables: WEBHOOK_URL : !Ref WebhookUrl USER : !Ref SlackUser Code: ZipFile: | import boto3 import os import json import urllib def send_slack(): user_name = os.environ['USER'] send_data = { "text": f"<@{user_name}>\nサンドボックス環境が予算を超過しそうです。\nリソースの整理をお願いします。", } send_text = json.dumps(send_data) request = urllib.request.Request( os.environ['WEBHOOK_URL'], data=send_text.encode('utf-8'), method="POST" ) with urllib.request.urlopen(request) as response: response_body = response.read().decode('utf-8') return def lambda_handler(event, context): # TODO implement try: send_slack() except Exception as e: print(e) raise e WarningTopicSubscription: Type: AWS::SNS::Subscription Properties: Protocol: lambda TopicArn: !Ref WarningTopic Endpoint: !GetAtt BudgetLambdaFunction.Arn BudgetLambdaFunctionPermission: Type: AWS::Lambda::Permission Properties: Action: lambda:InvokeFunction FunctionName: !Ref BudgetLambdaFunction Principal: sns.amazonaws.com SourceArn: !Ref WarningTopic
yamlファイルの作成が完了しましたら、ControlTowerの管理者アカウントでCloudFormationコンソールを開き、StackSetsタブを選択してから「StackSetsを作成」をクリックします。
実行ロールなどはそのままにし、「テンプレートの指定」で「テンプレートファイルのアップロード」から先ほどのyamlファイルをアップロードします。
StackSet名や説明は任意のものを入力し、その下に表示される「パラメータの入力」に進みます。
ここがポイントです。
ここに表示される値は、yamlファイルのParameters
以下の部分に当たります。
各パラメータについて説明すると、
- BudgetAmount: 各アカウントに設定する予算を入力します。単位はUSDです。
- Email: 予算を超過しそうになった時に通知するメールアドレスです。
- WarningThreshold: 予算のどれくらいの割合を超過した時に通知するかを設定します。値は%で設定します。(例:
80
)デフォルトで80が指定されています。 - WebhookUrl: Slackで通知するためのWebhookのURLを指定します。ここを変更することで通知先のチャンネルを変更することができます。
- SlackUser: メンバーにメンションするためのメンバーIDを設定します。メンバーIDは、
メンバーのプロフィールを表示 -> 3点マークをクリック -> Copy memberID
で確認できます。
チャンネル全体に通知したい場合は、yamlファイルの113行目、<@{user_name}>
を<!channel>
のように変更してください。
これらのパラメータを入力したら、次の画面に進みます。
「StackSet オプションの設定」は特に変更せず次に進みます。(必要に応じてタグを付与してください)
デプロイオプションの設定では、「アカウント」セクションでアカウント単位でデプロイするかOU単位でデプロイするかを選択できます。
リージョンは任意のものを設定、そのほかの設定は変更せず次へ進みます。
レビュー画面で内容を確認し送信を押すとスタックのデプロイが開始されます。
これでデプロイが完了すれば、アカウントまたはOU内の各アカウントにリソースが作成されます!
確認
デプロイが完了したら動作確認をしてみましょう!
例えば予算を0.01USDにして、価格の高いインスタンスを立ててみます。
すると以下のようにSlackに通知が飛ぶことが確認できるかと思います。
最後に
いかがでしたでしょうか?
CloudFormationを使えば、複数のアカウントに簡単にリソースをデプロイできるので、CT環境管理の一環として、ぜひ活用していただければ幸いです。
参考
- Control developer account costs with AWS CloudFormation and AWS Budgets
- SlackのIncoming Webhooksでメンションを飛ばす方法