You can set billing alerts on CloudWatch, but as a super-worried person, I would like to check the billing amount from the beginning of the month to the day before by email every day. There were many ways to notify Slack channels using AWS Lambda and webhooks, but there weren't many ways to notify by email, so I've summarized them.
In particular, the terms that appear in Amazon SNS and their relationships were complicated, so I will summarize them very roughly.
** ARN **: A name that uniquely identifies your AWS resource.
** Topic **: Ability to group multiple endpoints (here email addresses).
** Endpoint **: Delivery destination. This time it will be your email address.
** Subscription **: Associate topics with endpoints
For a deeper understanding, the terminology section of the following article was very helpful, so it is recommended that you read it carefully in advance.
Basic knowledge for sending push notifications on Amazon SNS | UNITRUST
If you haven't enabled Cost Explorer, enable it from My Billing Dashboard (https://console.aws.amazon.com/billing/home#/costexplorer).
Go to the Amazon SNS service screen.
From the "Topic" menu, press "Create Topic".
Enter "Name" and "Display name" and press "Create topic".
Create a subscription (+ endpoint).
Click "Create Subscription".
Enter the following items and press "Create Subscription".
item name | Input value / selection value |
---|---|
Topic ARN | ARN of the topic you wrote down |
protocol | |
end point | Received email address |
A confirmation email will be sent to the email address specified for the endpoint with the subject "AWS Notification --Subscription Confirmation", so press "Confirm subscription".
The status of the subscription associated with the topic will be "Confirmed".
You have created a topic subscription endpoint.
When you issue a message to the ARN of a topic, the message will be delivered to the endpoint (email address) associated with this topic. So next, create a Lambda function that will generate a message to issue to the topic's ARN. First, press "Create Function" from the AWS Lambda dashboard.
Confirm that the option is "Create from scratch", enter the following in "Basic information", and press "Create function".
item name | Input value / selection value |
---|---|
Function name | sendCost (with any name you like) |
runtime | Python 3.7 |
Role name | SNSServiceRoleForLambda (with any name you like) |
Policy template | Amazon SNS Issuance Policy |
Next, we will write the code to acquire the billing information, but to confirm the settings so far, first we will write the process to issue a test message. Enter the following code in the "Function Code" field at the bottom after creating the function.
TopicArn
, set ** ARN ** that you wrote down when creating the SNS topic.lambda_function.py
import boto3
def lambda_handler(event, context):
sns = boto3.client('sns')
subject = 'The subject of the test email from Lambda.'
message = 'This is the body of the test email from Lambda.'
response = sns.publish(
TopicArn = 'arn:aws:sns:*:*:*',
Subject = subject,
Message = message
)
return response
And, in reality, it will be executed periodically with a trigger, but I will try to send it manually.
After pressing "Save" at the top right of the screen, press "Test", enter an appropriate name in "Event Name", and press "Create". Others can be left at their default values.
When you return to the original screen and click "Test" on the upper right again, the function will be executed and the email should have arrived at the specified incoming email address.
If you do not receive it, check whether an error message is displayed on the console (Execution results) at the bottom of the code entry field, and whether the entered ARN is correct.
Finally, I will write the code to get the billing amount from Cost Explorer and notify it on Amazon SNS.
For TopicArn
, set the ARN that you wrote down when creating the SNS topic as before.
** * As will be described later, an error will occur even if you test without making additional settings! ** **
lambda_function.py
import boto3
from datetime import datetime, timedelta, date
def lambda_handler(event, context):
ce = boto3.client('ce')
sns = boto3.client('sns')
#Get the total bill for this month
total_billing = get_total_billing(ce)
#Get the total billing amount for this month (for each service)
service_billings = get_service_billings(ce)
#Generate a message to publish to an Amazon SNS topic
(subject, message) = get_message(total_billing, service_billings)
response = sns.publish(
TopicArn = 'arn:aws:sns:*:*:*',
Subject = subject,
Message = message
)
return response
def get_total_billing(ce):
(start_date, end_date) = get_total_cost_date_range()
response = ce.get_cost_and_usage(
TimePeriod={
'Start': start_date,
'End': end_date
},
Granularity='MONTHLY',
Metrics=[
'AmortizedCost'
]
)
return {
'start': response['ResultsByTime'][0]['TimePeriod']['Start'],
'end': response['ResultsByTime'][0]['TimePeriod']['End'],
'billing': response['ResultsByTime'][0]['Total']['AmortizedCost']['Amount'],
}
def get_service_billings(ce):
(start_date, end_date) = get_total_cost_date_range()
response = ce.get_cost_and_usage(
TimePeriod={
'Start': start_date,
'End': end_date
},
Granularity='MONTHLY',
Metrics=[
'AmortizedCost'
],
GroupBy=[
{
'Type': 'DIMENSION',
'Key': 'SERVICE'
}
]
)
billings = []
for item in response['ResultsByTime'][0]['Groups']:
billings.append({
'service_name': item['Keys'][0],
'billing': item['Metrics']['AmortizedCost']['Amount']
})
return billings
def get_total_cost_date_range():
start_date = date.today().replace(day=1).isoformat()
end_date = date.today().isoformat()
# get_cost_and_usage()Since the same date cannot be specified for start and end of, if today is 1st, it will be in the range from "1st of last month to 1st of this month (today)"
if start_date == end_date:
end_of_month = datetime.strptime(start_date, '%Y-%m-%d') + timedelta(days=-1)
begin_of_month = end_of_month.replace(day=1)
return begin_of_month.date().isoformat(), end_date
return start_date, end_date
def get_message(total_billing, service_billings):
start = datetime.strptime(total_billing['start'], '%Y-%m-%d').strftime('%Y/%m/%d')
#Since the End date is not included in the result, it should be the day before on the display.
end_today = datetime.strptime(total_billing['end'], '%Y-%m-%d')
end_yesterday = (end_today - timedelta(days=1)).strftime('%Y/%m/%d')
total = round(float(total_billing['billing']), 2)
subject = f'{start}~{end_yesterday}Billing amount:${total:.2f}'
message = []
message.append('[Breakdown]')
for item in service_billings:
service_name = item['service_name']
billing = round(float(item['billing']), 2)
if billing == 0.0:
#If there is no request, the breakdown will not be displayed
continue
message.append(f'・{service_name}: ${billing:.2f}')
return subject, '\n'.join(message)
I think this is complete, but ** the role assigned to Lamda does not have permission to access Cost Explorer **, so the following error occurs.
"errorMessage": "An error occurred (AccessDeniedException) when calling the GetCostAndUsage operation: User: arn:aws:sts::251745928455:assumed-role/SNSServiceRoleForLambda/sendCost is not authorized to perform: ce:GetCostAndUsage on resource: arn:aws:ce:us-east-1:251745928455:/GetCostAndUsage"
Therefore, on the IAM management screen, attach a policy that can access Cost Explorer to the role.
First, display Policy list on IAM management screen and press "Create policy".
Enter the following items and press "Confirm Policy".
item name | Input value / selection value |
---|---|
service | Cost Explorer Service |
action | Search for "Get Cost And Usage" and check it |
On the policy confirmation screen, enter "Name" and press "Create Policy".
Go to the Role List screen (https://console.aws.amazon.com/iam/home#/roles) and select the role you assigned to Lambda.
Click "Attach policy".
In "Policy Filter", enter the name you set when creating the policy and search ("AmazonCostExplorerGetCostAccess" in the example of this article), check the hits, and press "Attach Policy".
Now that the function can be executed normally, click "Test" at the top right of the setting screen of the created function.
If everything is set up correctly, you should receive an email like the one below.
Finally, set a trigger to notify you by email at a fixed time every day. Press "Add Trigger" on the left side of the function setting screen.
Set as follows and press "Add".
item name | Input value / selection value |
---|---|
Select a trigger | CloudWatch Events/EventBridge |
rule | 新規ruleの作成 |
Rule name | sendDailyCost (appropriately) |
Rule type | Schedule formula |
Schedule formula | cron(0 14 ? * * *) |
Trigger activation | To check |
This time I set it at 23:00. Note that the time is set in UTC, so set the time **, which is obtained by subtracting 9 hours from JST (Japan Standard Time) **.
After that, please make sure that you receive the email at the specified time every day.
Now you can sleep with peace of mind every day!
Recommended Posts