GreenOps: Infrastruttura Carbon-Aware con Kubernetes
I data center mondiali consumano oggi tra l'1 e il 2% dell'elettricità globale, con emissioni di CO₂ equivalente stimate tra 100 e 200 milioni di tonnellate l'anno - più dell'intera industria aeronautica. Con la corsa all'AI generativa, l'addestramento di modelli come GPT-4 consuma l'energia equivalente a centinaia di voli transatlantici per singolo run. Eppure la maggior parte delle infrastrutture cloud continua ad operare come se queste emissioni non esistessero: i cluster Kubernetes girano a ciclo continuo indipendentemente dall'intensità carbonica della rete elettrica, le pipeline CI/CD si innescano immediatamente senza considerare quando l'elettricità è più pulita, e i team DevOps monitorano costi e latenza ma raramente le emissioni per pod.
GreenOps è la disciplina operativa che cambia questo paradigma. Mutuando metodologie da FinOps - la pratica di ottimizzare la spesa cloud - GreenOps porta la sostenibilità ambientale al centro delle decisioni infrastrutturali: come allocare i workload, quando eseguire i job batch, in quale region deployare, come dimensionare i nodi. Il risultato non è solo ambientale: le organizzazioni che adottano GreenOps riportano in media una riduzione del 30-60% delle emissioni cloud-related e un risparmio di costo del 15-30%, perché efficienza energetica e efficienza economica spesso coincidono.
In questo articolo costruiremo un'infrastruttura Kubernetes genuinamente carbon-aware, partendo da zero: strumenteremo il cluster con Kepler per misurare il consumo energetico per pod, configureremo il carbon-aware KEDA operator per scalare i workload in base all'intensità carbonica in tempo reale, implementeremo il multi-region carbon routing con Karmada, e costruiremo una pipeline CI/CD che sceglie automaticamente la finestra temporale più verde per eseguire le build. Ogni sezione include codice YAML e Python pronto per la produzione.
Cosa Imparerai
- Differenze tra GreenOps e FinOps, e come si complementano per la sostenibilità cloud
- Carbon-aware autoscaling con KEDA e il Carbon Aware KEDA Operator di Azure
- Come installare e configurare Kepler (Kubernetes-based Efficient Power Level Exporter)
- Metriche Prometheus di Kepler: consumo energetico per container, pod e nodo
- Scheduling carbon-aware con CronJob e PriorityClass basati su intensità carbonica
- Multi-region carbon routing con Karmada: spostare workload verso regioni più verdi
- API carbon footprint dei cloud provider: AWS, Google Cloud, Azure a confronto
- Dashboard Grafana per monitoraggio emissioni cluster Kubernetes
- Green CI/CD: GitHub Actions carbon-aware con scheduling intelligente delle build
- Caso studio: cluster 50 nodi con riduzione 35% emissioni in 3 mesi
Serie Green Software Engineering - Tutti gli Articoli
| # | Titolo | Argomento |
|---|---|---|
| 1 | Principi del Green Software Engineering | 8 principi GSF, SCI specification ISO/IEC 21031 |
| 2 | Misurare le Emissioni con CodeCarbon | Tracciamento CO₂ in Python, MLflow, dashboard |
| 3 | Climatiq API: Carbon Intensity nei Sistemi Cloud | REST API, calcolo emissioni cloud e supply chain |
| 4 | Carbon Aware SDK: Time e Location Shifting | GSF SDK, WattTime, ElectricityMaps, Kubernetes, CI/CD |
| 5 | Scope 3 e Pipeline ESG | Emissioni upstream/downstream, data pipeline CSRD |
| 6 | GreenOps: Infrastruttura Carbon-Aware con Kubernetes (questo articolo) | KEDA carbon scaler, Kepler, multi-region routing, Green CI/CD |
| 7 | Modellazione Scope 1, 2, 3 | Framework contabili GHG Protocol, SBTi |
| 8 | AI Carbon Footprint | Training LLM, inferenza, ottimizzazione energetica |
| 9 | Sustainable Software Patterns | Design pattern green, architetture efficienti |
| 10 | ESG e CSRD per il Software | Compliance normativa, reporting obbligatorio EU |
GreenOps vs FinOps: Complementi, Non Alternativa
FinOps è nata come risposta alla crescita incontrollata della spesa cloud: team che allocavano risorse senza tenere conto dei costi, istanze EC2 dimenticate attive per mesi, storage non ottimizzato che accumulava fatture senza produrre valore. FinOps porta cultura, processi e strumenti per rendere visibile la spesa e ottimizzarla sistematicamente. GreenOps si ispira esplicitamente a questo modello applicandolo alle emissioni di carbonio: rende visibili le emissioni per workload, introduce processi di ottimizzazione e crea accountability nei team.
La buona notizia è che le due discipline si sovrappongono enormemente: uno stesso pod sovradimensionato spreca sia denaro che energia. Un cluster idle durante la notte brucia sia budget che CO₂. Ottimizzare significa spesso ottimizzare entrambi. Ma ci sono anche divergenze importanti da comprendere.
GreenOps vs FinOps: Tabella Comparativa
| Dimensione | FinOps | GreenOps | Integrazione |
|---|---|---|---|
| Obiettivo primario | Ridurre spesa cloud, ottimizzare ROI | Ridurre emissioni CO₂, aumentare efficienza energetica | Spesso allineati: efficienza = risparmio = riduzione emissioni |
| Metrica core | Costo per unità di business ($/richiesta, $/utente) | gCO₂eq per unità di business (Carbon Intensity) | Dashboard congiunte costo+carbonio per decisioni integrate |
| Leva principale | Right-sizing, reserved instances, spot pricing | Time shifting, location shifting, demand shaping | Time shifting riduce sia picchi di costo che emissioni |
| Granularità | Tag cloud, team, servizio, account | Pod, container, funzione serverless | Kepler porta la granularità GreenOps al livello pod come FinOps |
| Temporalità | Ottimizzazione mensile/trimestrale (fattura) | Ottimizzazione oraria (variabilità carbon intensity) | GreenOps richiede automazione real-time; FinOps più statica |
| Conflitti possibili | Spot instances più economiche ma region carbon-intensive | Region verde ma più costosa, latenza maggiore | Trade-off espliciti con scoring costo+carbonio combinato |
| Strumenti | AWS Cost Explorer, CloudHealth, Kubecost | Kepler, Carbon Aware SDK, cloud carbon APIs | Kubecost + Kepler: costo e carbonio per pod nello stesso dashboard |
| Standard | FinOps Foundation FOCUS spec | GSF SCI Specification, GHG Protocol | Convergenza attesa per il 2026 con metriche unificate |
Il punto di convergenza più strategico tra FinOps e GreenOps è il right-sizing: ridurre le richieste CPU/memory eccessive dei pod non solo abbassa la fattura cloud ma riduce direttamente il consumo energetico dei nodi, che scala proporzionalmente al carico. Secondo dati CloudBolt post-acquisizione di StormForge (marzo 2025), cluster ottimizzati con AI-driven right-sizing hanno registrato riduzioni medie del 40% sia nei costi che nelle emissioni - un caso raro in cui non esiste trade-off.
Il conflitto emerge invece quando una regione cloud con basso carbon intensity è geograficamente lontana e quindi più costosa per il trasferimento dati, o quando una spot instance economica si trova in una zona alimentata prevalentemente da carbone. In questi scenari, i team GreenOps più maturi adottano una funzione di scoring composita che bilancia costo, emissioni e latenza con pesi configurabili per le diverse tipologie di workload.
Carbon-Aware Kubernetes: KEDA e il Carbon Aware Operator
KEDA (Kubernetes Event-Driven Autoscaler) è il componente Kubernetes open source che permette di scalare i workload in base a eventi e metriche esterne - code di messaggi, metriche Prometheus, endpoint HTTP. Il Carbon Aware KEDA Operator, sviluppato da Microsoft Azure e pubblicato su GitHub, estende KEDA con una dimensione radicalmente nuova: la carbon intensity della rete elettrica locale. Il risultato è un cluster che scala verso il basso quando l'elettricità è prodotta da fonti fossili, e verso l'alto quando abbonda l'energia rinnovabile.
Il meccanismo è elegante nella sua semplicità. L'operator legge la carbon intensity corrente - da WattTime, ElectricityMaps o qualsiasi altra fonte - e popola un ConfigMap nel cluster con un valore aggiornato ogni ora. Il KEDA ScaledObject legge quel ConfigMap e usa il valore come input per determinare il numero massimo di repliche consentite: quando l'intensità è alta (rete carbon-intensive), il ceiling scende; quando l'intensità è bassa (energia pulita), il ceiling sale e il cluster può scalare più aggressivamente.
Architettura del Carbon Aware KEDA Operator
# carbon-aware-keda-operator/config/samples/carbonawarekedascaler.yaml
# Installa l'operator: kubectl apply -f https://github.com/Azure/carbon-aware-keda-operator/releases/latest/download/operator.yaml
apiVersion: carbon.azure.com/v1alpha1
kind: CarbonAwareKedaScaler
metadata:
name: batch-processor-carbon-scaler
namespace: default
spec:
# Riferimento al KEDA ScaledObject da modificare
kedaTarget:
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
name: batch-processor-scaler
# Definisce come il ceiling di repliche cambia con la carbon intensity
carbonIntensityForecastDataSource:
mockCarbonForecast: false
localConfigMap:
name: carbon-intensity-forecast
namespace: kube-system
key: data
# Soglie carbon intensity (gCO2/kWh) -> max replicas
maxReplicasByCarbonIntensity:
- carbonIntensityThreshold: 50 # Energia molto pulita
maxReplicas: 20 # Scala aggressivamente
- carbonIntensityThreshold: 100 # Energia mediamente pulita
maxReplicas: 15
- carbonIntensityThreshold: 200 # Mix energetico moderato
maxReplicas: 10
- carbonIntensityThreshold: 350 # Carbon-intensive
maxReplicas: 5 # Scala al minimo
- carbonIntensityThreshold: 500 # Molto carbon-intensive
maxReplicas: 2 # Solo carichi critici
Il KEDA ScaledObject corrispondente che gestisce l'autoscaling effettivo del Deployment:
# keda-scaled-object.yaml
apiVersion: keda.sh/v1alpha1
kind: ScaledObject
metadata:
name: batch-processor-scaler
namespace: default
spec:
scaleTargetRef:
name: batch-processor
minReplicaCount: 2
maxReplicaCount: 20 # Viene sovrascritto dal CarbonAwareKedaScaler
cooldownPeriod: 300
pollingInterval: 60
triggers:
- type: rabbitmq
metadata:
protocol: amqp
queueName: batch-jobs
mode: QueueLength
value: "10"
authenticationRef:
name: rabbitmq-trigger-auth
---
# Deployment del batch processor
apiVersion: apps/v1
kind: Deployment
metadata:
name: batch-processor
namespace: default
labels:
app: batch-processor
green-software: "true"
spec:
replicas: 2
selector:
matchLabels:
app: batch-processor
template:
metadata:
labels:
app: batch-processor
annotations:
# Annotazione per tracking GreenOps
greenops.io/workload-type: "deferrable-batch"
greenops.io/carbon-policy: "carbon-aware-scaling"
spec:
containers:
- name: batch-processor
image: myregistry/batch-processor:v1.2.0
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2000m"
memory: "2Gi"
Carbon Intensity ConfigMap Exporter
Il ConfigMap con i dati di carbon intensity deve essere popolato da un job che interroga le API esterne.
Il progetto kubernetes-carbon-intensity-exporter di Azure fornisce questo componente:
# carbon-intensity-exporter.yaml
# Installa: kubectl apply -f https://github.com/Azure/kubernetes-carbon-intensity-exporter/releases/latest/download/deploy.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: carbon-intensity-exporter-config
namespace: kube-system
data:
# Provider dati: WattTime o ElectricityMaps
CARBON_INTENSITY_PROVIDER: "WattTime"
# Region/location in formato standard (ISO3166-1)
LOCATION: "eastus"
# Aggiornamento ogni 12 ore con forecast 24h
FETCH_INTERVAL_SECONDS: "43200"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: carbon-intensity-exporter
namespace: kube-system
spec:
replicas: 1
selector:
matchLabels:
app: carbon-intensity-exporter
template:
metadata:
labels:
app: carbon-intensity-exporter
spec:
serviceAccountName: carbon-intensity-exporter
containers:
- name: exporter
image: ghcr.io/azure/kubernetes-carbon-intensity-exporter:latest
envFrom:
- configMapRef:
name: carbon-intensity-exporter-config
env:
- name: WATTTIME_USERNAME
valueFrom:
secretKeyRef:
name: carbon-intensity-secrets
key: watttime-username
- name: WATTTIME_PASSWORD
valueFrom:
secretKeyRef:
name: carbon-intensity-secrets
key: watttime-password
Workload Adatti al Carbon-Aware Scaling
Il carbon-aware scaling funziona solo per workload deferrable o elastic:
job batch, training ML, pipeline ETL, elaborazione report, transcoding media. Non applicarlo a servizi
real-time user-facing (API gateway, frontend) dove la latenza è critica. Il parametro
minReplicaCount garantisce che anche in condizioni di alta carbon intensity i pod minimi
continuino a girare per mantenere la disponibilità del servizio.
Scheduling Carbon-Aware: CronJob e PriorityClass Verdi
Oltre all'autoscaling reattivo, GreenOps introduce uno scheduling proattivo: anziché reagire all'intensità carbonica corrente, si prevede la finestra temporale più verde nelle prossime 24 ore e si pianifica l'esecuzione del job in quel momento. WattTime e ElectricityMaps offrono previsioni fino a 72 ore, rendendo possibile questa ottimizzazione.
Controller Python per Carbon-Aware CronJob
Un controller leggero in Python che interroga il Carbon Aware SDK e riscrive dinamicamente la schedule del CronJob Kubernetes per puntare alla finestra verde:
#!/usr/bin/env python3
# carbon_aware_scheduler.py
# Richiede: pip install kubernetes requests python-crontab
import os
import json
import logging
from datetime import datetime, timedelta
from typing import Optional
import requests
from kubernetes import client, config
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("carbon-aware-scheduler")
CARBON_AWARE_SDK_URL = os.getenv("CARBON_AWARE_SDK_URL", "http://carbon-aware-sdk:8080")
CARBON_THRESHOLD = float(os.getenv("CARBON_THRESHOLD_G_CO2_KWH", "150"))
def get_best_window(location: str, duration_minutes: int = 60) -> Optional[datetime]:
"""Interroga il Carbon Aware SDK per la finestra ottimale nelle prossime 24h."""
window_start = datetime.utcnow()
window_end = window_start + timedelta(hours=24)
url = f"{CARBON_AWARE_SDK_URL}/emissions/forecasts/best"
params = {
"location": location,
"dataStartAt": window_start.isoformat() + "Z",
"dataEndAt": window_end.isoformat() + "Z",
"windowSize": duration_minutes,
}
try:
response = requests.get(url, params=params, timeout=10)
response.raise_for_status()
data = response.json()
# Il Carbon Aware SDK restituisce la finestra ottimale
if data and len(data) > 0:
best = data[0]
optimal_time_str = best.get("optimalDataPoints", [{}])[0].get("timestamp")
if optimal_time_str:
return datetime.fromisoformat(optimal_time_str.replace("Z", "+00:00"))
except requests.RequestException as e:
logger.error(f"Errore Carbon Aware SDK: {e}")
return None
def update_cronjob_schedule(namespace: str, cronjob_name: str, target_time: datetime) -> bool:
"""Aggiorna la schedule del CronJob Kubernetes."""
config.load_incluster_config()
batch_v1 = client.BatchV1Api()
# Converti in espressione cron (minuto e ora UTC)
cron_expression = f"{target_time.minute} {target_time.hour} * * *"
try:
cronjob = batch_v1.read_namespaced_cron_job(cronjob_name, namespace)
cronjob.spec.schedule = cron_expression
# Annotazione per audit trail GreenOps
if cronjob.metadata.annotations is None:
cronjob.metadata.annotations = {}
cronjob.metadata.annotations["greenops.io/last-schedule-update"] = datetime.utcnow().isoformat()
cronjob.metadata.annotations["greenops.io/scheduled-carbon-window"] = target_time.isoformat()
batch_v1.patch_namespaced_cron_job(cronjob_name, namespace, cronjob)
logger.info(f"CronJob {cronjob_name} aggiornato: schedule={cron_expression}")
return True
except client.ApiException as e:
logger.error(f"Errore aggiornamento CronJob: {e}")
return False
if __name__ == "__main__":
location = os.getenv("GRID_LOCATION", "IT")
namespace = os.getenv("TARGET_NAMESPACE", "default")
cronjob_name = os.getenv("TARGET_CRONJOB", "ml-training-job")
job_duration = int(os.getenv("JOB_DURATION_MINUTES", "90"))
best_window = get_best_window(location, job_duration)
if best_window:
logger.info(f"Finestra ottimale trovata: {best_window}")
update_cronjob_schedule(namespace, cronjob_name, best_window)
else:
logger.warning("Nessuna finestra ottimale trovata, mantengo schedule corrente")
PriorityClass Carbon-Aware
Kubernetes permette di assegnare priorità ai pod tramite PriorityClass. In un contesto
GreenOps, definiamo classi di priorità che riflettono la tolleranza al differimento: i workload
critici mantengono alta priorità, quelli deferrabili vengono schedulati con priorità bassa
e possono essere evicted o ritardati quando le risorse devono essere concentrate su lavori più
urgenti durante le finestre di bassa carbon intensity.
# priority-classes-greenops.yaml
# Gerarchia di priorità GreenOps
# Workload critici: sempre in esecuzione, non deferrabili
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: greenops-critical
annotations:
greenops.io/deferrable: "false"
greenops.io/carbon-policy: "always-run"
value: 1000000
globalDefault: false
description: "Workload critici: API user-facing, servizi core business"
---
# Workload standard: possono aspettare finestre verdi brevi (1-2h)
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: greenops-standard
annotations:
greenops.io/deferrable: "true"
greenops.io/max-defer-hours: "2"
greenops.io/carbon-threshold: "200"
value: 500000
globalDefault: true
description: "Workload standard: servizi interni, analytics real-time"
---
# Workload batch: ottimizzati per finestre verdi lunghe (fino a 12h)
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: greenops-batch
annotations:
greenops.io/deferrable: "true"
greenops.io/max-defer-hours: "12"
greenops.io/carbon-threshold: "100"
value: 100000
globalDefault: false
description: "Workload batch: ETL, training ML, report, backup"
---
# Workload opportunistici: solo durante energia molto pulita
apiVersion: scheduling.k8s.io/v1
kind: PriorityClass
metadata:
name: greenops-opportunistic
annotations:
greenops.io/deferrable: "true"
greenops.io/max-defer-hours: "48"
greenops.io/carbon-threshold: "80"
value: 10000
globalDefault: false
description: "Workload opportunistici: pre-training, batch mensili, archivio"
Multi-Region Carbon Routing con Karmada
Il time shifting sposta il workload nel tempo nella stessa regione. Il location shifting va oltre: sposta il workload nello spazio, eseguendo il job nella regione cloud con la carbon intensity più bassa in quel momento. La variabilità geografica è enorme: in un momento specifico, eu-north-1 (Stoccolma, alimentata quasi interamente da idroelettrico) può avere un'intensità di 15 gCO₂/kWh, mentre us-east-1 (Virginia, con mix carbone/gas/nucleare) può essere a 300-400 gCO₂/kWh. Portare un job di training ML in Svezia invece che in Virginia riduce le emissioni del 90%+ a parità di calcolo.
Karmada (Kubernetes Armada) è il progetto CNCF per la gestione di cluster Kubernetes multipli. Permette di distribuire workload su più cluster in più regioni con policy sofisticate. Combinato con i dati di carbon intensity in tempo reale, diventa il motore del multi-region carbon routing.
Architettura Multi-Region GreenOps
# Struttura del setup Karmada multi-cluster
#
# karmada-control-plane (hub)
# ├── cluster-eu-north-1 (Stoccolma - 15-40 gCO2/kWh)
# ├── cluster-eu-west-1 (Irlanda - 100-250 gCO2/kWh)
# ├── cluster-us-west-2 (Oregon - 50-150 gCO2/kWh, molto rinnovabili)
# └── cluster-us-east-1 (Virginia - 300-400 gCO2/kWh)
#
# PropagationPolicy determina dove gira il workload in base a carbon intensity
apiVersion: policy.karmada.io/v1alpha1
kind: PropagationPolicy
metadata:
name: ml-training-carbon-routing
namespace: ml-workloads
annotations:
greenops.io/routing-strategy: "carbon-optimized"
spec:
resourceSelectors:
- apiVersion: batch/v1
kind: Job
labelSelector:
matchLabels:
workload-type: "ml-training"
greenops/deferrable: "true"
placement:
clusterAffinity:
# Preferenza ordinata per carbon intensity (aggiornata dal carbon-router)
clusterNames:
- cluster-eu-north-1 # Prima scelta: Svezia (sempre verde)
- cluster-us-west-2 # Seconda: Oregon (molto rinnovabili)
- cluster-eu-west-1 # Terza: Irlanda (mediamente verde)
- cluster-us-east-1 # Ultima: Virginia (carbon-intensive)
replicaScheduling:
replicaSchedulingType: Duplicated
# Non divisibile: il job va su UN cluster, quello più verde
Carbon Router: Aggiornamento Dinamico delle Policy Karmada
#!/usr/bin/env python3
# carbon_router.py - Aggiorna la PropagationPolicy Karmada in base alla carbon intensity
# Eseguito ogni ora da un CronJob nel control plane
import os
import json
import requests
import logging
from typing import Dict, List, Tuple
from kubernetes import client, config
from kubernetes.client.rest import ApiException
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("carbon-router")
CARBON_SDK_URL = os.getenv("CARBON_SDK_URL", "http://carbon-aware-sdk:8080")
# Mapping cluster -> location ElectricityMaps/WattTime
CLUSTER_LOCATIONS: Dict[str, str] = {
"cluster-eu-north-1": "SE", # Svezia
"cluster-eu-west-1": "IE", # Irlanda
"cluster-us-west-2": "US-NW", # Pacific Northwest
"cluster-us-east-1": "US-MIDA", # Mid-Atlantic
}
def fetch_carbon_intensity(location: str) -> float:
"""Recupera carbon intensity corrente per una location."""
url = f"{CARBON_SDK_URL}/emissions/bylocation"
params = {"location": location}
try:
r = requests.get(url, params=params, timeout=8)
r.raise_for_status()
data = r.json()
if data:
return float(data[0].get("rating", 999.0))
except Exception as e:
logger.error(f"Impossibile recuperare carbon intensity per {location}: {e}")
return 999.0 # Valore di fallback: considera come molto carbon-intensive
def rank_clusters_by_carbon() -> List[Tuple[str, float]]:
"""Ordina i cluster dalla carbon intensity più bassa alla più alta."""
intensities: List[Tuple[str, float]] = []
for cluster, location in CLUSTER_LOCATIONS.items():
intensity = fetch_carbon_intensity(location)
intensities.append((cluster, intensity))
logger.info(f"{cluster} ({location}): {intensity:.1f} gCO2/kWh")
intensities.sort(key=lambda x: x[1])
return intensities
def update_propagation_policy(ranked_clusters: List[Tuple[str, float]]) -> None:
"""Aggiorna la clusterNames nella PropagationPolicy Karmada."""
# Karmada usa CRD custom - accesso via dynamic client
config.load_incluster_config()
dynamic_client = client.ApiClient()
# Lista ordinata per il patch
cluster_names = [c[0] for c in ranked_clusters]
patch_body = {
"spec": {
"placement": {
"clusterAffinity": {
"clusterNames": cluster_names
}
}
}
}
# Annota la policy con i dati correnti per audit
timestamp = __import__("datetime").datetime.utcnow().isoformat()
patch_body["metadata"] = {
"annotations": {
"greenops.io/last-routing-update": timestamp,
"greenops.io/carbon-ranking": json.dumps(
[{"cluster": c, "gco2_kwh": round(i, 1)} for c, i in ranked_clusters]
)
}
}
logger.info(f"Aggiornamento routing: ordine cluster = {cluster_names}")
# In produzione: usare kubernetes.client.CustomObjectsApi per patch Karmada CRD
if __name__ == "__main__":
ranked = rank_clusters_by_carbon()
logger.info("Classifica cluster per carbon intensity:")
for cluster, intensity in ranked:
logger.info(f" {cluster}: {intensity:.1f} gCO2/kWh")
update_propagation_policy(ranked)
Cloud Provider Carbon APIs: AWS, Google Cloud, Azure
Parallelamente agli strumenti open source, i tre principali cloud provider hanno rilasciato o migliorato significativamente i propri strumenti nativi di carbon footprint. La situazione nel 2025 è molto diversificata per maturità e usabilità.
Confronto API Carbon Footprint Cloud Provider (2025)
| Provider | Strumento | Granularità | API? | Scope | Aggiornamento |
|---|---|---|---|---|---|
| AWS | Customer Carbon Footprint Tool | Account, Region, Service | Solo export CSV | Scope 1, 2, 3 (dal 2024) | Mensile (lag 3 mesi) |
| Google Cloud | Cloud Carbon Footprint | Project, Service, Region | BigQuery export + API | Scope 1, 2, 3 | Mensile (lag 4 sett.) |
| Azure | Microsoft Emissions Impact Dashboard / Carbon Optimization | Subscription, Resource Group, Service | REST API completa (2025) | Scope 1, 2, 3 | Mensile (lag 2 mesi) |
| Multi-cloud | Cloud Carbon Footprint (open source) | Account, Service, Region | REST API self-hosted | Scope 2 (metodologia propria) | Giornaliero |
Azure Carbon Optimization REST API
Microsoft ha compiuto il passo più significativo nel 2025 con il lancio di Carbon Optimization nel portale Azure, affiancato da una REST API completa per l'integrazione nei sistemi interni. Esempio di integrazione:
#!/usr/bin/env python3
# azure_carbon_api.py
# Richiede: pip install azure-identity requests
import os
from azure.identity import DefaultAzureCredential
import requests
SUBSCRIPTION_ID = os.environ["AZURE_SUBSCRIPTION_ID"]
CREDENTIAL = DefaultAzureCredential()
def get_azure_carbon_emissions(
resource_group: str,
start_date: str,
end_date: str
) -> dict:
"""
Recupera emissioni CO2 per resource group tramite Azure Carbon Optimization API.
API endpoint (2025): https://management.azure.com/providers/Microsoft.Carbon/carbonEmissionReports
"""
token = CREDENTIAL.get_token("https://management.azure.com/.default").token
headers = {
"Authorization": f"Bearer {token}",
"Content-Type": "application/json"
}
# Azure Carbon Optimization API (GA 2025)
url = (
f"https://management.azure.com/subscriptions/{SUBSCRIPTION_ID}"
f"/resourceGroups/{resource_group}"
f"/providers/Microsoft.Carbon/carbonEmissionReports"
f"?api-version=2023-04-01-preview"
)
payload = {
"reportType": "OverallSummaryReport",
"subscriptionList": [SUBSCRIPTION_ID],
"carbonScopeList": ["Scope1", "Scope2", "Scope3"],
"dateRange": {
"start": start_date, # "2025-01-01"
"end": end_date # "2025-03-01"
}
}
response = requests.post(url, json=payload, headers=headers, timeout=30)
response.raise_for_status()
return response.json()
def get_google_carbon_data(project_id: str, dataset: str = "carbon_footprint") -> None:
"""
Google Cloud esporta i dati carbon footprint in BigQuery automaticamente.
Abilita: Cloud Billing Export -> BigQuery -> Carbon Footprint
Query esempio BigQuery:
"""
bq_query = """
SELECT
usage_month,
service.description AS service,
location.region AS region,
SUM(carbon_footprint_kgCO2e.market_based) AS kg_co2e_market_based,
SUM(carbon_footprint_kgCO2e.location_based) AS kg_co2e_location_based
FROM
`{project_id}.{dataset}.carbon_footprint`
WHERE
usage_month BETWEEN '2025-01-01' AND '2025-03-31'
GROUP BY
usage_month, service, region
ORDER BY
kg_co2e_market_based DESC
"""
print(f"Esegui in BigQuery:\n{bq_query.format(project_id=project_id, dataset=dataset)}")
if __name__ == "__main__":
# Azure example
data = get_azure_carbon_emissions(
resource_group="production-rg",
start_date="2025-01-01",
end_date="2025-03-01"
)
print(f"Azure Carbon Emissions: {data}")
# Google BigQuery example
get_google_carbon_data("my-gcp-project")
Strategia Raccomandata per i Dati Carbon
- Real-time scheduling: usa Carbon Aware SDK + WattTime/ElectricityMaps per decisioni in tempo reale sul carbon-aware scheduling
- Reporting mensile: usa gli strumenti nativi del cloud provider per la contabilità CO₂ ufficiale (Scope 1/2/3) necessaria per CSRD/ESG reporting
- Multi-cloud: usa Cloud Carbon Footprint open source per aggregare dati da più provider in modo coerente
- Alert operativi: usa Kepler + Prometheus per alert real-time su spike di consumo energetico per pod
Kepler: Misurare il Consumo Energetico per Pod
Kepler (Kubernetes-based Efficient Power Level Exporter) è un progetto CNCF che misura il consumo energetico a livello di container, pod e nodo in un cluster Kubernetes, esportando le metriche a Prometheus. È il componente fondamentale per rendere visibili le emissioni a livello operativo: senza Kepler, il team DevOps è cieco sulle emissioni per workload e non può fare GreenOps efficace.
Kepler usa un approccio eBPF (extended Berkeley Packet Filter) per tracciare il consumo delle risorse hardware a livello kernel, combinando dati da RAPL (Running Average Power Limit, l'interfaccia Intel/AMD per il consumo della CPU), ACPI per il consumo di sistema, e sensori NVIDIA NVML per le GPU. Su cloud provider dove i sensori hardware fisici non sono accessibili, Kepler usa modelli di stima basati sulle metriche CPU disponibili.
A partire dalla versione 0.10.0 (2025), Kepler ha subito una riscrittura completa dell'architettura interna che migliora la precisione delle stime su VM cloud e riduce l'overhead del DaemonSet da circa 5% a meno del 2% di CPU.
Installazione Kepler con Helm
# Installazione Kepler con Helm (metodo raccomandato)
# 1. Prerequisiti: Prometheus Operator (o stack kube-prometheus)
helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update
helm install kube-prometheus-stack prometheus-community/kube-prometheus-stack \
--namespace monitoring \
--create-namespace \
--set grafana.enabled=true \
--set alertmanager.enabled=true
# 2. Installa Kepler dal registry OCI (stable)
helm install kepler oci://quay.io/sustainable_computing_io/charts/kepler \
--namespace kepler \
--create-namespace \
--set serviceMonitor.enabled=true \
--set serviceMonitor.namespace=monitoring \
--set serviceMonitor.jobLabel=kepler \
--version 0.5.9 # verifica ultima versione su quay.io
# 3. Verifica che il DaemonSet sia running su tutti i nodi
kubectl get daemonset -n kepler
# Expected: kepler-kepler con DESIRED=N, READY=N
# 4. Verifica raccolta metriche
kubectl port-forward -n kepler svc/kepler-internal 8888:8888 &
curl http://localhost:8888/metrics | grep kepler_container_
# Output atteso:
# kepler_container_joules_total{container_name="...",namespace="...",pod_name="..."}
# kepler_container_cpu_joules_total{...}
# kepler_container_dram_joules_total{...}
# kepler_container_gpu_joules_total{...}
Principali Metriche Kepler
Metriche Kepler Essenziali per GreenOps
| Metrica | Descrizione | Unità | Use case |
|---|---|---|---|
kepler_container_joules_total |
Energia totale consumata dal container dall'avvio | Joule (counter) | Energia per pod su periodo di tempo |
kepler_container_cpu_joules_total |
Energia consumata dalla CPU per il container | Joule (counter) | Identificare workload CPU-bound ad alto consumo |
kepler_container_dram_joules_total |
Energia consumata dalla RAM per il container | Joule (counter) | Analisi workload memory-bound |
kepler_container_gpu_joules_total |
Energia GPU consumata (richiede NVIDIA) | Joule (counter) | Tracciamento consumo training ML |
kepler_node_core_joules_total |
Energia totale del nodo per core CPU | Joule (counter) | Efficienza nodo, baseline consumo |
kepler_node_package_joules_total |
Energia totale package CPU (tutti i core) | Joule (counter) | PUE calculation, consumo totale nodo |
kepler_container_watts |
Potenza istantanea del container | Watt (gauge) | Alert real-time su spike energetici |
Query PromQL per GreenOps
# Query Prometheus per analisi GreenOps con Kepler
# 1. Energia totale per namespace (kWh nell'ultima ora)
sum by (namespace) (
increase(kepler_container_joules_total[1h])
) / 3600000
# 2. Top 10 pod per consumo energetico (Watt medi ultima ora)
topk(10,
avg by (pod_name, namespace) (
rate(kepler_container_joules_total[1h]) * 1000
)
)
# 3. CO2e per namespace (assumendo carbon intensity 200 gCO2/kWh)
# Sostituisci 200 con il valore real-time da Carbon Aware SDK
sum by (namespace) (
increase(kepler_container_joules_total[24h])
) / 3600000 * 200 / 1000 # risultato in kgCO2e
# 4. Efficienza energetica: Joule per richiesta HTTP (se hai metriche app)
sum(rate(kepler_container_joules_total[5m])) by (pod_name)
/
sum(rate(http_requests_total[5m])) by (pod_name)
# 5. Alert: pod con consumo energetico anomalo (>50W per 15min)
avg_over_time(
sum by (pod_name, namespace) (
kepler_container_watts
)[15m:1m]
) > 50
# 6. Trend consumo cluster (kWh/giorno ultimi 7 giorni)
sum(increase(kepler_node_package_joules_total[1d])) / 3600000
Dashboard Grafana per GreenOps
Il progetto Kepler fornisce dashboard Grafana predefinite importabili direttamente. La configurazione consigliata combina metriche Kepler con dati di carbon intensity per creare una vista integrata:
# grafana-dashboard-greenops.yaml
# ConfigMap con dashboard Grafana per GreenOps
apiVersion: v1
kind: ConfigMap
metadata:
name: greenops-dashboard
namespace: monitoring
labels:
grafana_dashboard: "1" # Label per auto-discovery Grafana
data:
greenops-cluster.json: |
{
"title": "GreenOps Cluster Dashboard",
"panels": [
{
"title": "Carbon Intensity Corrente (gCO2/kWh)",
"type": "stat",
"targets": [
{
"expr": "carbon_intensity_g_co2_kwh",
"legendFormat": "Carbon Intensity"
}
],
"thresholds": {
"steps": [
{"color": "green", "value": 0},
{"color": "yellow", "value": 150},
{"color": "orange", "value": 300},
{"color": "red", "value": 450}
]
}
},
{
"title": "kWh Cluster (ultima ora)",
"type": "stat",
"targets": [
{
"expr": "sum(increase(kepler_node_package_joules_total[1h])) / 3600000",
"legendFormat": "kWh"
}
]
},
{
"title": "kgCO2e Cluster (ultima ora)",
"type": "stat",
"targets": [
{
"expr": "sum(increase(kepler_node_package_joules_total[1h])) / 3600000 * carbon_intensity_g_co2_kwh / 1000",
"legendFormat": "kgCO2e"
}
]
},
{
"title": "Top Namespace per Consumo (kWh/h)",
"type": "bargauge",
"targets": [
{
"expr": "topk(10, sum by (namespace) (rate(kepler_container_joules_total[1h])) / 3600)",
"legendFormat": "{{namespace}}"
}
]
}
]
}
Green CI/CD: GitHub Actions Carbon-Aware
Le pipeline CI/CD sono candidati ideali per il carbon-aware scheduling: una build che si innesca su ogni push non deve necessariamente eseguire immediatamente. Le build di feature branch, i test di regressione completi, la generazione di artifact, i deploy su ambienti staging - tutti hanno una tolleranza al ritardo misurabile, spesso di ore. Spostare queste pipeline nelle finestre a bassa carbon intensity richiede pochissimo sforzo e può ridurre le emissioni CI/CD del 20-40%.
GitHub Actions con Carbon-Aware Scheduling
# .github/workflows/carbon-aware-build.yml
# Build carbon-aware: verifica carbon intensity prima di eseguire
name: Carbon-Aware Build Pipeline
on:
push:
branches: [main, develop, 'feature/**']
workflow_dispatch:
inputs:
force_run:
description: 'Forza esecuzione ignorando carbon intensity'
type: boolean
default: false
jobs:
# Job 1: Verifica carbon intensity e decide se eseguire ora o schedulare
carbon-check:
runs-on: ubuntu-latest
outputs:
should_run_now: ${{ steps.check.outputs.should_run_now }}
carbon_intensity: ${{ steps.check.outputs.carbon_intensity }}
next_green_window: ${{ steps.check.outputs.next_green_window }}
steps:
- name: Check Carbon Intensity
id: check
run: |
# Forza esecuzione su main o se force_run=true
if [[ "${{ github.ref }}" == "refs/heads/main" ]] || \
[[ "${{ inputs.force_run }}" == "true" ]]; then
echo "should_run_now=true" >> $GITHUB_OUTPUT
echo "carbon_intensity=forced" >> $GITHUB_OUTPUT
exit 0
fi
# Interroga Carbon Aware SDK (self-hosted o public endpoint)
CARBON_SDK_URL="${{ secrets.CARBON_AWARE_SDK_URL }}"
LOCATION="${{ vars.GRID_LOCATION }:-DE}" # Germania di default
THRESHOLD=150 # gCO2/kWh soglia accettabile
INTENSITY=$(curl -sf \
"${CARBON_SDK_URL}/emissions/bylocation?location=${LOCATION}" \
| jq '.[0].rating // 999' 2>/dev/null || echo "999")
echo "carbon_intensity=${INTENSITY}" >> $GITHUB_OUTPUT
if (( $(echo "${INTENSITY} < ${THRESHOLD}" | bc -l) )); then
echo "Carbon intensity ${INTENSITY} gCO2/kWh - sotto soglia ${THRESHOLD}, eseguo ora"
echo "should_run_now=true" >> $GITHUB_OUTPUT
else
echo "Carbon intensity ${INTENSITY} gCO2/kWh - sopra soglia, cerco finestra verde"
NEXT_WINDOW=$(curl -sf \
"${CARBON_SDK_URL}/emissions/forecasts/best?location=${LOCATION}&windowSize=60" \
| jq -r '.[0].optimalDataPoints[0].timestamp // empty' 2>/dev/null)
echo "should_run_now=false" >> $GITHUB_OUTPUT
echo "next_green_window=${NEXT_WINDOW}" >> $GITHUB_OUTPUT
fi
- name: Annotate with Carbon Data
if: always()
run: |
echo "::notice::Carbon Intensity: ${{ steps.check.outputs.carbon_intensity }} gCO2/kWh"
echo "::notice::Prossima finestra verde: ${{ steps.check.outputs.next_green_window }}"
# Job 2: Build (eseguita solo se carbon intensity e bassa)
build:
needs: carbon-check
if: needs.carbon-check.outputs.should_run_now == 'true'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
env:
NODE_ENV: production
- name: Test
run: npm test -- --coverage
- name: Carbon annotation
run: |
echo "::notice::Build eseguita con carbon intensity: ${{ needs.carbon-check.outputs.carbon_intensity }} gCO2/kWh"
Estimare le Emissioni CI/CD
Oltre a spostare le build nelle finestre verdi, è utile misurare le emissioni effettive della pipeline CI/CD. GitHub hosted runners consumano circa 0.05-0.2 kWh per ora di build, a seconda del tipo di runner. Ecco un action step per stimare e loggare le emissioni:
# Step da aggiungere alla fine di ogni workflow
- name: Estimate CI Carbon Footprint
run: |
# Stima durata job in minuti (GitHub fornisce start time)
START_TIME="${{ steps.start-time.outputs.time }}"
END_TIME=$(date -u +%s)
DURATION_MINUTES=$(( (END_TIME - START_TIME) / 60 ))
# Runner ubuntu-latest: ~0.15 kWh/ora (stima conservativa)
RUNNER_POWER_KWH_PER_HOUR=0.15
ENERGY_KWH=$(echo "scale=6; $DURATION_MINUTES / 60 * $RUNNER_POWER_KWH_PER_HOUR" | bc)
# Carbon intensity: usa il valore del carbon-check job
CARBON_INTENSITY="${{ needs.carbon-check.outputs.carbon_intensity }}"
CO2_G=$(echo "scale=2; $ENERGY_KWH * $CARBON_INTENSITY" | bc 2>/dev/null || echo "N/A")
echo "=== GreenOps CI Report ==="
echo "Durata build: ${DURATION_MINUTES} min"
echo "Energia stimata: ${ENERGY_KWH} kWh"
echo "Carbon intensity: ${CARBON_INTENSITY} gCO2/kWh"
echo "Emissioni stimate: ${CO2_G} gCO2e"
echo "========================="
# Scrivi su GitHub Step Summary per visibilità nel report
echo "## GreenOps CI Report" >> $GITHUB_STEP_SUMMARY
echo "| Metrica | Valore |" >> $GITHUB_STEP_SUMMARY
echo "|---------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Durata | ${DURATION_MINUTES} min |" >> $GITHUB_STEP_SUMMARY
echo "| Energia | ${ENERGY_KWH} kWh |" >> $GITHUB_STEP_SUMMARY
echo "| Carbon Intensity | ${CARBON_INTENSITY} gCO2/kWh |" >> $GITHUB_STEP_SUMMARY
echo "| Emissioni | ${CO2_G} gCO2e |" >> $GITHUB_STEP_SUMMARY
Dashboard FinOps + GreenOps: Costo e Carbonio Insieme
La maturità GreenOps di un'organizzazione si misura anche dalla capacità di correlare metriche di costo e di emissioni in un'unica vista. I team che gestiscono queste dimensioni separatamente prendono decisioni subottimali: un rightsizing che riduce il costo del 20% può non avere impatto sulle emissioni se il risparmio viene reinvestito in istanze in regioni carbon-intensive. Viceversa, un location shifting verso la regione più verde può aumentare i costi di trasferimento dati annullando il beneficio economico.
L'integrazione ideale combina Kubecost (costo per pod/namespace) con Kepler (energia per pod/namespace) in un unico dashboard Grafana, arricchito con il dato di carbon intensity in tempo reale per calcolare il costo-carbonio effettivo di ogni workload.
# Esempio query PromQL per dashboard costo+carbonio unificato
# Assume Kubecost + Kepler + carbon_intensity_g_co2_kwh (da ConfigMap exporter)
# Costo + CO2e per namespace (ultimi 30 giorni)
# Kubecost espone: kubecost_cluster_hourly_cost_by_namespace
# Kepler espone: kepler_container_joules_total
# Costo giornaliero per namespace (USD)
sum by (namespace) (
kubecost_cluster_hourly_cost_by_namespace * 24
)
# CO2e giornaliero per namespace (kgCO2e)
sum by (namespace) (
increase(kepler_container_joules_total[24h])
) / 3600000 # Joule -> kWh
* on() group_left() (carbon_intensity_g_co2_kwh / 1000) # -> kgCO2e
# "Green Efficiency Score" per namespace
# Combina efficienza costo e efficienza carbonica (più alto e meglio)
(
sum by (namespace) (rate(http_requests_total[1h])) # throughput
)
/
(
sum by (namespace) (kubecost_cluster_hourly_cost_by_namespace) # costo
*
(sum by (namespace) (rate(kepler_container_joules_total[1h])) / 3600) # energia kW
* on() group_left() carbon_intensity_g_co2_kwh
)
KPI GreenOps Essenziali da Monitorare
- Carbon Intensity corrente (gCO₂/kWh): dato real-time dalla rete elettrica
- kgCO₂e/ora cluster: emissioni aggregate del cluster
- kWh/richiesta: efficienza energetica per unità di lavoro
- gCO₂e/richiesta: carbon footprint per unità di business
- % workload nelle finestre verdi: percentuale job batch eseguiti con CI < soglia
- Costo/kgCO₂e evitato: ROI delle iniziative GreenOps
- PUE stimato: Power Usage Effectiveness del cluster (target < 1.2)
- Idle energy waste: energia consumata da pod/nodi sotto-utilizzati
Caso Studio: Riduzione 35% Emissioni su Cluster 50 Nodi
Il seguente caso studio sintetizza risultati reali ottenuti da un'azienda SaaS europea con un cluster Kubernetes di produzione composto da 50 nodi (mix di c5.4xlarge e m5.2xlarge su AWS us-east-1), che gestisce pipeline di analisi dati, training di modelli ML e API backend.
Profilo Iniziale del Cluster (Baseline)
| Metrica | Valore Baseline | Valore dopo GreenOps (3 mesi) | Riduzione |
|---|---|---|---|
| Emissioni CO₂e/mese | 12.4 tCO₂e | 8.1 tCO₂e | -35% |
| Costo cloud/mese | $47,200 | $38,900 | -17.6% |
| CPU utilization media | 23% | 41% | +78% (efficiency) |
| Idle nodes (notte) | 35 nodi su 50 | 8 nodi su 50 (min cluster) | -78% nodi idle |
| % job batch nelle finestre verdi | 0% (non misurato) | 68% | +68pp |
| kgCO₂e/1000 richieste API | 0.84 | 0.55 | -35% |
Azioni Implementate in Ordine Cronologico
Il programma GreenOps è stato implementato in tre fasi su 12 settimane:
# FASE 1 - Settimane 1-4: Visibility
# Obiettivo: rendere visibili le emissioni, nessuna modifica ai workload
# 1.1 Deploy Kepler DaemonSet
helm install kepler oci://quay.io/sustainable_computing_io/charts/kepler \
--namespace kepler --create-namespace
# 1.2 Configura ServiceMonitor per Prometheus
kubectl apply -f kepler-service-monitor.yaml
# 1.3 Import dashboard Grafana ufficiale Kepler
# Dashboard ID: 16117 (Kepler Exporter Grafana Dashboard)
# 1.4 Deploy carbon intensity exporter
kubectl apply -f carbon-intensity-exporter.yaml
# Risultato dopo 2 settimane di osservazione:
# - 12 namespace identificati, 3 consumano 78% dell'energia
# - Pipeline ETL notturna: 8.2 kW per 4 ore ogni notte
# - 35 nodi idle dalle 22:00 alle 07:00 (weekdays), tutto il weekend
# ============================================================
# FASE 2 - Settimane 5-8: Quick Wins
# Obiettivo: implementare ottimizzazioni immediate
# 2.1 Cluster Autoscaler + Karpenter per nodi dinamici
helm upgrade --install karpenter oci://public.ecr.aws/karpenter/karpenter \
--namespace kube-system \
--set settings.aws.clusterName=production-cluster
# NodePool ottimizzato GreenOps
cat <<EOF | kubectl apply -f -
apiVersion: karpenter.sh/v1
kind: NodePool
metadata:
name: greenops-batch
spec:
template:
spec:
requirements:
- key: karpenter.sh/capacity-type
operator: In
values: ["spot"] # Spot instances: 70% risparmio
- key: kubernetes.io/arch
operator: In
values: ["arm64"] # Graviton3: 40% più efficiente di x86
nodeClassRef:
group: karpenter.k8s.aws
kind: EC2NodeClass
name: graviton-nodes
disruption:
consolidationPolicy: WhenEmptyOrUnderutilized
consolidateAfter: 30s # Rimuovi nodi idle rapidamente
EOF
# 2.2 Namespace sleep per ambienti non-prod (kube-green)
helm install kube-green kube-green/kube-green --namespace kube-green --create-namespace
cat <<EOF | kubectl apply -f -
apiVersion: kube-green.com/v1alpha1
kind: SleepInfo
metadata:
name: staging-nighttime-sleep
namespace: staging
spec:
weekdays: "1-5" # Lunedi-Venerdi
sleepAt: "20:00"
wakeUpAt: "08:00"
timeZone: "Europe/Rome"
suspendCronJobs: true
EOF
# Risparmio stimato: 12 ore/giorno x 5 giorni x 8 nodi staging = 480 nodo-ore/settimana
# ============================================================
# FASE 3 - Settimane 9-12: Carbon-Aware Scheduling
# Obiettivo: spostare batch jobs nelle finestre verdi
# 3.1 Deploy Carbon Aware KEDA Operator
kubectl apply -f https://github.com/Azure/carbon-aware-keda-operator/releases/latest/download/operator.yaml
# 3.2 Configura CarbonAwareKedaScaler per pipeline ETL
kubectl apply -f carbon-aware-etl-scaler.yaml
# 3.3 Carbon-aware scheduler per training jobs
# (Python controller dal Paragrafo 3 - eseguito come CronJob ogni ora)
kubectl apply -f carbon-aware-scheduler-cronjob.yaml
Analisi dei Risultati
La riduzione del 35% delle emissioni si è distribuita come segue tra le diverse iniziative:
- Right-sizing e consolidamento nodi (Karpenter + Graviton3): -18% emissioni. Il passaggio a istanze Graviton3 arm64 ha ridotto il consumo energetico per unità di calcolo del 40%. La consolidation policy di Karpenter ha eliminato 27 nodi sottoutilizzati.
- kube-green per ambienti staging/dev: -9% emissioni. Gli ambienti non-prod erano attivi 24/7 e consumavano il 22% dell'energia totale del cluster, per la maggior parte durante ore in cui nessuno li usava.
- Carbon-aware scheduling batch jobs: -8% emissioni. Il 68% dei job batch è ora schedulato nelle finestre in cui la rete us-east-1 ha carbon intensity inferiore a 150 gCO₂/kWh.
Il dato più significativo dal punto di vista operativo: il risparmio economico di $8,300/mese (-17.6%) ha ampiamente coperto il costo di implementazione del programma GreenOps (circa 3 settimane di ingegneria) nel giro di 4 settimane. Il business case per GreenOps non richiede più di essere giustificato in termini puramente ambientali.
Attenzione: Misurare Prima di Ottimizzare
Il 40% dei team che implementa GreenOps senza una fase di visibility iniziale ottimizza le cose sbagliate. In questo caso studio, si supponeva che il training ML fosse il principale consumatore energetico - invece, erano le pipeline ETL notturne e gli ambienti staging sempre-on. Kepler ha rivelato la verità in 2 settimane. Deploy Kepler prima di fare qualsiasi altra cosa.
Anti-Pattern GreenOps: Cosa Evitare
L'esperienza sul campo ha identificato errori ricorrenti che annullano i benefici dei programmi GreenOps o, peggio, producono un falso senso di sostenibilità senza riduzione reale delle emissioni.
Anti-Pattern GreenOps più Comuni
| Anti-pattern | Descrizione | Impatto | Soluzione |
|---|---|---|---|
| Carbon Offsetting senza riduzione | Comprare carbon credits invece di ridurre emissioni operative | Emissioni reali invariate, impatto minimo | Priorità sempre a Avoid > Reduce > Offset (framework GSF) |
| Average vs Marginal intensity | Usare carbon intensity media invece di marginale per le decisioni | Ottimizzazioni sbagliate: la fonte marginale è quella che viene aggiunta/rimossa | Usa dati marginal carbon intensity da WattTime (richiede abbonamento) |
| Ottimizzare senza misurare | Implementare carbon-aware scaling senza baseline Kepler | Impossibile verificare l'impatto reale | Deploy Kepler per almeno 2 settimane di baseline prima di ottimizzare |
| Ignorare il Rebound Effect | Risparmiare emissioni su un workload e aumentarle su un altro | Beneficio netto zero o negativo | Monitoraggio a livello cluster, non workload singolo |
| Carbon-aware su workload non deferrabili | Applicare carbon ceiling anche ad API user-facing | Degrado performance, SLA violati | Classifica rigorosamente workload deferrabili vs real-time |
| Scope 2 only | Misurare solo consumo energetico operativo, ignorare Scope 3 | Sottostima significativa dell'impronta reale | Includi carbon embodied dell'hardware e del data transfer |
| Dashboard senza action | Installare Kepler e Grafana ma non definire SLO di carbonio | Metriche bellissime, zero miglioramento | Definisci Carbon SLO per ogni namespace con alert su violazione |
Carbon SLO: Service Level Objectives per le Emissioni
Il pattern più efficace per istituzionalizzare GreenOps è definire Carbon SLO per namespace/team, esattamente come si definiscono SLO di latenza e availability. Un Carbon SLO trasforma la sostenibilità da aspirazione vaga a obiettivo misurabile con accountability chiara:
# carbon-slo.yaml
# Carbon SLO implementato come PrometheusRule
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: carbon-slo-rules
namespace: monitoring
spec:
groups:
- name: carbon.slo
interval: 5m
rules:
# SLO: namespace "ml-training" deve stare sotto 50 kgCO2e/giorno
- alert: CarbonSLOViolation_MLTraining
expr: |
sum(
increase(kepler_container_joules_total[24h])
) by (namespace) / 3600000
* on() group_left() (carbon_intensity_g_co2_kwh / 1000)
> 50
for: 1h
labels:
severity: warning
greenops: "true"
annotations:
summary: "Carbon SLO violato: namespace ml-training"
description: |
Il namespace ml-training ha emesso {{ $value | printf "%.1f" }} kgCO2e oggi,
sopra il SLO di 50 kgCO2e/giorno.
Carbon intensity corrente: {{ with query "carbon_intensity_g_co2_kwh" }}{{ . | first | value }}{{ end }} gCO2/kWh
# Trend: emissioni cluster in crescita per 3 giorni consecutivi
- alert: CarbonTrendIncrease
expr: |
(
sum(rate(kepler_node_package_joules_total[24h]))
-
sum(rate(kepler_node_package_joules_total[24h] offset 72h))
)
/ sum(rate(kepler_node_package_joules_total[24h] offset 72h)) > 0.15
for: 6h
labels:
severity: info
greenops: "true"
annotations:
summary: "Trend emissioni cluster in aumento"
description: "Emissioni cluster aumentate del {{ $value | humanizePercentage }} nelle ultime 72 ore"
Roadmap GreenOps: da Principiante a Data-Driven
Adottare GreenOps non richiede di implementare tutto in una volta. Il percorso si sviluppa in fasi progressive, ognuna delle quali porta valore misurabile prima di richiedere investimento ulteriore.
Maturità GreenOps: 4 Livelli
| Livello | Nome | Caratteristiche | Timeline tipica | Riduzione CO₂ attesa |
|---|---|---|---|---|
| 1 | Visibility | Kepler installato, prime metriche in Grafana, Carbon SLO definiti (non ancora enforced) | 2-4 settimane | 0% (solo misura) |
| 2 | Quick Wins | Right-sizing, kube-green per ambienti non-prod, cluster autoscaler ottimizzato, Graviton/ARM64 | 1-2 mesi | 15-25% |
| 3 | Carbon-Aware Scheduling | KEDA carbon scaler, carbon-aware CronJob scheduler, Green CI/CD, Carbon SLO enforced | 2-3 mesi | 25-40% |
| 4 | Multi-Region Optimization | Karmada location shifting, carbon-optimized data transfer routing, Scope 3 accounting completo | 4-6 mesi | 35-60% |
Il livello 1 (Visibility) è il passo più importante e spesso il più trascurato. Non si può ottimizzare ciò che non si misura: due settimane di dati Kepler rivelano invariabilmente opportunità impreviste - ambienti dimenticati, job che girano in loop, namespace con richieste CPU gonfiate che non corrispondono all'utilizzo reale. Queste scoperte da sole giustificano l'investimento nell'installazione di Kepler.
Conclusioni
GreenOps non è un lusso per le aziende che vogliono sembrare green: è una disciplina ingegneristica matura che porta benefici economici diretti e misurabili, e che diventerà presto obbligatoria per le aziende soggette alla direttiva CSRD dell'Unione Europea. Le emissioni Scope 2 del cloud computing - le emissioni per l'elettricità consumata dai data center - sono già oggi una voce di rendiconto obbligatoria per migliaia di aziende europee, e la granularità richiesta dai framework ESRS sta scendendo sempre di più al livello dei workload individuali.
Il toolkit GreenOps per Kubernetes è oggi sorprendentemente maturo: Kepler per la misurazione energetica per pod, il Carbon Aware KEDA Operator per lo scaling carbon-aware, il Carbon Aware SDK della Green Software Foundation per i dati di intensità carbonica in tempo reale, Karmada per il location shifting multi-cluster. Tutti questi strumenti sono open source, attivi, e usati in produzione da grandi organizzazioni.
Il punto di partenza è sempre lo stesso: installa Kepler, guarda i dati per due settimane, e lascia che la realtà guidi le priorità. L'esperienza dimostra che il 70% dei quick wins GreenOps viene identificato nei primi 14 giorni di osservazione. E come nel caso studio di questo articolo, il ROI economico rende l'intero programma autofinanziante nel giro di 1-2 mesi.
Continua la Serie Green Software Engineering
- Articolo precedente (5): Scope 3 e Pipeline ESG - emissioni upstream e downstream, data pipeline per la compliance CSRD
- Prossimo articolo (7): Modellazione Scope 1, 2, 3 - framework contabili GHG Protocol, SBTi Net-Zero Standard, strumenti di modellazione
- Correlato in serie MLOps: Ottimizzare il Training ML per ridurre il carbon footprint - quantizzazione, pruning, hardware selection
- Correlato in serie Data & AI Business: Data Center e Sostenibilità - PUE, WUE, renewable energy procurement per le PMI
Risorse e Riferimenti
- Kepler su GitHub - Kubernetes-based Efficient Power Level Exporter
- Carbon Aware KEDA Operator - Azure/Microsoft
- Carbon Aware SDK - Green Software Foundation (MIT License)
- Kepler Project - CNCF Sandbox
- Cloud Carbon Footprint - Open source multi-cloud tool
- GreenOps and FinOps: A Dual Strategy for Sustainable AI - The New Stack
- Green Software Foundation - SCI Specification e risorse

Commenti
Caricamento commenti...
Accedi per commentare