Nel cloud computing, gestire i costi conta quanto gli aspetti tecnici legati al deployment e alla manutenzione dell'infrastruttura. AWS, tra i principali cloud provider, offre servizi potenti come Amazon RDS per la gestione dei database relazionali. Senza un monitoraggio e un'ottimizzazione adeguati, però, questi servizi possono tradursi rapidamente in bollette inattese. In questo articolo condivido uno script che ho scritto per le revisioni di ottimizzazione dei costi del servizio RDS: uno strumento utile per ottenere visibilità sulle risorse RDS inutilizzate e ridurre i costi in modo efficace.
Il problema delle risorse non sfruttate
Amazon RDS è un servizio di database gestito che garantisce scalabilità immediata e alta disponibilità. Per quanto sia un grande vantaggio, capita spesso che gli utenti effettuino il provisioning di istanze RDS con risorse superiori a quelle realmente necessarie. Con il tempo queste istanze si accumulano e diventano un peso superfluo sul budget cloud.
Un modo per affrontare il problema è monitorare le istanze RDS alla ricerca di quelle sottoutilizzate, tenendo d'occhio metriche come l'utilizzo della CPU e dello storage. AWS CloudWatch mette a disposizione gli strumenti per raccogliere questi dati. Analizzare manualmente le metriche di CloudWatch per ogni singola istanza RDS, però, richiede tempo e si presta agli errori: per questo ho deciso di automatizzare il processo, così da garantirne scalabilità, coerenza e riutilizzabilità in futuro.
"""Module to calculade RDS utilization"""
import string
from datetime import datetime, timezone, timedelta
import sys
import openpyxl
from openpyxl.styles import PatternFill, Font
import boto3
REGION = sys.argv[1] if len(sys.argv) > 1 else "us-east-1"
EXCEL_ROW_ITER = 2
column_headers = [\
"Instance",\
"Class",\
"Engine",\
"Engine version",\
"MultiAZ",\
"AZ",\
"Storage type",\
"Allocated Storage",\
"cpu < 25",\
"cpu 25-50",\
"cpu 50-74",\
"cpu > 75",\
"free storage < 25%",\
"free storage 25-50%",\
"free storage 50-74%",\
"free storage > 75%",\
]
red = PatternFill(patternType="solid", fgColor="FC2C03")
orange = PatternFill(patternType="solid", fgColor="E57909")
green = PatternFill(patternType="solid", fgColor="35FC03")
yellow = PatternFill(patternType="solid", fgColor="FCBA03")
wb = openpyxl.Workbook()
ws = wb["Sheet"]
def get_color(usage_type, usage_value):
"""Get color for utilization"""
if usage_type == "bl_25":
if 0 <= usage_value <= 25:
return green
if 25 <= usage_value <= 50:
return yellow
if 50 <= usage_value <= 75:
return red
if usage_value >= 75:
return red
if usage_type in ["bt_25_49", "bt_50_74"]:
if 0 <= usage_value <= 25:
return red
if 25 <= usage_value <= 50:
return orange
if 50 <= usage_value <= 75:
return yellow
if usage_value >= 75:
return green
if usage_type == "gt_75":
if 0 <= usage_value <= 25:
return yellow
if 25 <= usage_value <= 50:
return green
if 50 <= usage_value <= 75:
return orange
if usage_value >= 75:
return red
else:
return None
def fetch_metrics(db_instance_name, metricName):
"""Fetch metrics from cloudwatch"""
stats = cw.get_metric_statistics(
Namespace="AWS/RDS",
MetricName=metricName,
Dimensions=[\
{"Name": "DBInstanceIdentifier", "Value": db_instance_name},\
],
StartTime=datetime.now(timezone.utc) - timedelta(days=60),
EndTime=datetime.now(timezone.utc),
Period=3600,
Statistics=["Maximum"],
)
return stats
def add_row_to_excel(ws, row_data):
"""Write to excel"""
for cnt, data in enumerate(row_data):
ws[f'{string.ascii_uppercase[cnt]}{globals()["EXCEL_ROW_ITER"]}'] = data
if cnt == 8:
ws[\
f'{string.ascii_uppercase[cnt]}{globals()["EXCEL_ROW_ITER"]}'\
].fill = get_color("bl_25", data)
if cnt == 9:
ws[\
f'{string.ascii_uppercase[cnt]}{globals()["EXCEL_ROW_ITER"]}'\
].fill = get_color("bt_25_49", data)
if cnt == 10:
ws[\
f'{string.ascii_uppercase[cnt]}{globals()["EXCEL_ROW_ITER"]}'\
].fill = get_color("bt_50_74", data)
if cnt == 11:
ws[\
f'{string.ascii_uppercase[cnt]}{globals()["EXCEL_ROW_ITER"]}'\
].fill = get_color("gt_75", data)
globals()["EXCEL_ROW_ITER"] += 1
# Initialize Boto3 clients
rds = boto3.client("rds", region_name=REGION)
cw = boto3.client("cloudwatch", region_name=REGION)
# Define column headers
for cnt, header in enumerate(column_headers):
cell_address = f"{string.ascii_uppercase[cnt]}1"
ws[cell_address] = header
ws[cell_address].font = Font(bold=True)
# Fetch RDS instances
response = rds.describe_db_instances()
print(f'Found {len(response["DBInstances"])} databases')
for instance_data in response["DBInstances"]:
db_instance_name = instance_data["DBInstanceIdentifier"]
db_type = instance_data["DBInstanceClass"]
db_storage = instance_data["AllocatedStorage"]
db_engine = instance_data["Engine"]
db_engine_version = instance_data["EngineVersion"]
db_multiaz = instance_data["MultiAZ"]
db_az = instance_data["AvailabilityZone"]
db_storage_type = instance_data["StorageType"]
if db_engine == "docdb":
continue # Skip docdb instances
print(f"Pulling information for {db_instance_name}")
cpu_metrics = fetch_metrics(db_instance_name, "CPUUtilization")
# Calculate usage percentages
usage_values = [d["Maximum"] for d in cpu_metrics["Datapoints"]]
cpu_usage_percentages = [\
round(\
sum(1 for value in usage_values if 25 * i <= value < 25 * (i + 1))\
/ len(usage_values)\
* 100,\
1,\
)\
for i in range(4)\
]
for i, usage in enumerate(cpu_usage_percentages):
print(f"\tCPU {25 * i}% <= value < {25 * (i + 1)}%: {usage}%")
storage_usage_percentages = []
if db_storage_type != "aurora":
print("\tStorage found")
storage_metrics = fetch_metrics(db_instance_name, "FreeStorageSpace")
storage_usage_values = [d["Maximum"] for d in storage_metrics["Datapoints"]]
storage_usage_percentages = [\
round(\
sum(\
1\
for value in storage_usage_values\
if 25 * i\
<= value / (1024 * 1024 * 1024) / db_storage * 100\
< 25 * (i + 1)\
)\
/ len(storage_usage_values)\
* 100,\
1,\
)\
for i in range(4)\
]
for i, usage in enumerate(storage_usage_percentages):
print(f"\tStorage {25 * i}% <= value < {25 * (i + 1)}%: {usage}%")
# Add data to Excel worksheet
row_data = (
[\
db_instance_name,\
db_type,\
db_engine,\
db_engine_version,\
db_multiaz,\
db_az,\
db_storage_type,\
db_storage,\
]
+ cpu_usage_percentages
+ storage_usage_percentages
)
add_row_to_excel(ws, row_data)
# Save the Excel workbook
wb.save("results.xlsx")
Automatizzare la riduzione dei costi con Python
Per semplificare l'individuazione delle istanze RDS sottoutilizzate possiamo affidarci a Python, un linguaggio versatile e ampiamente diffuso.
1. Recupero dei dati delle istanze RDS
Lo script si collega ad AWS e recupera i dati relativi alle istanze RDS, raccogliendo informazioni chiave come nome, tipo, storage e altro ancora.
2. Raccolta dei dati delle metriche
Lo script interroga poi AWS CloudWatch per ottenere le metriche di utilizzo di CPU e storage. Sono dati che dicono molto sull'effettivo sfruttamento delle istanze RDS.
3. Calcolo delle percentuali di utilizzo
Sulla base delle metriche raccolte, lo script calcola le percentuali di utilizzo sia della CPU sia dello storage, suddividendole in livelli (basso, moderato, alto) in base a soglie predefinite.
4. Visualizzazione dei dati
Lo script non si ferma alla raccolta: produce anche un foglio Excel con tutte le informazioni, in cui le celle sono colorate in base al livello di utilizzo, così da individuare a colpo d'occhio le risorse sottoutilizzate.
Lo script in pratica
Per sfruttare al meglio questo script di risparmio, segua questi passaggi:
1. Prerequisiti
- Verifichi di avere Python installato sul proprio sistema.
- Installi le librerie Python necessarie:
boto3eopenpyxl.
pip3 install boto3 openpyxl
2. Configurazione AWS
- Si assicuri che le credenziali AWS siano configurate correttamente sulla sua macchina. Esegua il comando get-caller-identity per verificare di trovarsi sull'account giusto e con i permessi adeguati.
aws sts get-caller-identity
{
"UserId": "AIDAV7DHVCA7557LUGTRA",
"Account": "410386763839",
"Arn": "arn:aws:iam::410386763839:user/bogdan"
}
3. Esecuzione dello script
- Esegua lo script Python passando come argomento la regione AWS desiderata (oppure utilizzi quella predefinita 'us-east-1').
python3 run.py us-east-2
4. Analisi del report Excel
- Lo script genera un report Excel chiamato "results.xlsx" che mette in evidenza le istanze RDS sottoutilizzate. Lo esamini per individuare le opportunità di risparmio.
5. Passare all'azione
- Sulla base del report, valuti se ridimensionare o terminare le istanze RDS sottoutilizzate per abbattere i costi.

Come leggere i risultati
Lo script recupera la metrica CPUUtilization massima degli ultimi 60 giorni con periodi di 1 ora e la suddivide in 4 fasce di utilizzo: 0–25, 25–50, 50–75 e 75–100.
Calcola poi la percentuale delle ore totali in ciascuna fascia rispetto alle ore complessive di funzionamento dell'istanza.
Nei risultati di esempio sopra si nota che gran parte dei database resta per la maggior parte del tempo sotto il 25% di carico CPU: sono i primi candidati al ridimensionamento, al consolidamento o alla terminazione.
Per lo storage, lo script estrae la metrica FreeStorageSpace degli ultimi 60 giorni in finestre da 1 ora, ne calcola la percentuale rispetto allo storage totale provisionato e la inserisce in 4 fasce corrispondenti.
Possiamo notare che alcuni database utilizzano storage provisionato (gp2/gp3) e che, per certi di essi, nel 100% delle ore campionate è disponibile almeno il 75% di storage libero. È un chiaro segnale che la dimensione dello storage di queste istanze può essere ridotta.
Tenere sotto controllo i costi AWS è un tassello essenziale della gestione dell'infrastruttura cloud. Questo script Python semplifica l'acquisizione di visibilità sulle risorse RDS sottoutilizzate e aiuta a individuare con maggiore facilità le opportunità di risparmio. Automatizzando l'analisi delle metriche di CloudWatch e visualizzandone i risultati in un report Excel, può prendere decisioni basate sui dati per ottimizzare la spesa AWS.
Pur non potendosi paragonare alle soluzioni di monitoraggio commerciali pronte all'uso, offre la flessibilità di analizzare i dati come preferisce e di presentarli nel modo che ritiene più adatto. Nel prossimo articolo della serie aggiungerò ulteriori funzionalità legate ai costi nel report: resti sintonizzato.