0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

AWSの予算超過時の通知設定をIaCで手軽に実現してみる

Last updated at Posted at 2024-08-02

概要

  • AWSで予算が超過しそうな時にアラートをあげてくれるよう設定
  • CloudFormationを使って(半)自動で構築
  • 本記事では以下2パターン用意
    • ①メール通知のみ
    • ②メール通知&稼働中のEC2インスタンス停止

1. テンプレートファイルをローカルに準備

以下①か②どちらかをローカルPCに保存しておく

  • ①メール通知のみ
alarm-budget.yaml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  myAlarmCostUSD:
    Type: Number
    Default: 1 # 基準を1USDとする
    Description: Alarm Cost.USD.
  myEmail:
    Type: String
    AllowedPattern: >-
      [a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+.@[a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+\.([a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+\.)*([a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+)
Resources:
  # Budgetの設定
  myBudget:
    Type: AWS::Budgets::Budget
    Properties:
      Budget:
        BudgetLimit:
          Amount: !Ref myAlarmCostUSD
          Unit: USD
        TimeUnit: MONTHLY
        BudgetType: COST
      NotificationsWithSubscribers:
      # 予測コストの通知設定
        - Notification: 
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 0.1 # 基準の0.1%(0.001 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 50 # 基準の50%(0.5 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 100 # 基準の100%(1 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
      # 実コストの通知設定 
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 0.1 # 基準の0.1%(0.001 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 50 # 基準の50%(0.5 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 100 # 基準の100%(1 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
Outputs:
  BudgetId:
    Value: !Ref myBudget
  • ②メール通知&稼働中の全EC2インスタンスを停止(※終了ではない)
alarm-and-stopInstances-budget.yaml
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  myAlarmCostUSD:
    Type: Number
    Default: 1 # 基準を1USDとする
    Description: Alarm Cost.USD.
  myEmail:
    Type: String
    AllowedPattern: >-
      [a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+.@[a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+\.([a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+\.)*([a-zA-Z0-9]+[-._a-zA-Z0-9]*[a-zA-Z0-9]+)

# Lambda関数を呼び出すSNSの設定
Resources:
  BudgetNotificationTopic:
    Type: AWS::SNS::Topic
    Properties:
      DisplayName: "Budget Notification Topic"
      Subscription:
        - Endpoint: !GetAtt BudgetActionLambda.Arn
          Protocol: lambda

  # インスタンスを停止するLambda関数の設定
  BudgetActionLambda:
    Type: AWS::Lambda::Function
    Properties:
      Handler: index.handler
      Role: !GetAtt LambdaExecutionRole.Arn
      Runtime: python3.8
      Timeout: 10
      Code:
        ZipFile: |
          import json
          import boto3
          
          def handler(event, context):
              ec2 = boto3.client('ec2')
              
              # 実行中のインスタンス一覧を取得
              filters = [{'Name': 'instance-state-name', 'Values': ['running']}]
              
              response = ec2.describe_instances(Filters=filters)
              instance_ids = []
              
              for reservation in response['Reservations']:
                  for instance in reservation['Instances']:
                      if instance['State']['Name'] == 'running':
                          instance_ids.append(instance['InstanceId'])
                          
              # 実行中のインスタンスがあれば停止
              if instance_ids:
                  stop_response = ec2.stop_instances(InstanceIds=instance_ids)
                  
              return {
                  'statusCode': 200,
                  'body': json.dumps(f'Stopped instances: {instance_ids}')
              }
  # 必要な各種権限をLambda関数に付与
  LambdaExecutionRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: sts:AssumeRole
      Policies:
        - PolicyName: LambdaEC2Policy
          PolicyDocument:
            Version: '2012-10-17'
            Statement:
              - Effect: Allow
                Action:
                  - ec2:DescribeInstances
                  - ec2:StopInstances
                  - logs:CreateLogGroup
                  - logs:CreateLogStream
                  - logs:PutLogEvents
                Resource: "*"

  # Budgetの設定
  MyBudget:
    Type: "AWS::Budgets::Budget"
    Properties:
      Budget:
        BudgetName: "MyBudget"
        BudgetLimit:
          Amount: !Ref myAlarmCostUSD
          Unit: USD
        TimeUnit: MONTHLY
        BudgetType: COST
      NotificationsWithSubscribers:
      # 予測コストの設定
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 0.1 # 基準の0.1%(0.001 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 50 # 基準の50%(0.5 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
        - Notification:
            NotificationType: FORECASTED
            ComparisonOperator: GREATER_THAN
            Threshold: 100 # 基準の100%(1 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
      # 実コストの設定
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 0.1 # 基準の0.1%(0.001 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
            - SubscriptionType: SNS
              Address: !Ref BudgetNotificationTopic
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 50 # 基準の50%(0.5 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
            - SubscriptionType: SNS
              Address: !Ref BudgetNotificationTopic
        - Notification:
            NotificationType: ACTUAL
            ComparisonOperator: GREATER_THAN
            Threshold: 100 # 基準の100%(1 USD)
          Subscribers:
            - SubscriptionType: EMAIL
              Address: !Ref myEmail
            - SubscriptionType: SNS
              Address: !Ref BudgetNotificationTopic

Outputs:
  BudgetId:
    Value: !Ref MyBudget

①と②のどちらを使うかは各自の用途に合わせる

  • 例:
    • メール通知は欲しいがインスタンス停止までは要らない⇒①
    • 通知も欲しいし、インスタンスも自動で止めてほしい⇒②

2. CloudFormationの設定

1. AWSマネジメントコンソールにログイン
2. 左上の検索バーで「CloudFormation」と検索し、CloudFormationの画面を開く
3. 「スタックの作成」→「新しいリソースを使用(標準)」押下
howto_setup_1.png

4. スタックの作成で以下の通り設定
①既存のテンプレートを選択
②テンプレートファイルのアップロード
③「ファイルの選択」でローカルに保存したyamlファイルを選択
④「次へ」押下
howto_setup_2.png

5. スタックの詳細を指定で以下の通り設定
スタック名は自由に設定
myEmailに通知を受け取りたいメールアドレスを指定
③「次へ」押下
howto_setup_3.png

myAlarmCostUSDの値に対して何割超過したかがアラートをあげる条件になります。テンプレではデフォルト$1 USDにしていますが、1ドルじゃ小さすぎる場合はここを変えてください。

  • 例:myAlarmCostUSDを「100」にした場合
    • 1ドル、50ドル、100ドル到達タイミングでそれぞれ通知

6. スタックオプションの設定はどこも触らず「次へ」押下
howto_setup_4.png

7. 確認して作成で設定を確認し、「送信」押下
howto_setup_5.png
howto_setup_6.png

②のテンプレを使用する場合はこのタイミングでIAMリソース作成の許可を求められますのでチェックを入れてください。
howto_setup_6a.png

8. スタックが「CREATE_COMPLETE」になるまでしばらく待機
howto_setup_7.png

3. 設定内容の確認

1. 作成済みのスタックの①リソースタブを押下し②物理IDのリンクを押下
howto_setup_8.png

2. Budgetの画面に飛ぶので「アラート」タブを押下し、作成された通知設定の内容を確認
howto_setup_9.png

番外編. ②を使用した際のLambda関数の動作確認

説明を省きましたが、②のテンプレではEC2インスタンスを停止するためのLambda関数を定義しています。
その関数がちゃんと動くかテストする手順を紹介します。

  • リージョン内にEC2インスタンスが1個も存在しない場合は、実施前にテストで停止させる用のEC2インスタンスを作成しておいてください(設定はテキトーでOK)
  • リージョン内の全EC2インスタンスが停止するので、止めたくないものがある場合はリージョンを変える等で対応してください
    (※終了ではなく停止させるだけなので、インスタンスが消えることはありません。)

1. CloudFormationの「リソース」タブから作成されたLambdaの画面に飛ぶ
howto_setup_10.png

2. 「テスト」タブ押下
howto_setup_11.png

3. 「イベントJSON」に以下をコピペ
howto_setup_12.png

lambda_test.json
{
  "version": "0",
  "id": "abcdefg-hijklmn",
  "detail-type": "AWS Budgets Notification",
  "source": "aws.budgets",
  "account": "123456789012",
  "time": "2021-12-06T18:40:00Z",
  "region": "ap-northeast-1",
  "resources": [],
  "detail": {
    "budgetName": "MyBudget",
    "budgetLimit": {
      "amount": 100.0,
      "unit": "USD"
    },
    "costFilters": {},
    "costTypes": {
      "includeTax": true,
      "includeSubscription": true,
      "useBlended": false,
      "includeRefund": true,
      "includeCredit": true,
      "includeUpfront": true,
      "includeRecurring": true,
      "includeOtherSubscription": true,
      "includeSupport": true,
      "includeDiscount": true,
      "useAmortized": false
    },
    "timeUnit": "MONTHLY",
    "timePeriod": {
      "start": "2021-12-01T00:00:00Z",
      "end": "2021-12-31T23:59:59Z"
    },
    "notification": {
      "notificationType": "ACTUAL",
      "comparisonOperator": "GREATER_THAN",
      "threshold": 100.0,
      "thresholdType": "PERCENTAGE",
      "notificationState": "ALARM"
    },
    "calculatedSpend": {
      "actualSpend": {
        "amount": 120.0,
        "unit": "USD"
      },
      "forecastedSpend": null
    },
    "budgetAction": null
  }
}

4. 実際にテストしてみる

  • 1. テスト前のインスタンス
    image.png

  • 2. JSON貼り付けて「テスト」押下
    howto_setup_13.png

  • 3. ↓実行中
    image.png
    ↓成功したら緑になる
    image.png

  • 4. インスタンスも止まってました
    image.png

今後の展望

  • Slackとかにも通知を送れるようにしたい
  • EC2インスタンス停止以外にも予算超過時のアクションのバリエーションを増やしたい
  • GCPやOCIでもIaCの対応ができるようにTerraformも勉強したい
0
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?