Zestaw SDK Carbon Aware: przenoszenie obciążeń w czasie i przestrzeni
Sektor ICT zużywa ok 460-700 TWh energii elektrycznej rocznie w globalnych centrach danych, liczba ta ma się podwoić do 2030 r. w związku z gwałtownym wzrostem obciążeń związanych ze sztuczną inteligencją. Ale nie wszystko energia jest taka sama: jedna kilowatogodzina wyprodukowana w czasie, gdy sieć jest zasilana w 90%. Energia odnawialna powoduje jedną dziesiątą emisji CO₂ w porównaniu z energią wytwarzaną w szczytowym zapotrzebowaniu objęte elektrowniami gazowymi. Ta sama logika ma zastosowanie w przestrzeni kosmicznej: wykonaj zadanie w europejskim regionie chmury dzięki czystemu koszykowi energetycznemu może być czterokrotnie bardziej ekologiczny niż region zasilany głównie z węgla.
Ta koncepcja — uruchom oprogramowanie Gdy e Gdzie prąd jest sprzątaczka – to się nazywa przesunięcie popytu o obliczenia uwzględniające emisję dwutlenku węgla, i jest to jedna z podstawowych zasad zielonej inżynierii oprogramowania. Tam Fundacja Zielonego Oprogramowania wokół tej zasady zbudował referencyjny zestaw narzędzi open source: plik Pakiet SDK uwzględniający emisję dwutlenku węgla, dostępny na GitHubie na licencji MIT i wykorzystywany w produkcji przez takie firmy jak UBS, Vestas i sam Microsoft.
W tym artykule zobaczymy jak działa Carbon Aware SDK od środka: architektura, źródła danych, REST API, integracja z Pythonem, Kubernetesem i GitHub Actions. Będziemy budować konkretne przykłady przesunięcie czasu (przesuń pracę o 6-8 godzin, gdy spadnie intensywność emisji dwutlenku węgla) tj zmiana lokalizacji (kieruj obciążenia do najbardziej ekologicznego regionu chmury w czasie rzeczywistym).
Czego się nauczysz
- Jak działa Carbon Aware SDK: architektura WebAPI, CLI i biblioteka klienta
- Źródła danych o intensywności emisji dwutlenku węgla: WattTime, ElectricityMaps, Ember Climate
- Przesunięcie czasu: planowanie zadań wsadowych w oknach o niskiej intensywności emisji dwutlenku węgla
- Zmiana lokalizacji: wybierz region chmur z najczystszym miksem energetycznym
- Integracja Pythona z klientem Carbon Aware SDK
- Kubernetes + KEDA: automatyczne skalowanie uwzględniające emisję dwutlenku węgla bez zmian w kodzie aplikacji
- Działania GitHub: Rurociąg CI/CD uwzględniający emisję gazów cieplarnianych z inteligentnym planowaniem
- Marginalna a średnia intensywność emisji dwutlenku węgla: jakiego sygnału użyć i dlaczego
- Limity, kompromisy i rzeczywiste przypadki użycia z mierzalną redukcją emisji
Seria o inżynierii oprogramowania ekologicznego — wszystkie artykuły
| # | Tytuł | Temat |
|---|---|---|
| 1 | Zasady zielonej inżynierii oprogramowania | 8 zasad GSF, specyfikacja SCI ISO/IEC 21031 |
| 2 | Pomiar emisji za pomocą CodeCarbon | Śledzenie CO₂ w Pythonie, MLflow, dashboard |
| 3 | Climatiq API: Intensywność emisji dwutlenku węgla w systemach chmurowych | REST API, kalkulacja emisji w chmurze i łańcuch dostaw |
| 4 | Pakiet SDK Carbon Aware (ten artykuł) | Przesunięcie czasu, zmiana lokalizacji, Kubernetes, CI/CD |
| 5 | Zakres 3 i rurociąg ESG | Emisje typu upstream/downstream, rurociąg danych CSRD |
| 6 | Modelowanie zakresu 1, 2, 3 | Ramy rachunkowości protokołu GHG, SBTi |
| 7 | Ślad węglowy AI | Szkolenia LLM, wnioskowanie, optymalizacja energetyczna |
| 8 | Zrównoważone wzorce oprogramowania | Zielony wzór, wydajna architektura |
| 9 | ESG i CSRD dla oprogramowania | Zgodność z przepisami, obowiązkowe raportowanie UE |
| 10 | GreenOps: Zrównoważona działalność | FinOps+GreenOps, metryki operacyjne, zmiana kulturowa |
Zmiana popytu: zasada obliczeń uwzględniających emisję dwutlenku węgla
Intensywność emisji dwutlenku węgla w kilowatogodzinie energii elektrycznej nie jest stała: zmienia się co godzinę w oparciu o ilość energii odnawialnej dostępnej w sieci. W słoneczny dzień w Niemczech intensywność emisji dwutlenku węgla może spaść poniżej i 100 gCO₂/kWh w godzinach elektrownie; noc, gdy energia słoneczna jest wyłączona i spada zapotrzebowanie przemysłu, wzrasta powyżej 400 gCO₂/kWh. Ta sama zmienność istnieje w przestrzeni: Szwecja, nakarmiona prawie w całości z elektrowni wodnych i jądrowych, waha się w granicach 15–40 gCO₂/kWh, podczas gdy w Polsce nadal uzależniona od węgla, często przekracza 700 gCO₂/kWh.
Il przesunięcie popytu wykorzystaj tę zmienność, przenosząc elastyczne obciążenia — przetwarzanie wsadowe, szkolenia ML, tworzenie kopii zapasowych, analiza danych, testowanie CI/CD — w stronę momentów i regionów, w których prąd jest czystszy. Nie chodzi o rezygnację z kalkulacji: chodzi o to wybierz najlepszy czas go uruchomić.
Rodzaje przesunięcia popytu
| Typ | Opis | Praktyczny przykład | Typowa redukcja CO₂ |
|---|---|---|---|
| Przesunięcie czasu | Przenieś obciążenie do okna czasowego o niskiej intensywności emisji dwutlenku węgla w tym samym regionie | Przeprowadź szkolenie ML o 2:00 zamiast o 14:00 | 20-45% |
| Zmiana lokalizacji | Uruchamiaj obciążenia w regionie chmury, korzystając z czystszego zestawu mocy | Kieruj zadanie z us-east-1 do eu-north-1 (Sztokholm) | 30-70% |
| Kształtowanie popytu | Zmniejsz oferowane funkcje, gdy intensywność emisji dwutlenku węgla jest wysoka | Wyłącz rozdzielczość wideo 4K podczas szczytowej emisji | 10-25% |
Oczywiście nie wszystkie obciążenia można przenosić: żądanie HTTP użytkownika musi zostać obsłużone natychmiast, transakcja finansowa nie może czekać. Ale zaskakująco wysoki odsetek obciążeń jest informatyka korporacyjna elastyczny czasowo: Trening modelu ML, potok ETL co noc, generowanie raportów, tworzenie kopii zapasowych, skanowanie bezpieczeństwa, potok CI/CD. To są właśnie idealnymi kandydatami do planowania uwzględniającego emisję dwutlenku węgla.
Zestaw SDK świadomy emisji dwutlenku węgla: architektura i komponenty
Il Pakiet SDK uwzględniający emisję dwutlenku węgla to projekt open source Fundacji Green Software,
wydany na licencji MIT i obecnie w stanie Projekt dyplomowy (najwyższy poziom
zapadalności w portfelu GSF). Repozytorium znajduje się na GitHubie pod adresem
Green-Software-Foundation/carbon-aware-sdk i jest rozwijany głównie w języku C# z
ASP.NET Core WebAPI, ale udostępnia interfejsy, z których może korzystać dowolny język.
Architektura jest modułowa i koncentruje się wokół kluczowej koncepcji: pojedynczy interfejs znormalizowany aby uzyskać dostęp do danych o intensywności emisji dwutlenku węgla od różnych dostawców, z zawsze dostępnymi danymi wyjściowymi gCO₂eq/kWh niezależnie od źródła. Jest to kluczowe, ponieważ każdy dostawca stosuje inne jednostki, stopień szczegółowości geograficznej i metodologię obliczeń.
Składniki pakietu SDK Carbon Aware
| Część | Technologia | Używać | Kiedy go używać |
|---|---|---|---|
| WebAPI | ASP.NET Core (C#), Docker | REST API ze Swagger/OpenAPI, które można wdrożyć jako mikrousługę | Integracja z dowolnym stosem, architekturami mikroserwisowymi |
| interfejs wiersza polecenia | narzędzie dotnet, wieloplatformowe | Zapytanie terminala, skrypty bash/PowerShell | Skrypty wdrożeniowe, automatyzacja DevOps, szybkie testowanie |
| Biblioteka SDK | Pakiet NuGet (C#) | Bezpośrednia integracja z kodem aplikacji .NET | Aplikacje .NET, które wymagają wbudowanej logiki uwzględniającej emisję dwutlenku węgla |
| Klient Pythona | generator openapi, Python 3.8+ | Automatycznie wygenerowany klient na podstawie specyfikacji OpenAPI interfejsu WebAPI | Potok ML, skrypty inżynierii danych, Airflow DAG |
Typowy przebieg jest następujący: kręcisz WebAPI jako kontener Docker, skonfigurowany przy użyciu poświadczeń dostawcy intensywności emisji dwutlenku węgla, a punkty końcowe są sprawdzane ODPOCZYNEK z aplikacji do planowania. WebAPI zajmuje się normalizacją danych, zarządzaniem połączeniami dostawcom i zwrócić optymalne okna czasowe lub regiony.
Źródła danych: WattTime, ElectricityMaps i Ember Climate
Zestaw SDK Carbon Aware obsługuje wielu dostawców danych o intensywności emisji dwutlenku węgla, każdy z własnymi funkcjami i różnym zasięgu geograficznym. Wybór dostawcy ma ważne implikacje metodologiczne (marginalny vs średni) i praktyczny (zasięg, koszt, częstotliwość aktualizacji).
Porównanie dostawców intensywności emisji dwutlenku węgla
| Dostawcy | Typ sygnału | Zasięg | Częstotliwość | Prognoza | Koszt |
|---|---|---|---|---|---|
| WattTime | Marginalny (MOER) | Pełne USA, ponad 50 krajów | 5 minut | 24-72 godziny | Ograniczony plan bezpłatny, plany komercyjne |
| Mapy energii elektrycznej | Średnia (LCA) | Ponad 85 krajów, cała UE | Godzinowe/podgodzinne | 24 godziny | Darmowy plan deweloperski, komercyjny |
| Klimat Żaru | Średnia historyczna | Globalny (ponad 50 krajów) | Dzienne/historyczne | Nie (tylko historyczne) | Otwarte dane, za darmo |
| Statyczny JSON | Konfigurowalne | Zwyczaj | Podręcznik | No | Bezpłatnie (rozwój/testowanie) |
Marginalna a średnia intensywność emisji dwutlenku węgla: kluczowe rozróżnienie
Wybór pomiędzy sygnałem marginalny e średni to jest różnica najważniejsze podejście metodologiczne w informatyce uwzględniającej emisję dwutlenku węgla i ma konkretne implikacje dla wyników osiągalne.
Sygnał marginalny (MOER — marginalny wskaźnik emisji operacyjnej), dostarczone przez WattTime, odpowiada na pytanie: „Gdybym teraz zwiększył swoje zużycie o 1 kWh, która elektrownia wszedł w życie, aby pokryć to dodatkowe zapotrzebowanie?”. Odpowiedź brzmi prawie zawsze najbardziej elastyczną elektrownię gazową (tzw. „generator marginalny”), a nie OZE są już włączone na maksimum. Sygnał ten jest najbardziej istotny dla decyzji w czasie rzeczywistym tych, którzy chcą ograniczyć emisję spowodowany z jego przyrostowego zużycia.
Sygnał średnia (LCA – średnia cyklu życia), dostarczone przez ElectricityMaps, odpowiada zamiast na pytanie: „Jaka jest średnia emisja całej wyprodukowanej energii elektrycznej? w tej chwili w sieci?”. W sieci składającej się w 50% z energii słonecznej i w 50% z gazu, średni sygnał wynosi ~250 gCO₂/kWh, niezależnie od tego, co dzieje się na brzegu. Tego sygnału jest więcej nadaje się do raportowania ESG i rachunkowości rynkowej w zakresie 2.
Ważne: ElectricityMaps Zmiana podejścia w 2025 r
W 2025 r. ElectricityMaps ma sygnał marginalny jest nieciągły z Twojego API powołując się na obawy dotyczące weryfikowalności danych i zgodności z przepisami UE oraz USA zakazujące stosowania czynników marginalnych w rachunkowości Zakresu 2. Mapy energii elektrycznej oferuje obecnie wyłącznie sygnały średnie w oparciu o metodologię LCA (ocena cyklu życia). Jeśli Twój przypadek użycia wymaga sygnału marginalnego, WattTime pozostaje jedynym głównym dostawcą który to wspiera. Zestaw SDK Carbon Aware obsługuje tę różnicę w sposób przejrzysty poprzez konfiguracja dostawcy.
Kiedy używać jakiego sygnału
| Przypadek użycia | Zalecany sygnał | Dostawcy | Dlaczego |
|---|---|---|---|
| Zadania wsadowe z przesunięciem w czasie | Marginalny | WattTime | Maksymalizuje rzeczywistą redukcję powodowanych emisji |
| Zmiana lokalizacji w wielu regionach | Średnie lub marginalne | ElectricityMaps lub WattTime | Oba przydatne; bardziej stabilna średnia międzyregionalna |
| Raportowanie ESG / Zakres 2 | Średni | Mapy energii elektrycznej | Wymagane przez Protokół GHG i przepisy UE/USA |
| Rozwój i testowanie | Statyczny JSON | Plik lokalny | Bez kosztów, deterministyczne dane do testowania |
Instalacja i konfiguracja pakietu SDK Carbon Aware
Najszybszym sposobem lokalnego uruchomienia zestawu SDK Carbon Aware jest użycie Docker Compose. Zobaczmy pełną konfigurację z ElectricityMaps jako dostawcą (najłatwiej skonfigurować dzięki darmowemu kluczowi API dla programistów).
# docker-compose.yml - Carbon Aware SDK WebAPI
version: '3.8'
services:
carbon-aware-api:
image: ghcr.io/green-software-foundation/carbon-aware-sdk:latest
ports:
- "8080:80"
environment:
# Provider: ElectricityMaps (segnale medio LCA)
CarbonAwareVars__CarbonIntensityDataSource: "ElectricityMaps"
CarbonAwareVars__ElectricityMapsClient__APITokenHeader: "auth-token"
CarbonAwareVars__ElectricityMapsClient__APIToken: "${ELECTRICITY_MAPS_TOKEN}"
CarbonAwareVars__ElectricityMapsClient__BaseURL: "https://api.electricitymap.org/v3/"
# Oppure WattTime (segnale marginale MOER)
# CarbonAwareVars__CarbonIntensityDataSource: "WattTime"
# CarbonAwareVars__WattTimeClient__Username: "${WATTTIME_USER}"
# CarbonAwareVars__WattTimeClient__Password: "${WATTTIME_PASS}"
# CarbonAwareVars__WattTimeClient__BaseURL: "https://api2.watttime.org/v2/"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:80/health"]
interval: 30s
timeout: 10s
retries: 3
W środowisku programistycznym i testowym można użyć statycznego źródła danych JSON, które nie wymaga Klucz API. Zestaw SDK zawiera wstępnie załadowane przykładowe zestawy danych.
# appsettings.json - Configurazione con JSON statico (dev/test)
{
"CarbonAwareVars": {
"CarbonIntensityDataSource": "Json",
"JsonDataFileLocation": "./data/test-data.json",
"Proxy": {
"UseProxy": false
}
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}
Po uruchomieniu kontenera dokumentacja Swagger jest dostępna pod adresem
http://localhost:8080/swagger. Sprawdźmy, czy z jednym wszystko działa
połączenie testowe z CLI:
# Installazione Carbon Aware CLI (dotnet tool)
dotnet tool install -g GSF.CarbonAware.Cli
# Verifica emissioni attuali in una location
carbon-aware emissions location --location "westeurope" \
--config ./appsettings.json
# Output esempio:
# Location: westeurope
# Time: 2025-09-15T14:30:00Z
# Rating: 180.5 gCO2eq/kWh
# Duration: PT1H
Punkty końcowe interfejsu API: serce zestawu SDK obsługującego technologię Carbon Aware
WebAPI udostępnia zestaw punktów końcowych REST udokumentowanych za pomocą OpenAPI/Swagger. Zobaczmy główne z przykładami rzeczywistych połączeń i interpretacji odpowiedzi.
GET /emissions/bylocations — Bieżące emisje według lokalizacji
Zwraca dane dotyczące intensywności emisji dwutlenku węgla dla jednej lub większej liczby lokalizacji w przedziale czasu. Służy do porównywania aktualnej intensywności emisji dwutlenku węgla pomiędzy różnymi regionami (zmiana lokalizacji).
# Confronto carbon intensity tra regioni cloud (location shifting)
GET /emissions/bylocations?locations=westeurope&locations=eastus&locations=northeurope
&time=2025-09-15T14:00:00Z
&toTime=2025-09-15T15:00:00Z
# Risposta JSON:
[
{
"location": "westeurope",
"timestamp": "2025-09-15T14:00:00Z",
"duration": 60,
"rating": 185.3
},
{
"location": "eastus",
"timestamp": "2025-09-15T14:00:00Z",
"duration": 60,
"rating": 312.7
},
{
"location": "northeurope",
"timestamp": "2025-09-15T14:00:00Z",
"duration": 60,
"rating": 32.1
}
]
# North Europe (Irlanda/Stoccolma) quasi 10x più verde di East US!
GET /emissions/bylocation/best — Najlepsza lokalizacja
Bezpośrednio zwraca lokalizację o najniższej intensywności emisji dwutlenku węgla w zakresie określone. Idealny do automatycznego routingu obciążeń.
GET /emissions/bylocation/best?locations=westeurope&locations=eastus&locations=northeurope
&time=2025-09-15T14:00:00Z
&toTime=2025-09-15T15:00:00Z
# Risposta:
{
"location": "northeurope",
"timestamp": "2025-09-15T14:00:00Z",
"duration": 60,
"rating": 32.1
}
GET /emisje/prognozy/prąd — Prognoza intensywności emisji dwutlenku węgla
Jest to najpotężniejszy punkt końcowy dla przesunięcie czasu: powraca prognozę na następne 24–72 godziny z optymalnym oknem czasowym, w którym należy uruchomić obciążenie. Umożliwia określenie czasu trwania zadania w celu znalezienia najlepszego okna o tej długości.
GET /emissions/forecasts/current?locations=westeurope
&dataStartAt=2025-09-15T16:00:00Z
&dataEndAt=2025-09-16T16:00:00Z
&windowSize=60
# Risposta con finestra ottimale:
[
{
"generatedAt": "2025-09-15T14:30:00Z",
"location": "westeurope",
"dataStartAt": "2025-09-15T16:00:00Z",
"dataEndAt": "2025-09-16T16:00:00Z",
"windowSize": 60,
"optimalDataPoints": [
{
"location": "westeurope",
"timestamp": "2025-09-16T02:00:00Z",
"duration": 60,
"rating": 95.2
}
],
"forecastData": [
{ "timestamp": "2025-09-15T16:00:00Z", "rating": 195.8 },
{ "timestamp": "2025-09-15T17:00:00Z", "rating": 210.3 },
{ "timestamp": "2025-09-15T18:00:00Z", "rating": 230.1 },
{ "timestamp": "2025-09-15T22:00:00Z", "rating": 155.4 },
{ "timestamp": "2025-09-16T01:00:00Z", "rating": 105.7 },
{ "timestamp": "2025-09-16T02:00:00Z", "rating": 95.2 }, // OTTIMALE
{ "timestamp": "2025-09-16T03:00:00Z", "rating": 98.6 },
{ "timestamp": "2025-09-16T06:00:00Z", "rating": 120.3 }
]
}
]
# Il job da 1 ora dovrebbe partire alle 02:00 UTC (risparmio ~51% CO2 vs ora corrente)
POST /emissions/forecasts/batch — wiele prognoz
Umożliwia wysyłanie partii żądań prognoz w celu zaplanowania wielu zadań jednocześnie, przydatne do planowania złożonych potoków z zależnościami.
POST /emissions/forecasts/batch
Content-Type: application/json
[
{
"requestedAt": "2025-09-15T14:00:00Z",
"location": "westeurope",
"dataStartAt": "2025-09-15T20:00:00Z",
"dataEndAt": "2025-09-16T08:00:00Z",
"windowSize": 120
},
{
"requestedAt": "2025-09-15T14:00:00Z",
"location": "westeurope",
"dataStartAt": "2025-09-15T20:00:00Z",
"dataEndAt": "2025-09-16T08:00:00Z",
"windowSize": 30
}
]
Integracja z Pythonem: CarbonAwareClient i Time Shifting
W przypadku potoków języka Python — zazwyczaj inżynierii danych, szkoleń ML lub analiz wsadowych — Zestaw SDK Carbon Aware oferuje klienta Pythona automatycznie generowanego przez specyfikację OpenAPI. Zobaczmy, jak zbudować kompletny system przesuwania czasu dla zadania szkoleniowego ML.
Instalacja i konfiguracja klienta Python
# requirements.txt
carbon-aware-sdk-client>=1.0.0 # Client auto-generato da OpenAPI
requests>=2.31.0
python-dateutil>=2.8.2
apscheduler>=3.10.4 # Scheduling job
pytz>=2023.3
# Installazione da PyPI (se disponibile) oppure da sorgente:
# pip install openapi-python-client
# openapi-python-client generate \
# --url http://localhost:8080/swagger/v1/swagger.json
Klient Carbon Aware w Pythonie
"""
carbon_aware_client.py
Client Python per il Carbon Aware SDK WebAPI
Implementa time shifting e location shifting
"""
import requests
from datetime import datetime, timedelta, timezone
from typing import Optional
import logging
logger = logging.getLogger(__name__)
class CarbonAwareClient:
"""
Client per il Carbon Aware SDK WebAPI.
Incapsula le chiamate REST e fornisce metodi ad alto livello
per time shifting e location shifting.
"""
def __init__(self, base_url: str = "http://localhost:8080"):
self.base_url = base_url.rstrip("/")
self.session = requests.Session()
self.session.headers.update({
"Content-Type": "application/json",
"Accept": "application/json"
})
def get_current_emissions(
self,
locations: list[str],
time: Optional[datetime] = None,
to_time: Optional[datetime] = None
) -> list[dict]:
"""
Recupera la carbon intensity corrente per una o più location.
Args:
locations: Lista di location name (es. ["westeurope", "eastus"])
time: Inizio intervallo (default: ora corrente)
to_time: Fine intervallo (default: time + 1 ora)
Returns:
Lista di dict con location, timestamp, rating (gCO2eq/kWh)
"""
now = datetime.now(timezone.utc)
time = time or now
to_time = to_time or (time + timedelta(hours=1))
params = {
"locations": locations,
"time": time.isoformat(),
"toTime": to_time.isoformat()
}
response = self.session.get(
f"{self.base_url}/emissions/bylocations",
params=params
)
response.raise_for_status()
return response.json()
def get_best_location(
self,
locations: list[str],
time: Optional[datetime] = None,
to_time: Optional[datetime] = None
) -> dict:
"""
Trova la location con la più bassa carbon intensity.
Returns:
Dict con location, timestamp, rating della migliore location
"""
now = datetime.now(timezone.utc)
time = time or now
to_time = to_time or (time + timedelta(hours=1))
params = {
"locations": locations,
"time": time.isoformat(),
"toTime": to_time.isoformat()
}
response = self.session.get(
f"{self.base_url}/emissions/bylocation/best",
params=params
)
response.raise_for_status()
return response.json()
def get_optimal_window(
self,
location: str,
window_size_minutes: int,
search_start: Optional[datetime] = None,
search_end: Optional[datetime] = None
) -> dict:
"""
Trova la finestra temporale ottimale (più bassa carbon intensity)
per eseguire un job di durata window_size_minutes nella location indicata.
Args:
location: Location name (es. "westeurope")
window_size_minutes: Durata del job in minuti
search_start: Inizio finestra di ricerca (default: ora corrente)
search_end: Fine finestra di ricerca (default: search_start + 24 ore)
Returns:
Dict con optimalDataPoints (timestamp ottimale) e forecastData
"""
now = datetime.now(timezone.utc)
search_start = search_start or now
search_end = search_end or (search_start + timedelta(hours=24))
params = {
"locations": [location],
"dataStartAt": search_start.isoformat(),
"dataEndAt": search_end.isoformat(),
"windowSize": window_size_minutes
}
response = self.session.get(
f"{self.base_url}/emissions/forecasts/current",
params=params
)
response.raise_for_status()
forecasts = response.json()
if not forecasts:
raise ValueError(f"Nessun forecast disponibile per {location}")
return forecasts[0]
def calculate_carbon_savings(
self,
current_rating: float,
optimal_rating: float,
duration_hours: float,
power_kw: float
) -> dict:
"""
Calcola il risparmio di CO2 del time shifting.
Args:
current_rating: Carbon intensity attuale (gCO2/kWh)
optimal_rating: Carbon intensity ottimale (gCO2/kWh)
duration_hours: Durata del job in ore
power_kw: Potenza media del job in kW
Returns:
Dict con emissioni attuali, ottimali e risparmio
"""
energy_kwh = duration_hours * power_kw
current_emissions_g = current_rating * energy_kwh
optimal_emissions_g = optimal_rating * energy_kwh
savings_g = current_emissions_g - optimal_emissions_g
savings_pct = (savings_g / current_emissions_g) * 100 if current_emissions_g > 0 else 0
return {
"energy_kwh": energy_kwh,
"current_emissions_gco2": round(current_emissions_g, 2),
"optimal_emissions_gco2": round(optimal_emissions_g, 2),
"savings_gco2": round(savings_g, 2),
"savings_percentage": round(savings_pct, 1)
}
Przesunięcie czasu: Harmonogram szkolenia ML
"""
ml_training_scheduler.py
Scheduler carbon-aware per job di training ML.
Calcola la finestra ottimale nelle prossime 12 ore
e pianifica l'avvio del training.
"""
from datetime import datetime, timedelta, timezone
from apscheduler.schedulers.blocking import BlockingScheduler
from carbon_aware_client import CarbonAwareClient
import subprocess
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
class MLTrainingScheduler:
"""
Scheduler carbon-aware per training ML.
Trova la finestra a più bassa carbon intensity
nelle prossime 12 ore e schedula il training.
"""
def __init__(
self,
location: str = "westeurope",
training_duration_minutes: int = 120,
max_wait_hours: int = 12,
carbon_aware_url: str = "http://localhost:8080"
):
self.location = location
self.training_duration_minutes = training_duration_minutes
self.max_wait_hours = max_wait_hours
self.client = CarbonAwareClient(base_url=carbon_aware_url)
self.scheduler = BlockingScheduler(timezone="UTC")
def find_optimal_start(self) -> datetime:
"""
Interroga il Carbon Aware SDK per trovare
il momento ottimale nelle prossime max_wait_hours.
"""
now = datetime.now(timezone.utc)
search_end = now + timedelta(hours=self.max_wait_hours)
logger.info(
f"Ricerca finestra ottimale in {self.location} "
f"per job da {self.training_duration_minutes} minuti..."
)
forecast = self.client.get_optimal_window(
location=self.location,
window_size_minutes=self.training_duration_minutes,
search_start=now,
search_end=search_end
)
optimal_points = forecast.get("optimalDataPoints", [])
if not optimal_points:
logger.warning("Nessuna finestra ottimale trovata, uso ora corrente")
return now
optimal_timestamp_str = optimal_points[0]["timestamp"]
optimal_timestamp = datetime.fromisoformat(
optimal_timestamp_str.replace("Z", "+00:00")
)
optimal_rating = optimal_points[0]["rating"]
# Recupera rating corrente per calcolo risparmio
current_data = self.client.get_current_emissions([self.location])
current_rating = current_data[0]["rating"] if current_data else 300.0
# Calcola risparmio
savings = self.client.calculate_carbon_savings(
current_rating=current_rating,
optimal_rating=optimal_rating,
duration_hours=self.training_duration_minutes / 60,
power_kw=150 # GPU server: ~150kW stima
)
wait_minutes = (optimal_timestamp - now).total_seconds() / 60
logger.info(
f"Finestra ottimale trovata:\n"
f" Inizio: {optimal_timestamp.strftime('%Y-%m-%d %H:%M UTC')}\n"
f" Carbon intensity: {optimal_rating:.1f} gCO2/kWh\n"
f" Carbon intensity attuale: {current_rating:.1f} gCO2/kWh\n"
f" Attesa: {wait_minutes:.0f} minuti\n"
f" Risparmio CO2: {savings['savings_gco2']:.0f}g ({savings['savings_percentage']}%)"
)
return optimal_timestamp
def run_training(self):
"""Esegue il job di training ML."""
logger.info("Avvio training ML carbon-optimized...")
result = subprocess.run(
["python", "train_model.py", "--config", "config.yaml"],
capture_output=True,
text=True
)
if result.returncode == 0:
logger.info("Training completato con successo")
else:
logger.error(f"Training fallito: {result.stderr}")
def schedule_and_run(self):
"""Trova la finestra ottimale e schedula il training."""
optimal_start = self.find_optimal_start()
now = datetime.now(timezone.utc)
if optimal_start <= now + timedelta(minutes=5):
# Avvio immediato se il momento ottimale è imminente
logger.info("Avvio immediato (finestra ottimale = ora)")
self.run_training()
else:
# Schedula l'avvio
self.scheduler.add_job(
self.run_training,
"date",
run_date=optimal_start,
id="ml_training"
)
logger.info(f"Training schedulato per {optimal_start}")
self.scheduler.start()
# Utilizzo:
if __name__ == "__main__":
scheduler = MLTrainingScheduler(
location="westeurope",
training_duration_minutes=120, # Job da 2 ore
max_wait_hours=12 # Aspetta massimo 12 ore
)
scheduler.schedule_and_run()
Zmiana lokalizacji: wyznaczanie tras uwzględniających emisję gazów cieplarnianych w wielu regionach
Zmiana lokalizacji jest często bardziej skuteczna niż zmiana czasu, ponieważ jest to różnica
intensywność węgla pomiędzy obszarami chmur może być znacznie większa niż zmienność czasowa
w jednym regionie. Praca wykonana w
europe-north1 (Finlandia, ~9 gCO₂/kWh) zamiast
asia-east1 (Tajwan, ~550 gCO₂/kWh) zmniejsza emisję o ponad 98%.
Średnia intensywność emisji dwutlenku węgla według regionu chmur (2025)
| Dostawcy chmury | Region | Lokalizacja | Średnia intensywność emisji dwutlenku węgla (gCO₂/kWh) | Główne źródło |
|---|---|---|---|---|
| Chmura Google | Europa-Północ1 | Finlandia | 9 | Hydroelektrownia, nuklearna |
| Lazur | szwecja centralna | Szwecja | 18 | Hydroelektrownia, nuklearna |
| Lazur | Europa Północna | Irlandia | 280 | Wiatr, gaz |
| AWS | eu-zachód-1 | Irlandia | 320 | Wiatr, gaz |
| AWS | us-wschód-1 | Wirginia | 380 | Gaz, atom, węgiel |
| Chmura Google | Azja-Wschód1 | Tajwan | 545 | Węgiel, gaz |
| AWS | ap-południowy wschód-1 | Singapur | 408 | Gaz ziemny |
Python: automatyczne przesuwanie lokalizacji dla zadań wsadowych
"""
location_shifter.py
Location shifting carbon-aware per batch job multi-cloud.
Seleziona automaticamente la regione più verde
per eseguire un job Kubernetes.
"""
from carbon_aware_client import CarbonAwareClient
from datetime import datetime, timezone
import subprocess
import json
import logging
logger = logging.getLogger(__name__)
# Mapping location Carbon Aware SDK -> region cloud reale
LOCATION_TO_CLOUD_REGION = {
"northeurope": {
"aws": "eu-west-1",
"azure": "northeurope",
"gcp": "europe-west1"
},
"swedencentral": {
"azure": "swedencentral",
"gcp": "europe-north1"
},
"westeurope": {
"aws": "eu-central-1",
"azure": "westeurope",
"gcp": "europe-west4"
},
"eastus": {
"aws": "us-east-1",
"azure": "eastus",
"gcp": "us-east1"
}
}
CANDIDATE_LOCATIONS = ["swedencentral", "northeurope", "westeurope", "eastus"]
TARGET_CLOUD = "azure" # Cloud provider di destinazione
class LocationShifter:
def __init__(self, carbon_aware_url: str = "http://localhost:8080"):
self.client = CarbonAwareClient(base_url=carbon_aware_url)
def select_greenest_region(self) -> tuple[str, str, float]:
"""
Seleziona la regione cloud con la più bassa carbon intensity
tra le candidate.
Returns:
Tuple (location_name, cloud_region, carbon_intensity_rating)
"""
best = self.client.get_best_location(
locations=CANDIDATE_LOCATIONS
)
best_location = best["location"]
best_rating = best["rating"]
# Mappa a regione cloud reale
cloud_regions = LOCATION_TO_CLOUD_REGION.get(best_location, {})
cloud_region = cloud_regions.get(TARGET_CLOUD, "westeurope")
logger.info(
f"Regione selezionata: {best_location} -> {TARGET_CLOUD}:{cloud_region}\n"
f"Carbon intensity: {best_rating:.1f} gCO2/kWh"
)
# Log emissioni di tutte le candidate per confronto
all_emissions = self.client.get_current_emissions(CANDIDATE_LOCATIONS)
logger.info("Confronto regioni:")
for em in sorted(all_emissions, key=lambda x: x["rating"]):
marker = " <- SELEZIONATA" if em["location"] == best_location else ""
logger.info(f" {em['location']:20s} {em['rating']:6.1f} gCO2/kWh{marker}")
return best_location, cloud_region, best_rating
def deploy_job_to_region(self, cloud_region: str, job_config: dict):
"""
Deploya un batch job Kubernetes nella regione selezionata.
Usa kubectl con il context della regione target.
"""
# Sostituisce la regione nel manifest Kubernetes
manifest = job_config.copy()
manifest["metadata"]["annotations"]["target-region"] = cloud_region
manifest_json = json.dumps(manifest)
logger.info(f"Deploy job in regione {cloud_region}...")
result = subprocess.run(
["kubectl", "apply", "-f", "-", "--context", f"aks-{cloud_region}"],
input=manifest_json,
capture_output=True,
text=True
)
if result.returncode == 0:
logger.info(f"Job deployato con successo in {cloud_region}")
else:
raise RuntimeError(f"Deploy fallito: {result.stderr}")
def run_carbon_aware_job(self, job_config: dict):
"""Workflow completo: seleziona regione e deploya."""
location, cloud_region, rating = self.select_greenest_region()
logger.info(f"Avvio job carbon-aware in {cloud_region} ({rating:.1f} gCO2/kWh)")
self.deploy_job_to_region(cloud_region, job_config)
# Utilizzo
if __name__ == "__main__":
shifter = LocationShifter()
# Configurazione job Kubernetes
batch_job = {
"apiVersion": "batch/v1",
"kind": "Job",
"metadata": {
"name": "data-processing-carbon-aware",
"annotations": {
"green-software.io/carbon-aware": "true",
"target-region": "" # Verrà popolato dal location shifter
}
},
"spec": {
"template": {
"spec": {
"containers": [{
"name": "processor",
"image": "myapp/data-processor:latest",
"resources": {
"requests": {"cpu": "2", "memory": "4Gi"},
"limits": {"cpu": "4", "memory": "8Gi"}
}
}],
"restartPolicy": "Never"
}
}
}
}
shifter.run_carbon_aware_job(batch_job)
Kubernetes i KEDA: automatyczne skalowanie uwzględniające emisję dwutlenku węgla
Dla środowisk produkcyjnych Kubernetes firma Microsoft udostępniła wersję Operator KEDA świadomy emisji dwutlenku węgla (otwarte źródło na Azure GitHub), który integruje pakiet SDK Carbon Aware bezpośrednio z warstwą skalującą KEDA. Zasada jest prosta: gdy intensywność emisji dwutlenku węgla jest niska (dostępna jest czysta energia), KEDA może skalować do maksymalnej liczby replik; gdy jest wysoka, maksymalna liczba replikacji jest automatycznie zmniejszana.
Architektura składa się z trzech elementów: Eksporter intensywności emisji dwutlenku węgla Kubernetes (pobierz dane z pakietu SDK Carbon Aware i tam udostępnia jako ConfigMap w klastrze), plik Operator KEDA świadomy emisji dwutlenku węgla (odczytuje ConfigMap i aktualizuje limity skalowania KEDA) oraz zasób niestandardowy CarbonAwareKedaScaler który definiuje progi skalowania.
Instalacja eksportera intensywności emisji dwutlenku węgla
# Installa il Carbon Intensity Exporter con Helm
helm repo add azure-carbon https://azure.github.io/carbon-aware-keda-operator
helm repo update
# Crea il secret con le credenziali WattTime
kubectl create secret generic watttime-credentials \
--from-literal=username=MY_WATTTIME_USER \
--from-literal=password=MY_WATTTIME_PASS \
--namespace kube-system
# Installa l'exporter
helm install carbon-intensity-exporter azure-carbon/carbon-intensity-exporter \
--namespace kube-system \
--set carbonDataProvider=WattTime \
--set watttime.username=MY_WATTTIME_USER \
--set watttime.password=MY_WATTTIME_PASS \
--set location=westus \
--set forecastIntervalHours=12
ConfigMap wygenerowany przez eksportera
apiVersion: v1
kind: ConfigMap
metadata:
name: carbon-intensity
namespace: kube-system
data:
# Recuperata ogni 12 ore dall'exporter
lastUpdated: "2025-09-15T14:00:00Z"
forecastDateTime: "2025-09-15T14:00:00Z"
message: "Carbon intensity data for westus"
# Array JSON con forecast delle prossime 24 ore
# Format: ISO timestamp + gCO2/kWh
forecastData: |
[
{ "timestamp": "2025-09-15T14:00:00Z", "intensity": 312.5 },
{ "timestamp": "2025-09-15T15:00:00Z", "intensity": 298.3 },
{ "timestamp": "2025-09-15T16:00:00Z", "intensity": 285.1 },
{ "timestamp": "2025-09-15T22:00:00Z", "intensity": 195.7 },
{ "timestamp": "2025-09-16T02:00:00Z", "intensity": 145.2 },
{ "timestamp": "2025-09-16T03:00:00Z", "intensity": 138.9 }
]
# Intensità corrente
currentIntensity: "312.5"
currentIntensityUnit: "gCO2/kWh"
CarbonAwareKedaScaler: Niestandardowa definicja zasobów
apiVersion: carbonaware.azure.com/v1alpha1
kind: CarbonAwareKedaScaler
metadata:
name: batch-processor-carbon-scaler
namespace: default
spec:
# Riferimento al KEDA ScaledJob da controllare
kedaTargetRef:
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
name: batch-processor-scaledjob
# Sorgente dati carbon intensity (ConfigMap dell'exporter)
carbonIntensityForecastDataSource:
localConfigMap:
name: carbon-intensity
namespace: kube-system
key: forecastData
mockCarbonForecast: false
# Soglie: definiscono maxReplicas in base all'intensità carbonica
# Ordinate per intensità crescente
# - Se intensità <= 150: max 20 repliche (energia molto pulita)
# - Se intensità <= 300: max 10 repliche (energia moderatamente pulita)
# - Se intensità <= 500: max 4 repliche (energia poco pulita)
# - Se intensità > 500: max 1 replica (energia sporca)
maxReplicasByCarbonIntensity:
- carbonIntensityThreshold: 150
maxReplicas: 20
- carbonIntensityThreshold: 300
maxReplicas: 10
- carbonIntensityThreshold: 500
maxReplicas: 4
ScaledJob KEDA do przetwarzania wsadowego
apiVersion: keda.sh/v1alpha1
kind: ScaledJob
metadata:
name: batch-processor-scaledjob
namespace: default
labels:
app: batch-processor
green-software.io/carbon-aware: "true"
spec:
# Sorgente trigger: RabbitMQ, Kafka, Azure Queue, ecc.
jobTargetRef:
parallelism: 1
completions: 1
activeDeadlineSeconds: 3600
backoffLimit: 2
template:
metadata:
labels:
app: batch-processor
spec:
containers:
- name: processor
image: myapp/batch-processor:v2.1.0
resources:
requests:
cpu: "500m"
memory: "512Mi"
limits:
cpu: "2"
memory: "2Gi"
env:
- name: BATCH_SIZE
value: "1000"
- name: OUTPUT_BUCKET
value: "gs://my-processed-data"
restartPolicy: Never
# Trigger: coda Azure Service Bus
triggers:
- type: azure-servicebus
metadata:
queueName: batch-jobs
namespace: my-servicebus-namespace
messageCount: "50"
# Scaling: min 0, max viene controllato dal CarbonAwareKedaScaler
pollingInterval: 60 # Controlla la coda ogni 60 secondi
successfulJobsHistoryLimit: 5
failedJobsHistoryLimit: 3
minReplicaCount: 0
maxReplicaCount: 20 # Overridato dal CarbonAwareKedaScaler
Jak operator KEDA świadomy emisji dwutlenku węgla działa w praktyce
Co godzinę operator KEDA świadomy emisji dwutlenku węgla odczytuje zaktualizowaną ConfigMap od eksportera i aktualizuje
pole maxReplicaCount KEDA ScaledJob w oparciu o odpowiedni próg
do aktualnej intensywności emisji dwutlenku węgla. Jeśli w kolejce jest 200 stanowisk pracy, a intensywność emisji dwutlenku węgla jest taka
312 gCO₂/kWh (próg 300–500), KEDA może skalować maksymalnie 10 pracowników
zamiast 20. Gdy tylko natężenie spadnie poniżej 150 (np. o 2:00 w nocy przy silnym wietrze),
limit automatycznie wzrasta do 20, a zadania są przetwarzane szybciej.
Nie są konieczne żadne zmiany w kodzie aplikacji.
Kubernetes CronJob Carbon-Aware z kontenerem Init
W przypadku zadań planowanych okresowo można zastosować inny wzór: a kontener startowy który wysyła zapytanie do zestawu SDK Carbon Aware przed rozpoczęciem zadania i decyduje, czy wykonać, czy odłożyć. To podejście nie wymaga KEDA i działa standardowe Kubernetes CronJobs.
apiVersion: batch/v1
kind: CronJob
metadata:
name: nightly-etl-pipeline
namespace: default
annotations:
green-software.io/carbon-aware: "true"
green-software.io/max-intensity: "200"
spec:
# Schedulato per le 00:00 UTC ogni notte
# L'init container deciderà se eseguire o saltare
schedule: "0 0 * * *"
concurrencyPolicy: Forbid
successfulJobsHistoryLimit: 7
failedJobsHistoryLimit: 3
jobTemplate:
spec:
template:
spec:
initContainers:
# Init container che controlla la carbon intensity
- name: carbon-aware-check
image: curlimages/curl:8.5.0
env:
- name: CARBON_AWARE_API
value: "http://carbon-aware-api.monitoring.svc.cluster.local:8080"
- name: LOCATION
value: "westeurope"
- name: MAX_INTENSITY
value: "200"
command:
- sh
- -c
- |
set -e
echo "Controllo carbon intensity per ${LOCATION}..."
# Interroga il Carbon Aware SDK
RESPONSE=$(curl -sf "${CARBON_AWARE_API}/emissions/bylocations?locations=${LOCATION}")
INTENSITY=$(echo "$RESPONSE" | grep -o '"rating":[0-9.]*' | head -1 | cut -d: -f2)
echo "Carbon intensity corrente: ${INTENSITY} gCO2/kWh (massimo: ${MAX_INTENSITY})"
if [ $(echo "${INTENSITY} > ${MAX_INTENSITY}" | bc -l) -eq 1 ]; then
echo "ATTENZIONE: Intensità carbonica troppo alta (${INTENSITY} > ${MAX_INTENSITY})"
echo "Il job verrà rimandato al prossimo ciclo di scheduling"
exit 1 # Fallisce l'init container, il job non parte
fi
echo "OK: Intensità carbonica accettabile. Avvio job ETL..."
exit 0
containers:
- name: etl-pipeline
image: myapp/etl-pipeline:v1.5.0
command: ["python", "run_etl.py"]
env:
- name: PIPELINE_DATE
value: "$(date +%Y-%m-%d)"
resources:
requests:
cpu: "1"
memory: "2Gi"
limits:
cpu: "4"
memory: "8Gi"
restartPolicy: Never
Działania GitHub: Rurociąg CI/CD uwzględniający emisję dwutlenku węgla
Testowanie i budowanie rurociągów CI/CD należą do najłatwiejszych zadań związanych ze świadomością emisji dwutlenku węgla, ponieważ wiele z nich jest w rzeczywistości elastycznych w czasie: zestaw testów integracyjnych może poczekać 2–4 godziny bez wpływu na przepływ pracy programistów, szczególnie w przypadku potoków nocą lub planowo.
W badaniu z 2024 r. oszacowano ślad węglowy GitHub Actions dla całego ekosystemu to jest w kolejności 450-1000 MTCO₂eq/rok. Z planowaniem uwzględniającym emisję dwutlenku węgla nawet najcięższe przepływy pracy (kompilacja z kompilacją, kompletne zestawy testów, szkolenia modeli), redukcja może być znacząca.
Akcje przepływu pracy w GitHub z planowaniem uwzględniającym emisję dwutlenku węgla
# .github/workflows/carbon-aware-build.yml
# Pipeline CI/CD carbon-aware con scheduling intelligente
name: Carbon-Aware Build and Test
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
# Scheduled: ogni 6 ore per trovare la finestra migliore
schedule:
- cron: '0 */6 * * *'
workflow_dispatch:
inputs:
force_run:
description: 'Forza esecuzione indipendentemente dalla carbon intensity'
required: false
default: 'false'
env:
CARBON_AWARE_API: ${{ secrets.CARBON_AWARE_API_URL }}
MAX_INTENSITY: "250"
LOCATION: "westeurope"
jobs:
# Job 1: Controlla carbon intensity e decide se procedere
carbon-check:
name: Carbon Intensity Check
runs-on: ubuntu-latest
outputs:
should_run: ${{ steps.check.outputs.should_run }}
intensity: ${{ steps.check.outputs.intensity }}
optimal_time: ${{ steps.check.outputs.optimal_time }}
steps:
- name: Check Carbon Intensity
id: check
run: |
# Per PR e push diretti: esegui sempre (developer experience)
if [[ "${{ github.event_name }}" == "push" || \
"${{ github.event_name }}" == "pull_request" || \
"${{ github.event.inputs.force_run }}" == "true" ]]; then
echo "Event: ${{ github.event_name }} - Esecuzione forzata"
echo "should_run=true" >> $GITHUB_OUTPUT
echo "intensity=0" >> $GITHUB_OUTPUT
exit 0
fi
# Per scheduled e manual: controlla carbon intensity
RESPONSE=$(curl -sf \
"${CARBON_AWARE_API}/emissions/bylocations?locations=${LOCATION}" \
--max-time 30) || {
echo "WARN: Carbon Aware API non raggiungibile, esecuzione forzata"
echo "should_run=true" >> $GITHUB_OUTPUT
echo "intensity=-1" >> $GITHUB_OUTPUT
exit 0
}
INTENSITY=$(echo "$RESPONSE" | python3 -c "
import json, sys
data = json.load(sys.stdin)
print(data[0]['rating'] if data else 999)
")
echo "Carbon intensity corrente: ${INTENSITY} gCO2/kWh"
echo "Soglia massima: ${MAX_INTENSITY} gCO2/kWh"
echo "intensity=${INTENSITY}" >> $GITHUB_OUTPUT
if python3 -c "exit(0 if float('$INTENSITY') <= float('$MAX_INTENSITY') else 1)"; then
echo "Intensità accettabile - Avvio build"
echo "should_run=true" >> $GITHUB_OUTPUT
else
echo "Intensità troppo alta - Skip build"
echo "should_run=false" >> $GITHUB_OUTPUT
# Recupera finestra ottimale per info
FORECAST=$(curl -sf \
"${CARBON_AWARE_API}/emissions/forecasts/current?locations=${LOCATION}&windowSize=60")
OPTIMAL=$(echo "$FORECAST" | python3 -c "
import json, sys
data = json.load(sys.stdin)
pts = data[0].get('optimalDataPoints', []) if data else []
print(pts[0]['timestamp'] if pts else 'N/A')
")
echo "optimal_time=$OPTIMAL" >> $GITHUB_OUTPUT
echo "Finestra ottimale: $OPTIMAL"
fi
- name: Summary Carbon Check
run: |
echo "### Carbon Intensity Check" >> $GITHUB_STEP_SUMMARY
echo "| Parametro | Valore |" >> $GITHUB_STEP_SUMMARY
echo "|-----------|--------|" >> $GITHUB_STEP_SUMMARY
echo "| Location | ${LOCATION} |" >> $GITHUB_STEP_SUMMARY
echo "| Intensity | ${{ steps.check.outputs.intensity }} gCO2/kWh |" >> $GITHUB_STEP_SUMMARY
echo "| Max Threshold | ${MAX_INTENSITY} gCO2/kWh |" >> $GITHUB_STEP_SUMMARY
echo "| Will Run | ${{ steps.check.outputs.should_run }} |" >> $GITHUB_STEP_SUMMARY
# Job 2: Build e test (condizionale alla carbon intensity)
build-and-test:
name: Build and Test
runs-on: ubuntu-latest
needs: carbon-check
if: needs.carbon-check.outputs.should_run == 'true'
steps:
- uses: actions/checkout@v4
- name: Carbon Intensity Badge
run: |
echo "Esecuzione con carbon intensity: ${{ needs.carbon-check.outputs.intensity }} gCO2/kWh"
- 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
- name: Test
run: npm test -- --coverage
- name: Upload coverage
uses: codecov/codecov-action@v4
Studium przypadku: Szkolenie ML z redukcją emisji CO₂ o 35%.
Przedstawiamy realistyczne studium przypadku oparte na wzorcach udokumentowanych w literaturze akademickiej (Springer Nature, 2024) oraz z wdrożeń w przedsiębiorstwach za pomocą pakietu SDK Carbon Aware.
Scenariusz: Proces szkolenia w zakresie modelu rekomendacji handlu elektronicznego
| Parametr | Wartość |
|---|---|
| Agencja | Średniej wielkości e-commerce (500 tys. aktywnych klientów) |
| Obciążenia | Cotygodniowe szkolenie modelu rekomendacji |
| Czas trwania pracy | 3-4 godziny (w zależności od rozmiaru zbioru danych) |
| Sprzęt komputerowy | 4x NVIDIA A100 (maszyna wirtualna w chmurze), łącznie ~300 W |
| Zużycie energii na bieg | ~1,2 kWh |
| Region chmur | Azure Westeurope (Amsterdam) |
| Częstotliwość | Raz w tygodniu (w niedzielę wieczorem) |
| Elastyczność czasowa | 12 godzin (od niedzieli 20:00 do poniedziałku 8:00) |
Wyniki przed i po przyjęciu zestawu SDK Carbon Aware
Porównanie emisji: przed i po (w ujęciu rocznym)
| Metryczny | Przed (stały harmonogram w niedzielę o 22:00) | Po (przesunięcie czasu uwzględniające emisję dwutlenku węgla) | Zmiana |
|---|---|---|---|
| Średnia intensywność emisji dwutlenku węgla w momencie realizacji | 265 gCO₂/kWh | 172 gCO₂/kWh | -35% |
| Emisje na pojedynczy przebieg | 318 gCO₂ | 206 gCO₂ | -112g (-35%) |
| Roczne emisje (52 przebiegi) | 16,5 kgCO₂ | 10,7 kgCO₂ | -5,8kg (-35%) |
| Średnia zmiana czasu | 0 godzin | 6,2 godziny | Nie dotyczy |
| Zlecenia wykonane w oknie 12-godzinnym | 100% | 100% | Niezmienione |
| Koszt obliczeniowy | Niezmienione | Niezmienione | 0% |
Wynik -35% emisji CO₂ uzyskano to po prostu przesunięcie czasu realizacji w ramach już dostępnego okna 12-godzinnego. Żadnych zmian w kodzie szkoleniowym, żadnych dodatkowych kosztów, żadnego zmniejszenia funkcjonalności. Na tym polega siła obliczeń uwzględniających emisję dwutlenku węgla: korzyści dla środowiska mają swoją cenę działanie w pobliżu zera w przypadku obciążeń elastycznych czasowo.
Studium przypadku 2: Wdrożenie w wielu regionach uwzględniające emisję dwutlenku węgla
Drugi scenariusz dotyczy zmiana lokalizacji dla potoków przetwarzania danych które są uruchamiane w cotygodniowych partiach na globalnie rozproszonym zestawie danych.
Porównanie zmiany lokalizacji: emisje według regionu (praca 4 godziny, 500 W)
| Region | Średnia intensywność emisji dwutlenku węgla | Emisje na przebieg (gCO₂) | kontra Najlepszy |
|---|---|---|---|
| Azja-Wschód1 (Tajwan) | 545 gCO₂/kWh | 1090 gCO₂ | +11850% (najgorszy przypadek) |
| us-east-1 (Wirginia) | 380 gCO₂/kWh | 760 gCO₂ | +8160% |
| eu-west-1 (Irlandia) | 280 gCO₂/kWh | 560 gCO₂ | +5933% |
| szwecjacentralna (Lazur) | 18 gCO₂/kWh | 36 gCO₂ | +289% |
| Europa-Północ1 (Finlandia) | 9 gCO₂/kWh | 9,2 gCO₂ | NAJLEPSZE (-99% w porównaniu z Tajwanem) |
Lokalizacja przesuwa się w kierunku europe-north1 w miejscu asia-east1
zmniejsza emisję ponad 99% dla dokładnie tego samego obciążenia.
Oczywiście wykonalność zależy od miejsca przechowywania danych (waga danych) i wymagań
przepisy dotyczące opóźnień i przechowywania danych (RODO itp.). Ale dla obciążeń
analizy zbywalnych zbiorów danych, jest to najpotężniejsza dostępna dźwignia.
Porównanie z API Krajowych Operatorów Sieci
Oprócz dostawców natywnie obsługiwanych przez pakiet SDK Carbon Aware istnieją publiczne interfejsy API platformy operatorzy sieci krajowych, którzy dostarczają dane dotyczące intensywności emisji dwutlenku węgla lub koszyka energetycznego czasie rzeczywistym. Są one przydatne dla tych, którzy chcą danych hiperlokalnych lub nie chcą na nich polegać od dostawców komercyjnych.
Publiczni operatorzy sieci API dla intensywności emisji dwutlenku węgla
| Operator | Wieś | Pszczoła | Dostępne dane | Notatki |
|---|---|---|---|---|
| Terna | Włochy | przezroczystość.terna.it | Miks energetyczny RT, prognoza D+1 | Bezpłatnie, REST JSON |
| RTE | Francja | data.rte-france.com | Miks energetyczny, emisja CO₂, ceny | OAuth2, bezpłatny |
| KAISO | Kalifornia (USA) | caiso.com/awe | Udział energii odnawialnej, intensywność emisji dwutlenku węgla | Bezpłatny, XML/JSON |
| Krajowa sieć ESO | UK | carbonintensity.org.uk | Regionalna intensywność emisji dwutlenku węgla w Wielkiej Brytanii | Bezpłatnie, REST JSON, prognoza |
| ENTSO-E | Europa | przezroczystość.entsoe.eu | Miks energetyczny ponad 40 krajów UE | Rejestracja, SFTP/API |
Integracja z API Carbon Intensity UK (National Grid ESO)
Brytyjska National Grid ESO oferuje szczególnie dobrze udokumentowany, bezpłatny publiczny interfejs API, z danymi regionalnymi, prognozami i koszykiem energetycznym według obszaru. To świetny punkt wyjścia dla tych, którzy chcą eksperymentować bez kosztów.
"""
uk_carbon_intensity.py
Integrazione con API National Grid ESO (UK) per carbon intensity.
Alternativa gratuita per workload in regioni UK.
"""
import requests
from datetime import datetime, timezone
class UKCarbonIntensityClient:
"""
Client per l'API pubblica National Grid ESO.
Documentazione: https://carbon-intensity.github.io/api-definitions/
"""
BASE_URL = "https://api.carbonintensity.org.uk"
def get_current_intensity(self) -> dict:
"""Intensità carbonica attuale per UK nazionale."""
response = requests.get(f"{self.BASE_URL}/intensity")
response.raise_for_status()
return response.json()["data"][0]
def get_regional_intensity(self, region_id: int) -> dict:
"""
Intensità per regione UK specifica.
Region IDs: 1=North Scotland, 6=Yorkshire, 13=South East, ecc.
"""
response = requests.get(f"{self.BASE_URL}/regional/regionid/{region_id}")
response.raise_for_status()
return response.json()["data"][0]
def get_forecast_48h(self) -> list[dict]:
"""Forecast 48 ore per UK nazionale."""
response = requests.get(f"{self.BASE_URL}/intensity/date")
response.raise_for_status()
return response.json()["data"]
def find_optimal_window(self, duration_hours: int = 2) -> dict:
"""
Trova la finestra a più bassa carbon intensity
nelle prossime 48 ore.
"""
forecast = self.get_forecast_48h()
# Calcola media su finestra scorrevole
best_window_start = None
best_avg_intensity = float("inf")
slots_per_window = duration_hours * 2 # Dati ogni 30 min
for i in range(len(forecast) - slots_per_window + 1):
window = forecast[i : i + slots_per_window]
intensities = [
slot["intensity"]["actual"] or slot["intensity"]["forecast"]
for slot in window
if (slot["intensity"]["actual"] or slot["intensity"]["forecast"]) is not None
]
if not intensities:
continue
avg_intensity = sum(intensities) / len(intensities)
if avg_intensity < best_avg_intensity:
best_avg_intensity = avg_intensity
best_window_start = window[0]["from"]
return {
"optimal_start": best_window_start,
"avg_intensity_gco2_kwh": round(best_avg_intensity, 1),
"duration_hours": duration_hours
}
# Utilizzo:
client = UKCarbonIntensityClient()
# Intensità corrente
current = client.get_current_intensity()
print(f"Carbon intensity UK ora: {current['intensity']['actual']} gCO2/kWh")
print(f"Indice: {current['intensity']['index']}") # very low / low / moderate / high / very high
# Finestra ottimale per job da 2 ore
optimal = client.find_optimal_window(duration_hours=2)
print(f"Finestra ottimale: {optimal['optimal_start']}")
print(f"Intensità media: {optimal['avg_intensity_gco2_kwh']} gCO2/kWh")
Ograniczenia i kompromisy: opóźnienie a trwałość
Obliczenia uwzględniające emisję dwutlenku węgla nie są rozwiązaniem bezkompromisowym. To fundamentalne zrozumieć praktyczne ograniczenia i kompromisy, aby nie tworzyć więcej problemów, niż rozwiązuje.
Ograniczenia, które należy wziąć pod uwagę przed adopcją
-
Waga danych: Przenoszenie komputera jest łatwe, ale Twoje dane już tak
być zbyt nieporęczne, aby je przenieść. Zadanie, które uzyskuje dostęp do 10 TB danych
us-east-1raczej nie warto go w to wciągaćeu-north-1jeśli transfer emituje więcej CO₂ niż osiągnięte oszczędności. - Dokładność prognozy: Prognozy intensywności emisji dwutlenku węgla na 24-72 godziny mają margines błędu, który zwiększa się wraz z horyzontem czasowym. Warunki pogodowe (wiatr, słońce) są trudne do przewidzenia w czasie dłuższym niż 6-12 godzin z dużą dokładnością.
- Opóźnienie a trwałość: Przesunięcie czasu wprowadza nieodłączne opóźnienie w wynikach. Jeśli zespół analityki danych czeka na wyniki zadania szkoleniowego, opóźnienie o 8 godzin wiąże się z realnymi kosztami produkcji.
- Paradoks marginalny i średni: Jak wykazały najnowsze badania, optymalizacja pod kątem sygnału marginalnego i sygnału średniego może prowadzić do przeciwnych decyzji. Wybór niewłaściwego sygnału dla danego przypadku użycia może nawet zwiększyć emisję.
- Efekt odbicia: Jeśli komputery świadome emisji dwutlenku węgla zachęcają do konsumpcji więcej obliczeń w „zielonych” godzinach, efekt netto może być zerowy lub ujemny (Paradoks Jevonsa zastosowany do oprogramowania).
- Zasięg geograficzny: WattTime i ElectricityMaps mają zasięg doskonała w USA i UE, ale ograniczona w wielu krajach wschodzących, w których znajdują się centra danych szybko rosną.
Matryca wykonalności według rodzaju obciążenia
Zastosowanie obliczeń uwzględniających emisję dwutlenku węgla według rodzaju obciążenia
| Typ obciążenia | Przesunięcie czasu | Zmiana lokalizacji | Motywacja |
|---|---|---|---|
| Szkolenie modeli ML | Optymalny | Optymalny | Wysoka elastyczność, wysoka energochłonność |
| Potok wsadowy ETL | Optymalny | Dobry | Harmonogram już obecny, zależy od lokalizacji danych |
| Tworzenie kopii zapasowych i replikacja danych | Optymalny | Ograniczony | Elastyczne czasowo, ale już zaplanowane tworzenie kopii zapasowych obejmujących wiele regionów |
| Nieblokujące potoki CI/CD | Dobry | Dobry | Zależy to od zaakceptowanego przez zespół progu oczekiwania |
| Generowanie raportu | Optymalny | Ograniczony | Wysoka elastyczność czasowa, zależna od opóźnienia SLA |
| Serwowanie API (w czasie rzeczywistym) | Nie dotyczy | Ograniczony | Opóźnienie SLA nie pozwala na przesunięcie czasowe |
| Baza transakcyjna | Nie dotyczy | Ograniczony | Spójność i opóźnienie uniemożliwiają ruch |
| Przesyłanie strumieniowe w czasie rzeczywistym (Kafka) | Nie dotyczy | Nie dotyczy | Ciągły, wrażliwy na opóźnienia, stanowy |
Najlepsze praktyki dotyczące wdrażania pakietu SDK Carbon Aware
Lista kontrolna wdrożenia rozwiązań informatycznych uwzględniających emisję dwutlenku węgla
- Identyfikuj obciążenia elastyczne czasowo po pierwsze: zadania wsadowe katalogu z akceptowalnym oknem elastyczności. Zacznij od zadań o najwyższej wydajności zużycie energii i maksymalna elastyczność czasowa.
- Wybierz odpowiedniego dostawcę dla swojego przypadku użycia: WattTime na optymalizację rzeczywistych emisji krańcowych; ElectricityMaps do raportowania rynkowego ESG i zakresu 2.
- Zdefiniuj jasne umowy SLA: Określa maksymalne dopuszczalne okno oczekiwania dla każdego rodzaju pracy. Szkolenie ML może poczekać 12 godzin; raport wykonawczy musi być gotowy przed 9:00.
- Wdrażaj eleganckie rozwiązania awaryjne: Jeśli zestaw SDK Carbon Aware jest nieosiągalny lub prognoza nie jest dostępna, uruchom zadanie normalnie. Nigdy nie blokuj produkcja mająca na celu optymalizację środowiskową.
- Pomiar i raport: Zintegruj wskaźniki intensywności emisji dwutlenku węgla z pulpitami nawigacyjnymi operacyjny. Zaprezentuj zgromadzone oszczędności, aby zmotywować zespół i wykazać zwrot z inwestycji w zakresie ESG.
- Połącz zmianę czasu i lokalizacji: maksymalizować oszczędności, znajdź optymalną kombinację momentu i obszaru, nie optymalizuj dwóch wymiarów osobno.
- Zachowaj ostrożność podczas przesyłania danych: Uwzględnia emisje transferowe dane w obliczeniach całkowitego przesunięcia lokalizacji. Czasem najlepszy „zielony” region nie jest to wygodne ze względu na koszt transportu danych.
- Wdróż pakiet SDK Carbon Aware jako usługę pomocniczą lub usługę współdzieloną: nie każdy zespół musi skonfigurować własne wdrożenie. Scentralizowana instancja z buforowanie zmniejsza koszty i upraszcza zarządzanie.
TypeScript: narzędzie obsługujące technologię Carbon-Aware dla Next.js i Node.js
// carbon-aware.utils.ts
// Utility TypeScript per integrazione Carbon Aware SDK
// Usabile in Next.js (SSR/cron), Node.js, Deno
interface EmissionsData {
location: string;
timestamp: string;
duration: number;
rating: number;
}
interface OptimalWindow {
optimalTimestamp: string;
rating: number;
currentRating: number;
savingsPercentage: number;
}
export class CarbonAwareUtils {
private readonly baseUrl: string;
constructor(baseUrl: string = process.env.CARBON_AWARE_API_URL ?? 'http://localhost:8080') {
this.baseUrl = baseUrl;
}
/**
* Controlla se la carbon intensity corrente è sotto la soglia.
* Usabile come guard per job schedulati.
*/
async isCarbonIntensityAcceptable(
location: string,
maxIntensityGco2: number
): Promise<{ acceptable: boolean; current: number }> {
try {
const url = new URL(`${this.baseUrl}/emissions/bylocations`);
url.searchParams.append('locations', location);
const response = await fetch(url.toString(), {
signal: AbortSignal.timeout(10_000)
});
if (!response.ok) {
// Fail open: se l'API è down, procedi comunque
console.warn(`Carbon Aware API error: ${response.status}. Proceeding anyway.`);
return { acceptable: true, current: -1 };
}
const data: EmissionsData[] = await response.json();
const current = data[0]?.rating ?? 0;
return {
acceptable: current <= maxIntensityGco2,
current
};
} catch (error) {
// Timeout o rete non disponibile: fail open
console.warn('Carbon Aware API unreachable. Proceeding with job.', error);
return { acceptable: true, current: -1 };
}
}
/**
* Trova la migliore regione cloud per eseguire un job ora.
*/
async getBestRegion(locations: string[]): Promise<EmissionsData> {
const url = new URL(`${this.baseUrl}/emissions/bylocation/best`);
locations.forEach(loc => url.searchParams.append('locations', loc));
const response = await fetch(url.toString());
if (!response.ok) {
throw new Error(`Carbon Aware API error: ${response.status}`);
}
return response.json() as Promise<EmissionsData>;
}
/**
* Trova la finestra temporale ottimale per le prossime N ore.
*/
async getOptimalWindow(
location: string,
jobDurationMinutes: number,
searchHours: number = 24
): Promise<OptimalWindow | null> {
const now = new Date();
const searchEnd = new Date(now.getTime() + searchHours * 60 * 60 * 1000);
const url = new URL(`${this.baseUrl}/emissions/forecasts/current`);
url.searchParams.append('locations', location);
url.searchParams.append('dataStartAt', now.toISOString());
url.searchParams.append('dataEndAt', searchEnd.toISOString());
url.searchParams.append('windowSize', jobDurationMinutes.toString());
const response = await fetch(url.toString());
if (!response.ok) return null;
const forecasts = await response.json() as Array<{
optimalDataPoints: Array<{ timestamp: string; rating: number }>;
forecastData: Array<{ timestamp: string; rating: number }>;
}>;
const forecast = forecasts[0];
if (!forecast?.optimalDataPoints?.length) return null;
const optimal = forecast.optimalDataPoints[0];
const currentRating = forecast.forecastData[0]?.rating ?? 0;
const savingsPercentage = currentRating > 0
? Math.round(((currentRating - optimal.rating) / currentRating) * 100)
: 0;
return {
optimalTimestamp: optimal.timestamp,
rating: optimal.rating,
currentRating,
savingsPercentage
};
}
}
// Decorator per funzioni che supportano carbon-aware execution
export function carbonAware(
location: string,
maxIntensity: number = 300
) {
return function (
_target: object,
_propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value;
descriptor.value = async function (...args: unknown[]) {
const client = new CarbonAwareUtils();
const { acceptable, current } = await client.isCarbonIntensityAcceptable(location, maxIntensity);
if (!acceptable) {
console.log(`Job skipped: carbon intensity ${current} gCO2/kWh > threshold ${maxIntensity}`);
return null;
}
console.log(`Job starting: carbon intensity ${current} gCO2/kWh (threshold: ${maxIntensity})`);
return originalMethod.apply(this, args);
};
return descriptor;
};
}
Wnioski: Obliczenia uwzględniające emisję dwutlenku węgla jako standard operacyjny
Zestaw SDK Carbon Aware stanowi obecnie najbardziej dojrzałe i najlepiej obsługiwane narzędzie wdrożyć przesunięcie popytu w systemach oprogramowania dla przedsiębiorstw. Z API Standardowy REST, obsługa głównych dostawców danych (WattTime, ElectricityMaps) i gotowe integracje dla Kubernetes/KEDA i GitHub Actions, znacząco obniża bariera techniczna w przyjęciu zielonej inżynierii oprogramowania.
Wyniki są konkretne i udokumentowane: redukcja emisji o 20–45% w przypadku obciążeń roboczych wsadowo z przesunięciem w czasie, do 99% z przesunięciem lokalizacji do regionów europejskich zasilanych przez hydroelektryczne i nuklearne. A najważniejsze jest to, że te oszczędności mają koszty eksploatacji bliskie zeru: nie wymagają innego sprzętu, nie zmniejszają funkcji aplikacji, nie zwiększają kosztów chmury.
Kierunek jest jasny: główni dostawcy usług w chmurze integrują metryki intensywność emisji dwutlenku węgla w swoich konsolach (AWS Customer Carbon Footprint Tool, Google Cloud Carbon Ślad, pulpit nawigacyjny wpływu emisji platformy Azure). Obliczenia uwzględniające emisję dwutlenku węgla to dziś wyróżnik zaawansowane, w nadchodzących latach staną się standardowym wymogiem dla organizacji podlega regulacjom ESG i CSRD. Rozpoczęcie pracy teraz oznacza zdobycie umiejętności i infrastruktury z góry.
Następne kroki
-
Poznaj oficjalne repozytorium su
github.com/Green-Software-Foundation/carbon-aware-sdki rozpocznij Kontener Docker lokalnie z konfiguracją JSON do wykonania pierwszych eksperymentów bez klucza API. - Przeczytaj poprzedni artykuł z tej serii: Climatiq API: Intensywność emisji dwutlenku węgla nie Systemy chmurowe, aby zrozumieć, jak mierzyć emisje przed ich optymalizacją.
- Kontynuuj z Zakres 3 i rurociąg ESG zintegrować dane dotyczące emisji dwutlenku węgla intensywność raportowania zrównoważonego rozwoju przedsiębiorstw wymaganego przez CSRD.
- Jeśli pracujesz z modelami ML, kop głębiej Ślad węglowy AI gdzie zobaczmy, jak zoptymalizować cały cykl uczenia + wnioskowania poprzez techniki połączenie planowania uwzględniającego emisję dwutlenku węgla i wydajności modelu.
- Zarejestruj się Fundacja Zielonego Oprogramowania jako darmowy zwolennik uzyskaj dostęp do społeczności, seminariów technicznych i programu certyfikacji „Praktyk zielonego oprogramowania”.
Zasoby i referencje
- Carbon Aware SDK — Fundacja ekologicznego oprogramowania:
carbon-aware-sdk.greensoftware.foundation - Repozytorium GitHuba:
github.com/Green-Software-Foundation/carbon-aware-sdk - Operator KEDA Azure świadomy emisji dwutlenku węgla:
github.com/Azure/carbon-aware-keda-operator - Dokumentacja API WattTime:
docs.watttime.org - Interfejs API ElectricityMaps:
portal.electricitymaps.com/developer-hub/api - API intensywności emisji dwutlenku węgla w Wielkiej Brytanii:
carbonintensity.org.uk - Springer 2024: „Strategie zmiany czasu w zakresie niskoemisyjnych szkoleń LLM”
- Zrównoważony rozwój MDPI 2025: „Przestrzenno-czasowe obciążenie pracą uwzględniające emisję gazów cieplarnianych w chmurze brzegowej”
- Specyfikacja Green Software Foundation SCI: ISO/IEC 21031:2024







