Efficient management of cloud resources is essential for maintaining a company’s flexibility and cost-effectiveness. Reserved Instances (RIs) offer significant cost-saving opportunities, but to fully leverage its benefits, consistent monitoring and optimization are key. By automating Slack notifications for RI coverage, businesses can ensure teams stay informed and take timely action to maximize savings. This comprehensive article explores the advantages of integrating automated Slack notifications for RI coverage across all AWS regions, helping teams maintain financial efficiency and operational agility. It is crucial to effectively monitor Reserved Instance (RI) coverage for several reasons: Automated notifications are vital in helping you meet these obligations and ensuring you stay on the right side of regulatory requirements. To create an advanced automated Slack notifications for RI coverage, follow these steps: Note: Please be aware that there is typically a two-day delay in cost management reporting. Therefore, the report sent by your Lambda function will reflect data from the last two days. Automating Slack notifications for Reserved Instance (RI) coverage across all AWS regions is more than just a convenience; it’s a strategic approach to resource management that can lead to significant cost savings and operational efficiencies. Following the steps outlined in this article, you can create a comprehensive monitoring system that empowers your team to make informed decisions. As cloud environments become increasingly complex, staying ahead of your resources is essential. Embracing automation enhances your visibility into RI utilization and fosters a culture of proactive management. With the proper setup and ongoing optimization, your organization can navigate the intricacies of AWS effectively, ensuring that you maximize both performance and cost efficiency.
Importance of Monitoring RI Coverage
Setting up the RI Coverage Notification
import boto3
import datetime
import json
import os
import urllib3
from calendar import month_name
# Set AWS Cost Explorer as client
ce = boto3.client('ce')
# Set list of service and regions
services = ['Amazon Elastic Compute Cloud - Compute', 'Amazon Relational Database Service']
daterange = ['DAILY', 'MONTHLY']
# Slack webhook URL
slack_webhook_url = os.environ['SLACK_WEBHOOK_URL']
# Calculate dates
today = datetime.date.today() # to simulate aspecific date of a month choose a month(1-12), a day. Use this format for, today = datetime.date.today().replace(month=3, day=1)
yesterday = today - datetime.timedelta(days=1)
start_date_daily = yesterday.strftime('%Y-%m-%d')
end_date_daily = today.strftime('%Y-%m-%d')
def lambda_handler(event, context):
print(json.dumps(event)) # for testing purposes to see the event that triggers the lambda in json
for service in services:
for range in daterange:
if range == 'DAILY':
get_ri_coverage_daily(ce, start_date_daily, end_date_daily, service, range)
elif range == 'MONTHLY':
# Adjust start date calculation
if today.day == 1: # If today is the first day of the month
start_date_monthly = yesterday.replace(day=1).strftime('%Y-%m-%d') #the start day is prev month's first day
end_date_monthly = yesterday.strftime('%Y-%m-%d') # yesterday which means the last date of the prev month
else:
start_date_monthly = today.replace(day=1).strftime('%Y-%m-%d') # Start of current month
end_date_monthly = today.strftime('%Y-%m-%d') # End date
get_ri_coverage_monthly(ce, start_date_monthly, end_date_monthly, service, range)
def get_ri_coverage_daily(ce, start, end, service, range):
try:
filter = {
'Dimensions': {
'Key': 'SERVICE',
'Values': [service]
}
}
response = ce.get_reservation_coverage(
TimePeriod={'Start': start, 'End': end},
GroupBy=[
{'Type': 'DIMENSION', 'Key': 'INSTANCE_TYPE'},
{'Type': 'DIMENSION', 'Key': 'REGION'}
],
Filter=filter
)
print(f"{service} {range}")
print(json.dumps(response))
message = format_daily_message(service, range, start, end, response)
# Send Slack notification
send_slack_notification(message)
except Exception as e:
print(f"Error retrieving Reservation Coverage for {service} - DAILY: {str(e)}")
def get_ri_coverage_monthly(ce, start, end, service, range):
try:
filter = {
'Dimensions': {
'Key': 'SERVICE',
'Values': [service]
}
}
response = ce.get_reservation_coverage(
TimePeriod={'Start': start, 'End': end},
GroupBy=[
{'Type': 'DIMENSION', 'Key': 'REGION'}
],
Filter=filter
)
print(f"{service} {range}")
print(json.dumps(response))
# Convert start and end dates to datetime objects
start = datetime.datetime.strptime(start, '%Y-%m-%d')
end= datetime.datetime.strptime(end, '%Y-%m-%d')
message = format_monthly_message(service, range, start, end, response)
# Send Slack notification
send_slack_notification(message)
except Exception as e:
print(f"Error retrieving Reservation Coverage for {service} - MONTHLY: {str(e)}")
def format_daily_message(service, range, start, end, response):
message = f"*{':large_blue_square:' if 'Amazon Relational Database Service' in service else ':large_orange_square:'}"
message += f"{'Amazon RDS' if 'Amazon Relational Database Service' in service else 'Amazon EC2'}"
message += f" RI Coverage Report {range.upper()}- {start}*\n\n"
message += f"```" #this ecapsulates the message in a code snippet
for coverage in response.get('CoveragesByTime', []):
for group in coverage.get('Groups', []):
attributes = group.get('Attributes', {})
instance_type = attributes.get('instanceType')
region = attributes.get('region', 'Unknown Region')
coverage_hours = group.get('Coverage', {}).get('CoverageHours', {})
on_demand_hours = round(float(coverage_hours.get('OnDemandHours')), 2)
reserved_hours = round(float(coverage_hours.get('ReservedHours')), 2)
total_running_hours = round(float(coverage_hours.get('TotalRunningHours')), 2)
coverage_hours_percentage = round(float(coverage_hours.get('CoverageHoursPercentage')), 2)
message += f"\n• {instance_type} in {region}\n"
message += f" - On-Demand Hours: {on_demand_hours}\n"
message += f" - Reserved Hours: {reserved_hours}\n"
message += f" - Total Running Hours: {total_running_hours}\n"
message += f" - Coverage Hours Percentage: {coverage_hours_percentage}%\n"
# Extract total coverage information
total_coverage = coverage.get('Total', {}).get('CoverageHours', {})
total_on_demand_hours = round(float(total_coverage.get('OnDemandHours', 0)), 2)
total_reserved_hours = round(float(total_coverage.get('ReservedHours', 0)), 2)
total_running_hours = round(float(total_coverage.get('TotalRunningHours', 0)), 2)
total_coverage_percentage = round(float(total_coverage.get('CoverageHoursPercentage', 0)), 2)
# Include total coverage in the message
message += "\n*Total Coverage*\n"
message += f" - Total On-Demand Hours: {total_on_demand_hours}\n"
message += f" - Total Reserved Hours: {total_reserved_hours}\n"
message += f" - Total Running Hours: {total_running_hours}\n"
message += f" - Total Coverage Hours Percentage: {total_coverage_percentage}%\n"
message += f"```" #this ecapsulates the message in a code snippet
return message
def format_monthly_message(service, range, start, end, response):
message = f"*{':large_blue_square:' if 'Amazon Relational Database Service' in service else ':large_orange_square:'}"
message += f"{'Amazon RDS' if 'Amazon Relational Database Service' in service else 'Amazon EC2'}"
message += f" RI Coverage Report {range.upper()}- {start.strftime('%B')}*\n\n"
progress_bar_length = 60 # Length of the progress bar, adjust as needed
for coverage in response.get('CoveragesByTime', []):
for group in coverage.get('Groups', []):
region = group.get('Attributes', {}).get('region')
coverage_hours_percentage = round(float(group.get('Coverage', {}).get('CoverageHours', {}).get('CoverageHoursPercentage', 0)),2)
progress_bar_fill = int(coverage_hours_percentage / 100 * progress_bar_length)
progress_bar = '|' * progress_bar_fill + ' ' * max(0, ( progress_bar_length - progress_bar_fill))
message += f"Region: *{region}*\n"
message += f"Actual Coverage: *|{progress_bar}|* ({coverage_hours_percentage}%)\n\n"
return message
def send_slack_notification(message):
http = urllib3.PoolManager()
data = {'text': message}
encoded_data = json.dumps(data).encode('utf-8')
try:
response = http.request('POST', slack_webhook_url, body=encoded_data, headers={'Content-Type': 'application/json'})
print(response.status)
except Exception as e:
print(f"Error sending Slack notification: {e}")
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ce:GetReservationCoverage",
"Resource": "*"
}
]
}
Conclusion
Automated Slack Notifications for RI Coverage Across All AWS Regions
AWS, Azure, and GCP Certifications are consistently among the top-paying IT certifications in the world, considering that most companies have now shifted to the cloud. Earn over $150,000 per year with an AWS, Azure, or GCP certification!
Follow us on LinkedIn, YouTube, Facebook, or join our Slack study group. More importantly, answer as many practice exams as you can to help increase your chances of passing your certification exams on your first try!
View Our AWS, Azure, and GCP Exam Reviewers Check out our FREE coursesOur Community
~98%
passing rate
Around 95-98% of our students pass the AWS Certification exams after training with our courses.
200k+
students
Over 200k enrollees choose Tutorials Dojo in preparing for their AWS Certification exams.
~4.8
ratings
Our courses are highly rated by our enrollees from all over the world.