Carbon Aware SDK: 시간과 공간에 걸쳐 워크로드 이동
ICT 부문은 대략적으로 소비합니다. 연간 460-700TWh의 전력 글로벌 데이터 센터에서는 AI 워크로드의 폭발적인 증가로 인해 2030년까지 이 수치는 두 배로 늘어날 것으로 예상됩니다. 하지만 이게 전부는 아니죠 에너지는 동일합니다. 전력망의 90%가 전력으로 공급될 때 한 번에 1킬로와트시가 생산됩니다. 재생에너지는 수요가 가장 많을 때 생산되는 에너지에 비해 CO2 배출량이 10분의 1로 줄어듭니다. 가스 발전소로 덮여 있습니다. 동일한 논리가 우주에도 적용됩니다. 즉, 유럽 클라우드 지역에서 작업을 실행합니다. 청정 에너지 혼합을 사용하면 전력이 공급되는 지역보다 4배 더 친환경적일 수 있습니다. 주로 석탄에서.
이 개념은 소프트웨어를 실행하는 것입니다. 언제 e 어디 전기는 청소기 - 이름이 수요 이동 o 탄소 인식 컴퓨팅, 이는 친환경 소프트웨어 엔지니어링의 기본 원칙 중 하나입니다. 거기 그린 소프트웨어 재단 이 원칙을 중심으로 참조 오픈 소스 툴킷을 구축했습니다. 탄소 인식 SDK, MIT 라이선스에 따라 GitHub에서 사용할 수 있으며 UBS, Vestas 및 Microsoft와 같은 회사에서 생산에 사용됩니다.
이 기사에서는 Carbon Aware SDK가 아키텍처, 데이터 소스, REST API, Python, Kubernetes 및 GitHub Actions와의 통합. 구체적인 사례를 구축하겠습니다. 시간 이동 (탄소집약도가 떨어지면 6~8시간 작업을 이동) 전자 위치 이동 (가장 친환경적인 클라우드 지역으로 워크로드 라우팅 실시간으로).
무엇을 배울 것인가
- Carbon Aware SDK 작동 방식: WebAPI 아키텍처, CLI 및 클라이언트 라이브러리
- 탄소 강도 데이터 소스: WattTime, ElectricityMaps, Ember Climate
- 시간 이동: 저탄소 강도 창에서 일괄 작업 예약
- 위치 이동: 가장 깨끗한 에너지 혼합을 갖춘 클라우드 지역 선택
- Carbon Aware SDK 클라이언트와 Python 통합
- Kubernetes + KEDA: 애플리케이션 코드 변경 없이 탄소 인식 자동 확장
- GitHub Actions: 지능형 스케줄링을 갖춘 탄소 인식 CI/CD 파이프라인
- 한계 탄소 강도와 평균 탄소 강도: 사용할 신호 및 이유
- 측정 가능한 배출 감소를 통한 한계, 절충 및 실제 사용 사례
그린 소프트웨어 엔지니어링 시리즈 — 모든 기사
| # | 제목 | 주제 |
|---|---|---|
| 1 | 그린소프트웨어공학의 원리 | 8가지 GSF 원칙, SCI 사양 ISO/IEC 21031 |
| 2 | CodeCarbon으로 배출량 측정 | Python, MLflow, 대시보드에서 CO2 추적 |
| 3 | Climatiq API: 클라우드 시스템의 탄소 집약도 | REST API, 클라우드 배출량 계산 및 공급망 |
| 4 | 탄소 인식 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, 운영 지표, 문화 변화 |
수요 변화: 탄소 인식 컴퓨팅의 기본 원리
1킬로와트시 전기의 탄소 강도는 고정되어 있지 않으며 매 시간마다 다릅니다. 그리드에서 얼마나 많은 재생 가능 에너지를 사용할 수 있는지에 따라 결정됩니다. 독일의 화창한 날, 탄소 강도는 i 이하로 떨어질 수 있습니다. 100gCO₂/kWh 몇 시간 안에 발전소; 태양광 발전이 꺼지고 산업 수요가 감소하는 밤에는 400gCO₂/kWh. 우주에도 동일한 변동성이 존재합니다. 스웨덴, 먹이 거의 전적으로 수력발전과 원자력으로 이루어지며, 15~40gCO2/kWh 사이에서 변화하는 반면, 폴란드는 여전히 석탄에 의존하고 있으며 종종 700gCO2/kWh를 초과합니다.
Il 수요 이동 유연한 워크로드를 이동하여 이러한 가변성을 활용합니다. 일괄 처리, ML 교육, 백업, 데이터 분석, CI/CD 테스트 - 순간과 지역을 향해 전기가 더 깨끗해요. 계산을 포기하는 것이 아니라 가장 좋은 시간을 선택하세요 그것을 실행합니다.
수요 이동의 유형
| 유형 | 설명 | 실제 사례 | 일반적인 CO2 감소 |
|---|---|---|---|
| 시간 이동 | 워크로드를 동일한 지역의 탄소 강도가 낮은 기간으로 이동하세요. | 오후 2시가 아닌 오전 2시에 ML 학습 실행 | 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의 구성 요소
| 요소 | 기술 | 사용 | 언제 사용하는가 |
|---|---|---|---|
| 웹API | ASP.NET Core(C#), 도커 | Swagger/OpenAPI가 포함된 REST API, 마이크로서비스로 배포 가능 | 모든 스택, 마이크로서비스 아키텍처와의 통합 |
| CLI | dotnet 도구, 크로스 플랫폼 | 터미널 쿼리, bash/PowerShell 스크립팅 | 배포 스크립트, DevOps 자동화, 신속한 테스트 |
| SDK 라이브러리 | NuGet 패키지(C#) | 애플리케이션 .NET 코드에 직접 통합 | 내장된 탄소 인식 논리를 원하는 .NET 애플리케이션 |
| Python 클라이언트 | openapi 생성기, 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 — 한계 운영 배출율), WattTime 제공, 질문에 대답합니다: "지금 당장 소비량을 1kWh 늘렸다면, 발전소는 추가 수요를 충당하기 위해 운영을 시작했나요?”. 대답은 거의 항상 가장 유연한 가스 발전소(소위 "한계 발전기")는 재생 에너지가 아닙니다. 이미 최대로 켜져 있습니다. 이 신호는 의사결정과 가장 관련성이 높습니다. 배출량을 줄이고 싶은 사람들의 실시간 원인 점진적인 소비로 인해 발생합니다.
신호 평균 (LCA - 수명주기 평균)ElectricityMaps에서 제공하는 가 응답합니다. 대신 질문에 : "생산된 모든 전기의 평균 배출량은 얼마입니까? 지금 네트워크에 있어?". 태양광이 50%, 가스가 50%인 그리드에서 평균 신호는 다음과 같습니다. 엣지에서 발생하는 상황에 관계없이 ~250gCO²/kWh. 이 신호는 더 ESG 보고 및 시장 기반 Scope 2 회계에 적합합니다.
중요: 2025년 ElectricityMaps 접근 방식 변경
2025년에 ElectricityMaps는 한계 신호가 불연속적입니다. 귀하의 API에서 데이터 검증 가능성 및 EU 규정 준수에 대한 우려를 언급 미국은 범위 2 회계에서 한계 요소의 사용을 금지합니다. 전기지도 이제 LCA(Life Cycle Assessment) 방법론을 기반으로 한 평균 신호만을 제공합니다. 만약에 귀하의 사용 사례에는 한계 신호가 필요합니다. WattTime은 유일한 주류 제공업체로 남아 있습니다. 그것을 지원하는 것입니다. Carbon Aware SDK는 다음을 통해 이러한 차이를 투명하게 처리합니다. 공급자 구성.
언제 어떤 신호를 사용해야 하는가
| 사용 사례 | 권장 신호 | 공급자 | Perché |
|---|---|---|---|
| 시간 이동 일괄 작업 | 가장자리 가의 | 와트시간 | 발생하는 배출가스의 실질적인 감소를 극대화합니다. |
| 다중 지역 위치 이동 | 중간 또는 한계 | ElectricityMaps 또는 WattTime | 둘 다 유용합니다. 더욱 안정적인 지역 간 평균 |
| ESG/Scope 2 보고 | 중간 | 전기지도 | GHG 프로토콜 및 EU/USA 규정에 따라 요구됨 |
| 개발 및 테스트 | 정적 JSON | 로컬 파일 | 테스트를 위한 비용 없음, 결정적 데이터 |
Carbon Aware SDK 설정 및 구성
Carbon Aware SDK를 로컬에서 시작하는 가장 빠른 방법은 Docker Compose를 사용하는 것입니다. ElectricityMaps를 공급자로 사용하여 전체 구성을 살펴보겠습니다(가장 쉬운 방법은 개발자를 위한 무료 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. 모든 것이 하나로 작동하는지 확인합시다
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 엔드포인트: 탄소 인식 SDK의 핵심
WebAPI는 OpenAPI/Swagger로 문서화된 REST 엔드포인트 세트를 공개합니다. 주요내용을 살펴보자 실제 통화의 예와 답변의 해석을 제공합니다.
GET /emissions/bylocations — 위치별 현재 배출량
특정 시간 간격에 있는 하나 이상의 위치에 대한 탄소 강도 데이터를 반환합니다. 서로 다른 지역 간의 현재 탄소 강도를 비교하는 데 사용됩니다(위치 이동).
# 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 및 Time Shifting
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_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 gCO2/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는 최대 복제본 수까지 확장할 수 있습니다. 높을 때 최대 숫자 복제 횟수가 자동으로 줄어듭니다.
아키텍처는 세 가지 요소로 구성됩니다. Kubernetes 탄소 강도 내보내기 (Carbon Aware SDK에서 데이터를 가져오고 거기에서 클러스터에서 ConfigMap으로 노출됨) 탄소 인식 KEDA 운영자 (ConfigMap을 읽고 KEDA 확장 제한 업데이트) 및 사용자 정의 리소스 CarbonAwareKedaScaler 이는 조정 임계값을 정의합니다.
Carbon Intensity 수출업체 설치
# 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
탄소 인식 KEDA 운영자가 실제로 일하는 방식
매시간 Carbon Aware KEDA Operator는 내보내기에서 업데이트된 ConfigMap을 읽고 업데이트합니다.
들판 maxReplicaCount 해당 임계값을 기반으로 한 KEDA ScaledJob의
현재 탄소강도를 기준으로 합니다. 대기열에 200개의 일자리가 있고 탄소 집약도가
312 gCO²/kWh(임계값 300-500), KEDA는 최대 10명의 작업자까지 확장 가능
20 대신. 강도가 150 아래로 떨어지자마자(예: 강한 바람이 부는 오전 2시)
제한은 자동으로 20개로 증가하고 작업이 더 빠르게 처리됩니다.
애플리케이션 코드를 변경할 필요가 없습니다.
Init 컨테이너를 사용한 Kubernetes CronJob 탄소 인식
주기적으로 예약된 작업의 경우 다른 패턴을 사용할 수 있습니다. 컨테이너 초기화 작업을 시작하기 전에 Carbon Aware SDK를 쿼리합니다. 실행 또는 연기 여부를 결정합니다. 이 접근법은 KEDA를 필요로 하지 않으며 다음과 같이 작동합니다. 표준 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
GitHub Actions: 탄소 인식 CI/CD 파이프라인
CI/CD 테스트 및 빌드 파이프라인은 탄소 인식을 위한 가장 쉬운 워크로드 중 하나입니다. 그 중 다수는 실제로 시간 유연성이 있기 때문입니다. 통합 테스트 스위트 특히 파이프라인의 경우 개발자 워크플로에 영향을 주지 않고 2~4시간을 기다릴 수 있습니다. 밤 또는 예정.
2024년 연구에서는 전체 생태계에 대한 GitHub Actions의 탄소 배출량을 추정했습니다. 그것은 순서대로이다 450-1000MTCO²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
사례 연구: CO2 35% 감소를 통한 ML 교육
학술 문헌에 기록된 패턴을 기반으로 현실적인 사례 연구를 제시합니다. (Springer Nature, 2024) 및 Carbon Aware SDK를 사용한 기업 구현에서.
시나리오: 전자상거래 추천 모델 교육 파이프라인
| 매개변수 | Valore |
|---|---|
| 대행사 | 중간 규모 전자상거래(활성 고객 50만 명) |
| 워크로드 | 추천 모델의 주간 학습 |
| 작업 기간 | 3~4시간(데이터세트 크기에 따라 다름) |
| 하드웨어 | 4x NVIDIA A100(클라우드 VM), 총 ~300W |
| 실행당 에너지 소비 | ~1.2kWh |
| 클라우드 지역 | Azure westeurope (암스테르담) |
| 빈도 | 일주일에 한 번 (일요일 밤) |
| 시간적 유연성 | 12시간(일요일 오후 8시부터 월요일 오전 8시까지) |
Carbon Aware SDK 도입 전후 결과
배출량 비교: 이전과 이후(연간 기준)
| 미터법 | 이전 (일요일 오후 10시 확정) | 이후(탄소 인식 시간 이동) | 변화 |
|---|---|---|---|
| 실행 당시 평균 탄소 집약도 | 265gCO₂/kWh | 172gCO₂/kWh | -35% |
| 단일 실행당 배출량 | 318gCO₂ | 206gCO₂ | -112g (-35%) |
| 연간 배출량(52회 실행) | 16.5kgCO₂ | 10.7kgCO₂ | -5.8kg(-35%) |
| 평균 시간 이동 | 0시간 | 6.2시간 | 해당 없음 |
| 12시간 이내에 실행된 작업 | 100% | 100% | 변하지 않은 |
| 계산 비용 | 변하지 않은 | 변하지 않은 | 0% |
결과 -35% CO2 배출량 간단하게 얻은거다 실행 시간을 이미 사용 가능한 12시간 범위 내로 이동합니다. 훈련 코드 변경, 추가 비용, 기능 감소가 없습니다. 이것이 바로 탄소 인식 컴퓨팅의 강점입니다. 환경적 이점에는 비용이 따릅니다. 시간에 유연한 워크로드를 위해 거의 0에 가깝게 작동합니다.
사례 연구 2: 탄소 인식 다중 지역 배포
두 번째 시나리오는 다음에 관한 것입니다. 위치 이동 데이터 처리 파이프라인용 이는 전 세계적으로 분산된 데이터 세트에서 매주 배치로 실행됩니다.
위치 이동 비교: 지역별 배출량(4시간 작업, 500W)
| 지역 | 탄소 집약도 평균 | 실행당 배출량(gCO₂) | 대 최고 |
|---|---|---|---|
| asia-east1(대만) | 545gCO₂/kWh | 1090gCO₂ | +11850%(최악의 경우) |
| us-east-1(버지니아) | 380gCO₂/kWh | 760gCO₂ | +8160% |
| eu-west-1(아일랜드) | 280gCO₂/kWh | 560gCO₂ | +5933% |
| 스웨덴 중앙(Azure) | 18gCO²/kWh | 36gCO² | +289% |
| 유럽-북쪽1(핀란드) | 9gCO²/kWh | 9.2gCO₂ | 최고(대만 대비 -99%) |
위치가 다음으로 이동 중입니다. europe-north1 대신에 asia-east1
배출량을 줄입니다 99% 이상 정확히 동일한 작업 부하에 대해.
분명히 타당성은 데이터가 어디에 있는지(데이터 중력), 요구 사항에 따라 달라집니다.
대기 시간 및 데이터 상주 규정(GDPR 등). 하지만 워크로드의 경우
전송 가능한 데이터 세트에 대한 분석은 사용 가능한 가장 강력한 수단입니다.
National Grid Operators의 API와의 비교
Carbon Aware SDK에서 기본적으로 지원하는 공급자 외에도 다음의 공개 API가 있습니다. 탄소 집약도 또는 에너지 혼합 데이터를 제공하는 국가 네트워크 사업자 실시간. 이는 초지역화된 데이터를 원하거나 이에 의존하고 싶지 않은 사람들에게 유용합니다. 상업 공급자로부터.
탄소 집약도를 위한 공개 API 그리드 운영자
| 연산자 | 마을 | 아피스 | 사용 가능한 데이터 | 메모 |
|---|---|---|---|---|
| 테르나 | 이탈리아 | transparent.terna.it | RT 에너지 믹스, D+1 예측 | 무료, REST JSON |
| RTE | 프랑스 | data.rte-france.com | 에너지 믹스, CO2 배출량, 가격 | OAuth2, 무료 |
| 카이소 | 캘리포니아(미국) | caiso.com/awe | 재생가능%, 탄소집약도 | 무료, XML/JSON |
| 내셔널 그리드 ESO | UK | carbonintensity.org.uk | 영국 지역 탄소 집약도 | 무료, REST JSON, 예측 |
| ENTSO-E | 유럽 | transparent.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")
한계와 장단점: 지연 시간과 지속 가능성
탄소 인식 컴퓨팅은 타협할 수 없는 솔루션이 아닙니다. 그것은 근본적이다 해결하는 것보다 더 많은 문제를 일으키지 않도록 실제적인 한계와 장단점을 이해하십시오.
채택 전 고려해야 할 한계
-
데이터 중력: 컴퓨터를 옮기는 것은 쉽지만 데이터가
옮기기에는 너무 부피가 크다. 10TB의 데이터에 접근하는 직업
us-east-1실행할 가치가 거의 없습니다eu-north-1이전을 통해 달성한 절감액보다 더 많은 CO2가 배출되는 경우. - 예측 정확도: 24~72시간 동안의 탄소 강도 예측 시간 범위에 따라 증가하는 오차 한계가 있습니다. 기상 조건 (바람, 태양)은 6~12시간 이상을 높은 정확도로 예측하기 어렵습니다.
- 지연 시간과 지속 가능성: 시간 이동으로 인해 고유한 대기 시간이 발생합니다. 결과에서. 데이터 과학 팀이 훈련 작업 결과를 기다리고 있는 경우 8시간 지연하면 실제 생산 비용이 발생합니다.
- 한계 대 평균 역설: 최근 연구에서 강조된 바와 같이, 한계 신호와 평균 신호를 최적화하면 반대 결정이 내려질 수 있습니다. 사용 사례에 맞지 않는 신호를 선택하면 배출량이 늘어날 수도 있습니다.
- 리바운드 효과: 탄소 인식 컴퓨팅이 소비를 장려한다면 "녹색" 시간 동안 더 많은 계산을 수행하면 순 효과는 0이거나 음수가 될 수 있습니다. (소프트웨어에 적용되는 Jevons 역설)
- 지리적 범위: WattTime 및 ElectricityMaps가 적용됩니다. 미국과 EU에서는 우수하지만 데이터 센터가 있는 많은 신흥 국가에서는 제한적입니다. 그들은 빠르게 성장하고 있습니다.
워크로드 유형별 타당성 매트릭스
워크로드 유형별 탄소 인식 컴퓨팅의 적용 가능성
| 워크로드 유형 | 시간 이동 | 위치 이동 | 동기 부여 |
|---|---|---|---|
| ML 모델 학습 | 최적 | 최적 | 높은 유연성, 높은 에너지 강도 |
| 일괄 ETL 파이프라인 | 최적 | 좋은 | 스케줄러가 이미 존재하며 데이터 지역성에 따라 다름 |
| 데이터 백업 및 복제 | 최적 | 제한된 | 시간이 유연하지만 다중 지역 백업이 이미 예약되어 있음 |
| 비차단 CI/CD 파이프라인 | 좋은 | 좋은 | 팀이 수락한 대기 임계값에 따라 다릅니다. |
| 보고서 생성 | 최적 | 제한된 | 높은 시간적 유연성은 SLA 대기 시간에 따라 다름 |
| API 제공(실시간) | 해당 없음 | 제한된 | SLA 대기 시간은 시간적 변위를 허용하지 않습니다. |
| 트랜잭션 데이터베이스 | 해당 없음 | 제한된 | 일관성과 대기 시간으로 인해 움직임이 방지됨 |
| 실시간 스트리밍(Kafka) | 해당 없음 | 해당 없음 | 지속적, 지연 시간에 민감, 상태 저장 |
Carbon Aware SDK 채택 모범 사례
탄소 인식 컴퓨팅 구현 체크리스트
- 시간에 유연한 워크로드 식별 우선: 카탈로그 일괄 작업 허용 가능한 유연성 범위를 갖추고 있습니다. 가장 성과가 좋은 작업부터 시작하세요 에너지 소비 및 최대 시간 유연성.
- 귀하의 사용 사례에 적합한 공급자를 선택하세요: 최적화를 위한 WattTime 실제 한계 배출; 시장 기반 ESG 및 범위 2 보고를 위한 ElectricityMaps.
- 명확한 SLA 정의: 허용 가능한 최대 대기 창을 지정합니다. 모든 유형의 직업에 대해. ML 훈련은 12시간 동안 기다릴 수 있습니다. 경영 보고서 오전 9시 이전에 준비해야 합니다.
- 우아한 대체 구현: Carbon Aware SDK에 접근할 수 없는 경우 또는 예측을 사용할 수 없는 경우 작업을 정상적으로 실행하세요. 차단하지 않음 환경 최적화를 위한 생산.
- 측정 및 보고: 탄소 집약도 지표를 대시보드에 통합합니다. 운영. 팀에 동기를 부여하고 ESG ROI를 입증하기 위해 누적된 절감액을 보여줍니다.
- 시간과 위치 이동 결합: 절감 효과를 극대화하기 위해, 순간과 영역의 최적 조합을 찾고 두 차원을 최적화하지 마십시오 별도로.
- 데이터 전송에 주의하세요: 이동배출을 고려 전체 위치 이동 계산의 데이터입니다. 때때로 최고의 "녹색" 지역 데이터 전송 비용 때문에 편리하지 않습니다.
- Carbon Aware SDK를 사이드카 또는 공유 서비스로 배포: 아니다 각 팀은 자체 배포를 구성해야 합니다. 다음을 갖춘 중앙 집중식 인스턴스 캐싱은 비용을 절감하고 관리를 단순화합니다.
TypeScript: Next.js 및 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;
};
}
결론: 운영 표준으로서의 탄소 인식 컴퓨팅
Carbon Aware SDK는 오늘날 가장 성숙하고 최고의 지원을 제공하는 도구입니다. 구현 수요 이동 엔터프라이즈 소프트웨어 시스템에서. API 사용 표준 REST, 주요 데이터 제공자(WattTime, ElectricityMaps) 지원 및 Kubernetes/KEDA 및 GitHub Actions에 대한 통합 준비가 완료되어 비용이 크게 절감됩니다. 친환경 소프트웨어 엔지니어링 채택을 가로막는 기술적 장벽.
결과는 구체적이고 문서화되어 있습니다. 작업 부하에 대한 배출량이 20~45% 감소합니다. 시간 이동을 통한 배치, 유럽 지역으로의 위치 이동을 통해 최대 99% 수력발전과 원자력. 그리고 중요한 점은 이러한 절감 효과가 운영 비용은 0에 가깝습니다.: 다른 하드웨어가 필요하지 않으며 크기도 줄이지 않습니다. 애플리케이션 기능을 사용하더라도 클라우드 비용이 증가하지 않습니다.
방향은 명확합니다. 주요 클라우드 제공업체가 측정항목을 통합하고 있습니다. 콘솔의 탄소 강도(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 - ElectricityMaps API:
portal.electricitymaps.com/developer-hub/api - 영국 탄소 강도 API:
carbonintensity.org.uk - Springer 2024: "탄소 효율적인 LLM 교육을 위한 시간 이동 전략"
- MDPI 지속 가능성 2025: "에지 클라우드의 탄소 인식 시공간 워크로드 이동"
- 그린 소프트웨어 재단 SCI 사양: ISO/IEC 21031:2024







