Carbon Aware SDK: 時間と空間を超えてワークロードを移動
ICT 部門は約 年間460~700TWhの電力量 グローバルデータセンターでは、 AI ワークロードの爆発的な増加により、この数字は 2030 年までに 2 倍になると見込まれています。しかし、これがすべてではありません エネルギーは同じです。グリッドの 90% が電力で供給されている場合、一度に 1 キロワット時が生産されます。 再生可能エネルギーによる CO₂ 排出量は、需要のピーク時に発生したものと比較して 10 分の 1 です。 ガス発電所によってカバーされています。同じロジックが宇宙にも当てはまります。ヨーロッパのクラウド リージョンでジョブを実行します。 クリーンなエネルギーの組み合わせにより、電力供給地域よりも 4 倍環境に優しい地域になる可能性があります 主に石炭から。
このコンセプト — ソフトウェアを実行する いつ e どこ 電気は クリーナー — それは呼ばれます 需要の変化 o カーボンアウェアコンピューティング、 これはグリーン ソフトウェア エンジニアリングの基本原則の 1 つです。そこには グリーン ソフトウェア財団 は、この原則に基づいてリファレンス オープン ソース ツールキットを構築しました。 カーボンアウェア SDK、 MIT ライセンスに基づいて GitHub で入手でき、UBS、Vestas、Microsoft 自体などの企業によって運用環境で使用されています。
この記事では、Carbon Aware SDK が内部からどのように機能するか、アーキテクチャ、データ ソース、 REST API、Python、Kubernetes、GitHub Actions との統合。具体的な例を構築していきます タイムシフト (炭素強度が低下したら、ジョブを 6 ~ 8 時間移動します) e 場所の移動 (ワークロードを最も環境に優しいクラウド リージョンにルーティングします リアルタイムで)。
何を学ぶか
- Carbon Aware SDK の仕組み: WebAPI アーキテクチャ、CLI、クライアント ライブラリ
- 炭素強度データ ソース: WattTime、ElectricityMaps、Ember Climate
- タイムシフト: 低炭素強度ウィンドウでのバッチ ジョブのスケジュール設定
- ロケーションシフト: 最もクリーンなエネルギーミックスを持つクラウド地域を選択します
- Python と Carbon Aware SDK クライアントの統合
- Kubernetes + KEDA: アプリケーション コードを変更せずにカーボンを考慮した自動スケーリングを実現
- GitHub Actions: インテリジェントなスケジューリングを備えたカーボン対応 CI/CD パイプライン
- 限界炭素強度と平均炭素強度: どのシグナルを使用するか、およびその理由
- 限界、トレードオフ、および測定可能な排出量削減を実現する実際の使用例
グリーン ソフトウェア エンジニアリング シリーズ — すべての記事
| # | タイトル | 主題 |
|---|---|---|
| 1 | グリーン ソフトウェア エンジニアリングの原則 | 8 GSF 原則、SCI 仕様 ISO/IEC 21031 |
| 2 | CodeCarbon による排出量の測定 | Python、MLflow、ダッシュボードでの CO₂ 追跡 |
| 3 | Climatiq API: クラウド システムの炭素強度 | REST API、クラウド排出量計算およびサプライチェーン |
| 4 | Carbon Aware SDK (この記事) | タイムシフト、ロケーションシフト、Kubernetes、CI/CD |
| 5 | スコープ3とESGパイプライン | 上流/下流排出量、CSRD データ パイプライン |
| 6 | スコープ1、2、3のモデリング | GHG プロトコル会計フレームワーク、SBTi |
| 7 | AI の二酸化炭素排出量 | LLM トレーニング、推論、エネルギー最適化 |
| 8 | 持続可能なソフトウェア パターン | 環境に優しいパターン設計、効率的なアーキテクチャ |
| 9 | ソフトウェアのESGとCSRD | 規制遵守、EU報告義務 |
| 10 | GreenOps: 持続可能な運営 | FinOps+GreenOps、運用指標、文化の変化 |
需要の変化: カーボンアウェア コンピューティングの背後にある原理
キロワット時の電力の炭素強度は一定ではなく、時間ごとに変化します。 送電網上で利用可能な再生可能エネルギーの量に基づきます。ドイツの晴れた日、 炭素強度は i を下回る可能性があります 100 gCO₂/kWh 時間内に 発電所。太陽光発電が停止され、産業需要が減少する夜は、上空に昇ります。 400 gCO₂/kWh。同じ変動が宇宙にも存在します: スウェーデン、給餌 ほぼすべてが水力発電と原子力によるもので、15 ~ 40 gCO₂/kWh の間で変化しますが、ポーランドでは、 依然として石炭に依存しており、多くの場合 700 gCO₂/kWh を超えます。
Il 需要の変化 柔軟なワークロードを移動することでこの変動性を利用します — バッチ処理、ML トレーニング、バックアップ、データ分析、CI/CD テスト — 必要な瞬間と領域に向けて 電気はよりクリーンになります。それは計算を放棄することではありません。 最適な時間を選択してください それを実行します。
需要の変化の種類
| タイプ | 説明 | 実践例 | 一般的な CO₂ 削減 |
|---|---|---|---|
| タイムシフト | ワークロードを同じ地域内の低炭素強度の時間枠に移動します。 | ML トレーニングを午後 2 時ではなく午前 2 時に実行する | 20~45% |
| 場所の移動 | よりクリーンな電力構成を備えたクラウド リージョンでワークロードを実行します | ジョブを us-east-1 から eu-north-1 (ストックホルム) にルーティングします。 | 30-70% |
| デマンドシェーピング | 炭素強度が高い場合に提供される機能を減らす | ピーク発光中に 4K ビデオ解像度を無効にする | 10~25% |
明らかに、すべてのワークロードが移動可能であるわけではありません。ユーザーの HTTP リクエストは直ちに処理される必要があります。 金融取引は待つことができません。しかし、負荷の割合が驚くほど高い エンタープライズコンピューティングは 時間に柔軟に対応できる: ML モデルのトレーニング、ETL パイプライン 夜間、レポート生成、バックアップ、セキュリティ スキャン、CI/CD パイプライン。これらはまさに、 カーボンを意識したスケジューリングの理想的な候補です。
Carbon Aware SDK: アーキテクチャとコンポーネント
Il カーボンアウェア SDK は Green Software Foundation のオープンソース プロジェクトです。
MIT ライセンスに基づいてリリースされ、現在は状態にあります 卒業プロジェクト (最高レベル
GSF ポートフォリオの成熟度)。リポジトリは GitHub にあります。
Green-Software-Foundation/carbon-aware-sdk 主に C# で開発されており、
ASP.NET Core WebAPI ですが、任意の言語で使用できるインターフェイスを公開しています。
このアーキテクチャはモジュール式であり、次の重要な概念を中心に展開します。 単一のインターフェース 正規化された さまざまなプロバイダーの炭素強度データにアクセスし、常に出力を得ることができます。 gCO₂eq/kWh ソースに関係なく。これは非常に重要です。 各プロバイダーは、異なる単位、地理的粒度、および計算方法を使用します。
Carbon Aware SDK のコンポーネント
| 成分 | テクノロジー | 使用 | いつ使用するか |
|---|---|---|---|
| WebAPI | ASP.NETコア(C#)、Docker | Swagger/OpenAPI を使用した REST API、マイクロサービスとして展開可能 | あらゆるスタック、マイクロサービス アーキテクチャとの統合 |
| CLI | ドットネット ツール、クロスプラットフォーム | ターミナル クエリ、bash/PowerShell スクリプト | デプロイメントスクリプト、DevOps自動化、迅速なテスト |
| SDKライブラリ | NuGet パッケージ (C#) | アプリケーションの .NET コードへの直接統合 | 組み込みのカーボン対応ロジックが必要な .NET アプリケーション |
| Pythonクライアント | openapi-generator、Python 3.8+ | WebAPIのOpenAPI仕様から自動生成されるクライアント | ML パイプライン、データ エンジニアリング スクリプト、Airflow DAG |
典型的なフローは次のとおりです。 DockerコンテナとしてのWebAPI、 炭素強度プロバイダーの資格情報を使用して構成され、エンドポイントが照会されます スケジュール アプリケーションからの REST。 WebAPI はデータの正規化と呼び出しの管理を行います。 プロバイダーに送信し、最適な時間枠または地域を返します。
データソース: WattTime、ElectricityMaps、Ember Climate
Carbon Aware SDK は、それぞれ独自の機能を持つ複数の炭素強度データプロバイダーをサポートします。 地理的範囲も異なります。プロバイダーの選択には重要かつ方法論的な意味があります (限界対平均) かつ実用的 (カバー範囲、コスト、更新頻度)。
炭素強度プロバイダーの比較
| プロバイダー | 信号の種類 | カバレッジ | 頻度 | 予報 | 料金 |
|---|---|---|---|---|---|
| ワットタイム | マージナル (MOER) | アメリカ全土、50か国以上 | 5分 | 24~72時間 | 限定された無料プラン、商用プラン |
| 電気地図 | 平均 (LCA) | 85 か国以上、完全な EU | 毎時/毎時 | 24時間 | 開発者無料、商用プラン |
| 残り火気候 | 過去の平均 | グローバル (50 か国以上) | 日次/履歴 | いいえ (履歴のみ) | オープンデータ、無料 |
| 静的JSON | 設定可能 | カスタム | マニュアル | No | 無料(開発/テスト) |
限界炭素強度と平均炭素強度: 重要な違い
信号の選択 限界的な e 中くらい それが違いです カーボンアウェアコンピューティングの最も重要な方法論的アプローチであり、結果に具体的な影響を与えます。 入手可能。
信号 限界 (MOER — 限界運転排出率)ワットタイム提供、 質問に答えます: 「今、消費量を 1 kWh 増やしたら、どの発電所で その追加需要をカバーするために稼働したのですか?」。答えはほとんどの場合、 再生可能エネルギーではなく、最も柔軟なガス発電所 (いわゆる「限界発電機」) それらはすでに最大でオンになっています。このシグナルは意思決定に最も関係します 排出量を削減したい人々のリアルタイム 引き起こされた その増分消費から。
信号 平均 (LCA — ライフサイクル平均)、ElectricityMaps によって提供され、応答します 代わりに質問に: 「生産されたすべての電力の平均排出量はいくらですか 今ネットワーク上ですか?」。太陽光が 50%、ガスが 50% のグリッドでは、平均信号は次のようになります。 エッジで何が起こっても、最大 250 gCO₂/kWh。この信号はさらに ESGレポートや市場ベースのスコープ2会計に適しています。
重要: 2025 年における ElectricalMaps のアプローチの変更
2025 年に、ElectricityMaps は マージナル信号が不連続である APIから データの検証可能性とEU規制との整合性に関する懸念を理由に挙げた 米国はスコープ 2 の会計における限界要因の使用を禁止しています。電気地図 現在は、LCA (ライフ サイクル アセスメント) 手法に基づいた平均信号のみを提供しています。もし あなたのユースケースでは限界信号が必要ですが、 WattTime は依然として唯一の主流プロバイダーです それをサポートするものです。 Carbon Aware SDK は、この違いを透過的に処理します。 プロバイダーの構成。
いつどの信号を使用するか
| 使用事例 | 推奨信号 | プロバイダー | なぜ |
|---|---|---|---|
| タイムシフトバッチジョブ | 限界 | ワットタイム | 引き起こされる排出量の実質的な削減を最大化します |
| 複数地域のロケーションの移行 | 中程度または限界的 | 電力マップまたはワットタイム | どちらも便利です。より安定した領域間平均 |
| ESG / スコープ 2 レポート | 中くらい | 電気地図 | GHG プロトコルおよび EU/米国の規制で必須 |
| 開発とテスト | 静的JSON | ローカルファイル | コストがかからない、テスト用の確定的なデータ |
Carbon Aware SDK のセットアップと構成
Carbon Aware SDK をローカルで起動する最も速い方法は、Docker Compose を使用することです。 ElectricalMaps をプロバイダーとして使用した完全な構成を見てみましょう (最も簡単な構成) 開発者向けの無料 API キーのおかげで設定できます)。
# 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
開発およびテスト環境では、静的 JSON データ ソースを使用できます。 APIキー。 SDK には、プリロードされたサンプル データセットが含まれています。
# 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"
}
}
}
コンテナーが開始されると、Swagger のドキュメントが次の場所から入手できます。
http://localhost:8080/swagger。すべてが 1 つで動作することを確認しましょう
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
API エンドポイント: Carbon Aware SDK の中心
WebAPI は、OpenAPI/Swagger で文書化された REST エンドポイントのセットを公開します。主なものを見てみましょう 実際の通話の例と応答の解釈を示します。
GET /emissions/bylocations — 場所別の現在の排出量
時間間隔内の 1 つ以上の場所の炭素強度データを返します。 異なる地域間で現在の炭素強度を比較するために使用されます (場所の移動)。
# 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 — 最適な場所
範囲内で炭素強度が最も低い位置を直接返します。 指定されています。ワークロードの自動ルーティングに最適です。
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 /emissions/forecasts/current — 炭素強度の予測
これは、 タイムシフト: 戻ります ワークロードを実行する最適な時間枠を含む今後 24 ~ 72 時間の予測。 ジョブの期間を指定して、その長さの最適なウィンドウを見つけることができます。
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 — 複数の予測
予測リクエストのバッチを送信して、複数のジョブを同時にスケジュールできます。 依存関係のある複雑なパイプラインをスケジュールするのに役立ちます。
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
}
]
Python の統合: CarbonAwareClient とタイムシフト
Python パイプラインの場合 (通常はデータ エンジニアリング、ML トレーニング、またはバッチ分析) Carbon Aware SDK は、OpenAPI 仕様によって自動的に生成された Python クライアントを提供します。 ML トレーニング ジョブ用の完全なタイムシフト システムを構築する方法を見てみましょう。
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
Python の Carbon Aware クライアント
"""
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)
}
タイムシフト: 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()
ロケーションシフト: マルチリージョンのカーボン認識ルーティング
多くの場合、場所を移動することは時間を移動するよりも効果的です。
雲領域間の炭素強度は時間的変動よりもはるかに大きくなる可能性があります
単一の地域内で。で実行されたジョブ
europe-north1 (フィンランド、約 9 gCO₂/kWh)
asia-east1 (台湾、約 550 gCO₂/kWh) 排出量を 98% 以上削減します。
雲領域別の平均炭素強度 (2025 年)
| クラウドプロバイダー | 地域 | 位置 | 平均炭素強度 (gCO₂/kWh) | 主要な情報源 |
|---|---|---|---|---|
| グーグルクラウド | ヨーロッパ北部1 | フィンランド | 9 | 水力発電、原子力 |
| アズール | スウェーデン中央 | スウェーデン | 18 | 水力発電、原子力 |
| アズール | 北欧 | アイルランド | 280 | 風力、ガス |
| AWS | EU-西-1 | アイルランド | 320 | 風力、ガス |
| AWS | 米国東部-1 | バージニア州 | 380 | ガス、原子力、石炭 |
| グーグルクラウド | アジア東1 | 台湾 | 545 | 石炭、ガス |
| AWS | ap-南東-1 | シンガポール | 408 | 天然ガス |
Python: バッチジョブの自動ロケーションシフト
"""
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 と KEDA: カーボンを意識した自動スケーリング
運用 Kubernetes 環境向けに、Microsoft は カーボンを意識した KEDA オペレーター (Azure GitHub 上のオープンソース)、 これは、Carbon Aware SDK を KEDA スケーリング レイヤーに直接統合します。 原理は単純です。炭素強度が低い (クリーン エネルギーが利用可能) 場合、 KEDA はレプリカの最大数までスケールアップできます。高い場合は最大数 レプリケーションの数は自動的に削減されます。
アーキテクチャは 3 つの要素で構成されます。 Kubernetes 炭素強度エクスポーター (Carbon Aware SDK からデータを取得し、そこから クラスター内で ConfigMap として公開されます)、 カーボンを意識した KEDA オペレーター (ConfigMap を読み取り、KEDA スケーリング制限を更新します)、およびカスタム リソース CarbonAwareKedaScaler これはスケーリングのしきい値を定義します。
Carbon Intensity Exporter のインストール
# 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
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: カスタム リソース定義
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
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
Carbon Aware KEDA オペレーターが実際にどのように機能するか
Carbon Aware KEDA Operator は 1 時間ごとに、更新された ConfigMap をエクスポーターから読み取り、更新します。
フィールド maxReplicaCount 対応するしきい値に基づいた KEDA ScaledJob の
現在の炭素強度に換算します。キューに 200 個のジョブがあり、炭素強度が
312 gCO₂/kWh (しきい値 300-500)、KEDA は最大 10 人の作業者までスケールアップ可能
強度が 150 を下回るとすぐに (例: 強風の午前 2 時)、
制限は自動的に 20 に増加し、ジョブはより高速に処理されます。
アプリケーション コードを変更する必要はありません。
Init コンテナを使用した Kubernetes CronJob カーボン対応
定期的にスケジュールされたジョブの場合は、別のパターンを使用できます。 コンテナの初期化 ジョブを開始する前に Carbon Aware SDK をクエリします。 そして実行するか延期するかを決定します。このアプローチは KEDA を必要とせず、KEDA と連動します。 標準の Kubernetes CronJob。
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
GitHub アクション: カーボン対応 CI/CD パイプライン
CI/CD のテストおよびビルド パイプラインは、カーボン対応にするのが最も簡単なワークロードの 1 つです。 なぜなら、それらの多くは実際には時間に柔軟に対応できるからです: 統合テスト スイート 特にパイプラインの場合、開発者のワークフローに影響を与えることなく 2 ~ 4 時間待つことができます。 夜または予定。
2024 年の調査では、エコシステム全体における GitHub Actions の二酸化炭素排出量を推定しました の順番です 450-1000 MTCO₂eq/年。二酸化炭素を意識したスケジューリングを使用 最も重いワークフロー (コンパイルを使用したビルド、完全なテスト スイート、トレーニング) であっても のモデル)、削減は大幅になる可能性があります。
カーボンアウェアなスケジューリングを使用したワークフロー GitHub アクション
# .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
ケーススタディ: CO₂ を 35% 削減した ML トレーニング
学術文献に記載されたパターンに基づいた現実的なケーススタディを紹介します。 (Springer Nature、2024)、および Carbon Aware SDK を使用したエンタープライズ実装によるものです。
シナリオ: 電子商取引推奨モデルのトレーニング パイプライン
| パラメータ | 価値 |
|---|---|
| 代理店 | 中規模の電子商取引 (アクティブ顧客 50 万人) |
| ワークロード | レコメンデーションモデルの毎週のトレーニング |
| ジョブ期間 | 3 ~ 4 時間 (データセットのサイズによって異なります) |
| ハードウェア | 4x NVIDIA A100 (クラウド VM)、合計約 300W |
| 1回の実行あたりのエネルギー消費量 | ~1.2kWh |
| クラウド領域 | アジュール ウェストヨーロッパ (アムステルダム) |
| 頻度 | 週に1回(日曜日の夜) |
| 時間的な柔軟性 | 12時間(日曜日午後8時から月曜日午前8時まで) |
Carbon Aware SDK の導入前後の結果
排出量の比較: 前と後 (年間ベース)
| メトリック | 前(固定スケジュール日曜日午後10時) | 後 (カーボン対応タイムシフト) | 変化 |
|---|---|---|---|
| 実行時の平均炭素強度 | 265 gCO₂/kWh | 172 gCO₂/kWh | -35% |
| 1 回の実行あたりの排出量 | 318 gCO₂ | 206 gCO₂ | -112g (-35%) |
| 年間排出量 (52 回の実行) | 16.5kgCO₂ | 10.7kgCO₂ | -5.8kg (-35%) |
| 平均タイムシフト | 0時間 | 6.2時間 | 該当なし |
| 12 時間以内に実行されたジョブ | 100% | 100% | 変わらない |
| 計算コスト | 変わらない | 変わらない | 0% |
の結果 CO₂ 排出量 -35% それは単純に得られたものです すでに利用可能な 12 時間枠内で実行時間を移動します。 トレーニング コードの変更、追加コスト、機能の低下はありません。 これがカーボンアウェア コンピューティングの強みです。環境上の利点には代償が伴います。 時間に柔軟に対応できるワークロードに対してほぼゼロに近い稼働率を実現します。
ケーススタディ 2: カーボンを意識したマルチリージョン展開
2 番目のシナリオは、 場所の移動 データ処理パイプライン用 これらは、グローバルに分散されたデータセットに対して毎週バッチで実行されます。
ロケーションシフトの比較: 地域別の排出量 (4 時間の作業、500W)
| 地域 | 炭素強度の平均 | 実行ごとの排出量 (gCO₂) | 対ベスト |
|---|---|---|---|
| アジアイースト1 (台湾) | 545 gCO₂/kWh | 1090 gCO₂ | +11850% (最悪の場合) |
| us-east-1 (バージニア) | 380 gCO₂/kWh | 760gCO₂ | +8160% |
| eu-west-1 (アイルランド) | 280 gCO₂/kWh | 560gCO₂ | +5933% |
| スウェーデンセントラル (Azure) | 18 gCO₂/kWh | 36gCO₂ | +289% |
| ヨーロッパ北1 (フィンランド) | 9 gCO₂/kWh | 9.2 gCO₂ | BEST (対台湾) -99% |
位置が次の方向に移動します europe-north1 の代わりに asia-east1
排出量を削減する 99%以上 まったく同じワークロードの場合。
明らかに、実現可能性はデータが存在する場所 (データグラビティ) と要件に依存します。
レイテンシとデータの保存に関する規制 (GDPR など)。ただし、ワークロードに関しては
転送可能なデータセットの分析、これは利用可能な最も強力な手段です。
National Grid Operator の API との比較
Carbon Aware SDK によってネイティブにサポートされるプロバイダーに加えて、 炭素強度またはエネルギーミックスデータを提供する全国的なネットワークオペレーター。 リアルタイム。これらは、ハイパーローカライズされたデータが必要な場合、またはデータに依存したくない場合に役立ちます。 商用プロバイダーから。
炭素強度のパブリック API グリッド オペレーター
| オペレーター | Paese | API | 利用可能なデータ | 注意事項 |
|---|---|---|---|---|
| テルナ | イタリア | 透明性.terna.it | RT エネルギーミックス、D+1 予測 | 無料、REST JSON |
| RTE | フランス | data.rte-france.com | エネルギーミックス、CO₂排出量、価格 | OAuth2、無料 |
| カイソ | カリフォルニア (米国) | caiso.com/awe | 再生可能エネルギーの割合、炭素強度 | 無料、XML/JSON |
| ナショナルグリッドESO | UK | 炭素強度.org.uk | 英国の地域炭素強度 | 無料、REST JSON、予測 |
| ENTSO-E | ヨーロッパ | 透明性.entsoe.eu | エネルギーミックス 40 以上の EU 諸国 | 登録、SFTP/API |
Carbon Intensity UK API (National Grid ESO) との統合
英国の National Grid ESO は、特に十分に文書化された無料のパブリック API を提供しています。 地域ごとのデータ、予測、エネルギーミックスを提供します。素晴らしい出発点です コストをかけずに実験したい人向け。
"""
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")
制限とトレードオフ: レイテンシー vs 持続可能性
カーボンを意識したコンピューティングは、妥協のないソリューションではありません。それは基本的なことです 解決する以上の問題を引き起こさないように、実際的な制限とトレードオフを理解してください。
導入前に考慮すべき制限
-
データグラビティ: コンピュータの移動は簡単ですが、データが移動する可能性があります
かさばりすぎて転送できない。 10TBのデータにアクセスするジョブ
us-east-1実行する価値はほとんどありませんeu-north-1移転により達成された節約よりも多くの CO₂ が排出される場合。 - 予測精度: 24 ~ 72 時間の炭素強度予測 誤差の範囲は時間の経過とともに増加します。気象条件 (風、太陽)は、6 ~ 12 時間を超えると高精度に予測することが困難になります。
- レイテンシーと持続可能性: タイムシフトにより固有の遅延が発生します 結果では。データ サイエンス チームがトレーニング ジョブの結果を待っている場合、 8 時間遅らせると実際の生産コストがかかります。
- 限界と平均のパラドックス: 最近の研究で明らかになったように、 限界信号と平均信号の最適化は、逆の決定につながる可能性があります。 ユースケースに合わせて間違った信号を選択すると、エミッションが増加する可能性さえあります。
- リバウンド効果: カーボンアウェアコンピューティングが消費を促進する場合 「グリーン」時間中により多くの計算が行われるため、最終的な効果はゼロまたはマイナスになる可能性があります (ジェボンのパラドックスをソフトウェアに適用)。
- 地理的範囲: WattTime と ElectricalMaps がカバーされています 米国と EU では優れていますが、データセンターが設置されている多くの新興国では制限されています。 彼らは急速に成長しています。
ワークロード タイプ別の実現可能性マトリックス
ワークロード タイプ別のカーボン アウェア コンピューティングの適用可能性
| ワークロードの種類 | タイムシフト | 場所の移動 | モチベーション |
|---|---|---|---|
| ML モデルのトレーニング | 最適 | 最適 | 高い柔軟性、高いエネルギー強度 |
| バッチ ETL パイプライン | 最適 | 良い | スケジューラはすでに存在します。データの局所性に依存します |
| データのバックアップとレプリケーション | 最適 | 限定 | 時間に柔軟に対応できますが、マルチリージョンのバックアップはすでにスケジュールされています |
| ノンブロッキング CI/CD パイプライン | 良い | 良い | チームが受け入れる待機しきい値によって異なります |
| レポートの生成 | 最適 | 限定 | 高い時間的柔軟性、SLA レイテンシに依存 |
| API 提供 (リアルタイム) | 適用できない | 限定 | SLA レイテンシでは時間的なずれは許容されません |
| トランザクションデータベース | 適用できない | 限定 | 一貫性と遅延により移動が妨げられる |
| リアルタイム ストリーミング (Kafka) | 適用できない | 適用できない | 継続的、遅延に敏感、ステートフル |
Carbon Aware SDK を導入するためのベスト プラクティス
カーボン アウェア コンピューティング導入チェックリスト
- 時間に柔軟性のあるワークロードを特定する まず最初に: カタログ バッチ ジョブ 許容可能な柔軟性のウィンドウを備えています。最もパフォーマンスの高いジョブから開始する エネルギー消費量と時間の柔軟性を最大限に高めます。
- ユースケースに適したプロバイダーを選択してください: 最適化のためのワットタイム 実質限界排出量。市場ベースの ESG およびスコープ 2 レポート用の ElectricalMap。
- 明確な SLA を定義する: 許容可能な最大待機ウィンドウを指定します。 あらゆる種類の仕事に。 ML トレーニングは 12 時間かかる場合があります。エグゼクティブレポート 午前9時までに準備ができていなければなりません。
- 正常なフォールバックを実装する: Carbon Aware SDK にアクセスできない場合 または予測が利用できない場合は、ジョブを通常どおり実行します。決してブロックしないでください 環境最適化のための生産。
- 測定と報告: 炭素強度指標をダッシュボードに統合 稼働中。蓄積された節約を披露してチームのモチベーションを高め、ESG ROI を実証します。
- 時間と場所の移動を組み合わせる: 節約を最大限に高めるには、 モーメントと領域の最適な組み合わせを見つけます。2 つの次元を最適化するのではありません。 別に。
- データ移行には注意してください:移動排出量を考慮 総位置シフト計算のデータ。時には最高の「緑の」地域 データ転送にコストがかかるため、不便です。
- Carbon Aware SDK をサイドカーまたは共有サービスとしてデプロイする: 違います 各チームは独自の展開を構成する必要があります。一元化されたインスタンス キャッシュによりコストが削減され、管理が簡素化されます。
TypeScript: Next.js および Node.js 用の Carbon-Aware ユーティリティ
// 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;
};
}
結論: 運用標準としてのカーボンアウェア コンピューティング
Carbon Aware SDK は、現在、最も成熟しており、最もサポートされているツールです。 を実装する 需要の変化 エンタープライズ ソフトウェア システムで。 APIを使用する 標準 REST、主要なデータ プロバイダー (WattTime、ElectricityMaps) のサポート、および Kubernetes/KEDA および GitHub Actions の統合が準備されているため、 グリーン ソフトウェア エンジニアリングの導入に対する技術的障壁。
結果は具体的で文書化されています: ワークロードの排出量が 20 ~ 45% 削減 タイムシフト付きのバッチ、最大 99% のロケーションをヨーロッパ地域にシフト 水力発電と原子力。そして重要な点は、これらの節約には次のような特徴があるということです。 運用コストがゼロに近い: 異なるハードウェアを必要とせず、コストも削減しません。 アプリケーションの機能を強化しても、クラウドのコストが増加することはありません。
方向性は明確です: 大手クラウドプロバイダーはメトリクスを統合しています コンソールの炭素強度 (AWS Customer Carbon Footprint Tool、Google Cloud Carbon) フットプリント、Azure 排出量影響ダッシュボード)。カーボンを意識したコンピューティング、今日の差別化要因 先進的であり、今後数年間で組織の標準要件となるでしょう ESGおよびCSRD規制の対象となります。今すぐ始めるには、スキルとインフラストラクチャを取得する必要があります 事前に。
次のステップ
-
を探索してください 公式リポジトリ su
github.com/Green-Software-Foundation/carbon-aware-sdkそして、 最初の実験を行うための、JSON 構成を使用したローカルの Docker コンテナー APIキーなし。 - このシリーズの前の記事を読んでください。 Climatiq API: 炭素強度 nei クラウドシステム、排出量を最適化する前に排出量を測定する方法を理解します。
- 続けて スコープ3とESGパイプライン 炭素データを統合する CSRD が要求する企業の持続可能性報告の強化。
- ML モデルを使用する場合は、さらに深く掘り下げてください AI の二酸化炭素排出量 どこで テクニックを通じてトレーニングと推論のサイクル全体を最適化する方法を見てみましょう カーボンを意識したスケジューリングとモデル効率の組み合わせ。
- に登録する グリーン ソフトウェア財団 無料サポーターとして コミュニティ、技術ウェビナー、認定プログラムにアクセスする 「グリーン ソフトウェア プラクティショナー」。
リソースと参考資料
- Carbon Aware SDK — グリーン ソフトウェア財団:
carbon-aware-sdk.greensoftware.foundation - GitHub リポジトリ:
github.com/Green-Software-Foundation/carbon-aware-sdk - Azure Carbon Aware KEDA オペレーター:
github.com/Azure/carbon-aware-keda-operator - WattTime API ドキュメント:
docs.watttime.org - ElectricalMaps API:
portal.electricitymaps.com/developer-hub/api - 英国炭素強度 API:
carbonintensity.org.uk - Springer 2024: 「炭素効率の高い LLM トレーニングのためのタイムシフト戦略」
- MDPI サステナビリティ 2025: 「エッジクラウドにおける炭素を意識した時空間ワークロードの移行」
- グリーン ソフトウェア財団 SCI 仕様: ISO/IEC 21031:2024







