概要
- AWSで予算が超過しそうな時にアラートをあげてくれるよう設定
- CloudFormationを使って(半)自動で構築
- 本記事では以下2パターン用意
- ①メール通知のみ
- ②メール通知&稼働中のEC2インスタンス停止
1. テンプレートファイルをローカルに準備
以下①か②どちらかをローカルPCに保存しておく
- ①メール通知のみ
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インスタンスを停止(※終了ではない)
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. 「スタックの作成」→「新しいリソースを使用(標準)」押下
4. スタックの作成で以下の通り設定
①既存のテンプレートを選択
②テンプレートファイルのアップロード
③「ファイルの選択」でローカルに保存したyamlファイルを選択
④「次へ」押下
5. スタックの詳細を指定で以下の通り設定
①スタック名
は自由に設定
②myEmail
に通知を受け取りたいメールアドレスを指定
③「次へ」押下
myAlarmCostUSD
の値に対して何割超過したかがアラートをあげる条件になります。テンプレではデフォルト$1 USDにしていますが、1ドルじゃ小さすぎる場合はここを変えてください。
- 例:
myAlarmCostUSD
を「100」にした場合- 1ドル、50ドル、100ドル到達タイミングでそれぞれ通知
8. スタックが「CREATE_COMPLETE」になるまでしばらく待機
3. 設定内容の確認
1. 作成済みのスタックの①リソースタブを押下し②物理IDのリンクを押下
2. Budgetの画面に飛ぶので「アラート」タブを押下し、作成された通知設定の内容を確認
番外編. ②を使用した際のLambda関数の動作確認
説明を省きましたが、②のテンプレではEC2インスタンスを停止するためのLambda関数を定義しています。
その関数がちゃんと動くかテストする手順を紹介します。
- リージョン内にEC2インスタンスが1個も存在しない場合は、実施前にテストで停止させる用のEC2インスタンスを作成しておいてください(設定はテキトーでOK)
- リージョン内の全EC2インスタンスが停止するので、止めたくないものがある場合はリージョンを変える等で対応してください
(※終了ではなく停止させるだけなので、インスタンスが消えることはありません。)
1. CloudFormationの「リソース」タブから作成されたLambdaの画面に飛ぶ
{
"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. 実際にテストしてみる
今後の展望
- Slackとかにも通知を送れるようにしたい
- EC2インスタンス停止以外にも予算超過時のアクションのバリエーションを増やしたい
- GCPやOCIでもIaCの対応ができるようにTerraformも勉強したい