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.
Importance of Monitoring RI Coverage
It is crucial to effectively monitor Reserved Instance (RI) coverage for several reasons:
- It helps prevent wasted resources. Since RIs represent a financial commitment, diligent monitoring ensures you aren’t paying for unused capacity. This proactive approach not only safeguards your budget but also enhances overall efficiency.
- Accurate tracking enables you to maximize savings by identifying when additional RIs are necessary or when existing ones should be adjusted to align with current workloads.
- Compliance with regulations often requires a certain level of resource management and reporting.
Automated notifications are vital in helping you meet these obligations and ensuring you stay on the right side of regulatory requirements.
Setting up the RI Coverage Notification
To create an advanced automated Slack notifications for RI coverage, follow these steps:
- Create a AWS Lambda Function:
- Open your web browser and go to the AWS Lambda console to get started.
- Ensure that you operate in the Ohio region (us-east-2) or any region you prefer to align with your resource requirements.
- Click the “Create function” button to initiate the function setup process.
- Select “Author from scratch” to build your function from zero, then add a “Function Name”.
- Choose “Python 3.12” as the runtime environment, which is ideal for our coding needs.
- Copy and paste the provided Python code into the code editor below. Review and customize it as necessary to suit your specific requirements.
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}")
- Set the timeout for your Lambda function to 10 seconds. This ensures your function has sufficient time to execute without timing out prematurely.
- Create an inline policy. Then, attach the required permission to your Lambda function to enable access to AWS services. Navigate to “Configuration” and then “Permissions,” and click on the execution role name.
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Action": "ce:GetReservationCoverage", "Resource": "*" } ] }
- Set up the environment variables, including variables for Instance ID and Slack channel.
- Open your web browser and go to the AWS Lambda console to get started.
- Amazon EventBridge Events Configuration:
- Navigate to the AWS Management Console and open the EventBridge console.
- In the left sidebar, choose “Rules” to view and manage your existing CloudWatch rules.
- Click the “Create rule” button to initiate the rule configuration process.
- Specify the schedule for triggering your rule.
- In the “Targets” section, select your Lambda function as the target for this rule. This establishes the connection between the event schedule and your function.
- After reviewing your settings, click the “Save” button to create the rule.
- Finally, test the setup to ensure the rule triggers your Lambda function as expected. You can monitor the function’s logs in the Lambda console to confirm it executes correctly.
- Navigate to the AWS Management Console and open the EventBridge console.
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.
Conclusion
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.
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 courses