This document describes the technical architecture of GreenKube. The goal is to create a lightweight, modular, and extensible platform to measure, report, and optimize the carbon footprint and cost of Kubernetes workloads.
flowchart TB
subgraph K8s["Kubernetes Cluster"]
Prom["Prometheus"]
OC["OpenCost"]
K8sAPI["K8s API"]
EM["Electricity Maps API"]
WN["Wattnet API"]
Boavizta["Boavizta API"]
end
subgraph GreenKube["GreenKube"]
direction TB
subgraph Collectors["Collectors (Input Adapters)"]
PC["PrometheusCollector"]
OCC["OpenCostCollector"]
NC["NodeCollector"]
PodC["PodCollector"]
EMC["ElectricityMapsCollector"]
WNC["WattnetCollector"]
BC["BoaviztaCollector"]
end
subgraph Core["Core (Business Logic)"]
DP["DataProcessor<br/>(Facade)"]
CO["CollectionOrchestrator<br/>(Parallel Fetch)"]
Est["BasicEstimator<br/>(CPU → Joules)"]
Calc["CarbonCalculator<br/>(Joules → CO₂e)"]
MA["MetricAssembler<br/>(Build CombinedMetric)"]
NZM["NodeZoneMapper<br/>(Cloud → eMaps Zone)"]
PRM["PrometheusResourceMapper<br/>(Per-Pod Resources)"]
CN["CostNormalizer<br/>(Per-Step Cost)"]
ESvc["EmbodiedEmissionsService<br/>(Boavizta Cache + Fallback)"]
SR["SummaryRefresher<br/>(Pre-computed Cache)"]
HRP["HistoricalRangeProcessor<br/>(Chunked Range)"]
Rec["Recommender<br/>(9 Types)"]
end
subgraph Storage["Storage (Output Adapters)"]
PG["PostgreSQL"]
SQLite["SQLite"]
end
subgraph Presentation["Presentation"]
API["FastAPI<br/>REST API"]
CLI["Typer CLI"]
Dash["SvelteKit<br/>Dashboard"]
Grafana["Grafana<br/>(via Prometheus)"]
end
end
Prom --> PC
OC --> OCC
K8sAPI --> NC
K8sAPI --> PodC
EM --> EMC
WN --> WNC
Boavizta --> BC
PC --> CO
OCC --> CO
PodC --> CO
NC --> DP
CO --> DP
EMC --> DP
WNC --> DP
BC --> ESvc
DP --> Est
DP --> NZM
DP --> PRM
DP --> CN
DP --> MA
DP --> HRP
DP --> SR
MA --> Calc
MA --> ESvc
MA --> Storage
SR --> Storage
Storage --> API
Storage --> CLI
API --> Dash
API --> Grafana
Storage --> Rec
Rec --> API
Rec --> CLI
GreenKube operates as an asynchronous agent that collects, processes, analyzes, and reports data. It runs as both a scheduled service (continuous monitoring) and an on-demand CLI tool (ad-hoc reporting).
The system is designed around the principles of Clean Architecture and Hexagonal Architecture:
All I/O operations use Python’s asyncio for high-performance, non-blocking concurrent execution.
The core business logic (src/greenkube/core) NEVER depends on a specific storage implementation. This is enforced through:
CarbonIntensityRepository, NodeRepository)src/greenkube/core/factory.py instantiates the appropriate repository based on configurationSupported backends:
Cloud-specific details (AWS, GCP, Azure, OVH, Scaleway) are isolated in:
src/greenkube/utils/region_mapping.py (cloud region → carbon zone)src/greenkube/energy/instance_profiles.py (instance type → power characteristics)asyncio.gather for parallel data collectionAll collectors are fully asynchronous and implement a common pattern.
asyncio.gather)httpx.AsyncClient for non-blocking HTTP requestsPrometheusMetric (aggregated result with all metric lists)kubernetes_asyncio client for async K8s API callsDict[str, NodeInfo] (node name → metadata)List[PodMetric]List[CostMetric]/v1/footprints), token-based auth (/token-request/get_token)footprint_type=water) is not consumed yet — see docs/wattnet.mdcollect(zone, target_datetime))Selection: ELECTRICITY_PROVIDER config (electricity_maps |
wattnet), instantiated by the factory |
EmbodiedRepositoryEmbodiedEmissionsService injects a profile using DEFAULT_EMBODIED_EMISSIONS_KG (default: 350 kg CO₂e) and marks the metric as estimatedPrometheusMetric (CPU usage rates per pod)DEFAULT_INSTANCE_PROFILE when node type unknownList[EnergyMetric] (Joules per pod)The main orchestrator that coordinates the data pipeline from collection to metric assembly.
Architecture: The processor acts as a facade, delegating specialized work to focused collaborators while managing the overall pipeline flow.
Key Responsibilities:
Pipeline Stages:
run()): Real-time collection using Prometheus instant queries, structured into four explicit phases:
run_range()): Historical analysis with day-sized chunking for memory efficiencyImplementation Pattern:
CO2e = (kWh * grid_intensity * PUE) / 1000(zone, timestamp) → intensityCarbonResult (CO2e grams, grid intensity, timestamp)24h, 7d, 30d, 1y, ytd)metrics_summary (scalar totals per window) and metrics_timeseries_cache (ordered time buckets per window)POST /api/v1/metrics/dashboard-summary/refreshAnalyzes CombinedMetric data to identify optimization opportunities.
Recommendation Types:
Configuration: All thresholds configurable via config.py and Helm values
Repositories use asynchronous drivers for high-performance database interactions. All implement abstract base classes to ensure database agnosticism.
Abstract base class for carbon intensity storage.
Implementations:
asyncpg (native async PostgreSQL)carbon_intensity table with zone, timestamp, intensityaiosqlite (async SQLite wrapper)Abstract base class for node state snapshots.
Implementations:
node_snapshots table with timestamp, name, instance_type, zone, capacityStores final aggregated metrics (energy + carbon + cost + resources).
Schema (33 columns):
co2e_grams — indirect emissions from purchased electricity (grid intensity × energy × PUE)embodied_co2e_grams — upstream hardware manufacturing emissions (Boavizta, allocated by CPU share)total_co2e_grams — computed field, full pod carbon footprintMigrations: All backends support automatic schema evolution (ADD COLUMN IF NOT EXISTS)
Caches Boavizta API responses for hardware embodied emissions.
Schema: provider, instance_type, gwp_manufacture, lifespan_hours, last_updated
Stores pre-computed KPI scalar totals per window.
Schema: window_slug, namespace, total_co2e_grams, total_embodied_co2e_grams, total_cost, total_energy_joules, pod_count, namespace_count, updated_at
Stores pre-computed time-series buckets per window and granularity.
Schema: window_slug, namespace, bucket_ts, co2e_grams, embodied_co2e_grams, total_cost, joules
src/greenkube/api//api/v1/docsEndpoints:
GET /api/v1/health — Health check and versionGET /api/v1/health/services — Health status for all data sources (Prometheus, OpenCost, Electricity Maps, Wattnet, Boavizta, Kubernetes)GET /api/v1/health/services/{name} — Health status for a single data sourcePOST /api/v1/config/services — Update service URLs or tokens at runtimeGET /api/v1/config — Current configuration (sanitized secrets)GET /api/v1/metrics — Per-pod metrics with filteringGET /api/v1/metrics/summary — Aggregated totals (on-demand, full scan)GET /api/v1/metrics/timeseries — Time-series data with granularity (on-demand, full scan)GET /api/v1/metrics/dashboard-summary — Pre-computed KPI scalars from cache (fast, no full scan)GET /api/v1/metrics/dashboard-timeseries/{window_slug} — Pre-computed time-series buckets from cache (24h, 7d, 30d, 1y, ytd)POST /api/v1/metrics/dashboard-summary/refresh — Trigger on-demand background refresh of all cache tables (HTTP 202)GET /api/v1/namespaces — Active namespaces listGET /api/v1/nodes — Node inventoryGET /api/v1/recommendations — Optimization suggestionsGET /api/v1/report/summary — Report preview (row count, totals, unique pods/namespaces)GET /api/v1/report/export — Stream a report file (CSV or JSON) for direct browser downloadReport export query parameters:
format — csv (default) or jsonlast — time range string (1h, 24h, 7d, 30d, 1y, etc.)namespace — filter to a single namespaceaggregate — true to aggregate by (namespace, pod, period)granularity — grouping when aggregate=true: hourly, daily, weekly, monthly, yearlyfrontend//Pages:
/ — Dashboard (KPIs, charts, breakdown)/metrics — Interactive metrics table/nodes — Node inventory/recommendations — Optimization recommendations/report — Report builder: choose time range, namespace, aggregation, format and download CSV/JSON/settings — Configuration, service health overview with color-coded status indicators, and runtime service configurationFeatures:
src/greenkube/cli/greenkube report — Generate reports with filteringgreenkube recommend — Get optimization recommendationsgreenkube start — Run as background servicegreenkube api — Start API servergreenkube demo — Launch demo mode with sample datagreenkube version — Show version infodashboards/greenkube-grafana.json/prometheus/metricsServiceMonitor, NetworkPolicy, Prometheus RBACUsed for real-time monitoring and scheduled collection.
NodeCollector.collect()
└─ Node metadata (instance type, zone, capacity, provider)
└─ Build node_instance_map directly from Phase-1 data
NodeZoneMapper.map_nodes(nodes_info)
└─ Map cloud region → Electricity Maps zone per node
asyncio.gather(
┌─ CollectionOrchestrator.collect_all(nodes_info):
│ ├─ PrometheusCollector.collect()
│ │ ├─ CPU usage (8 concurrent queries)
│ │ ├─ Memory usage
│ │ ├─ Network I/O (rx + tx)
│ │ ├─ Disk I/O (read + write)
│ │ ├─ Restart counts
│ │ └─ Node labels (enriched with Phase-1 data)
│ ├─ OpenCostCollector.collect()
│ │ └─ Cost allocation data
│ └─ PodCollector.collect()
│ └─ Resource requests (CPU, memory, storage)
└─ EmbodiedEmissionsService.prepare_embodied_data(nodes_info):
├─ Check EmbodiedRepository cache
├─ Fetch missing profiles from Boavizta API
└─ Inject fallback profile (DEFAULT_EMBODIED_EMISSIONS_KG) for unknowns
)
├─ BasicEstimator.estimate() → EnergyMetric per pod (Joules)
├─ MetricAssembler.prefetch_intensities()
│ ├─ Group pods by zone
│ └─ Prefetch intensity for (zone, timestamp) pairs
└─ MetricAssembler.assemble()
├─ CarbonCalculator.calculate_emissions()
├─ EmbodiedEmissionsService.calculate_pod_embodied()
│ └─ Mark metric is_estimated=True if fallback used
└─ Build CombinedMetric (energy + carbon + cost + resources + metadata)
Repository.write_combined_metrics()
└─ Batch insert to database (Postgres/SQLite)
SummaryRefresher.run()
├─ For each window (24h, 7d, 30d, 1y, ytd):
│ ├─ aggregate_summary() → MetricsSummaryRow → upsert metrics_summary
│ └─ aggregate_timeseries() → TimeseriesCachePoint[] → upsert metrics_timeseries_cache
└─ Repeated for each namespace
Used for reporting over time ranges with historical accuracy.
NodeRepository.get_latest_snapshots_before(start)
NodeRepository.get_snapshots(start, end)
└─ Reconstruct node timeline
For each chunk (1 day):
├─ Prometheus range queries (concurrent):
│ ├─ CPU usage over time
│ ├─ Network I/O over time
│ ├─ Disk I/O over time
│ ├─ Restart counts over time
│ └─ (5 queries via asyncio.gather)
│
├─ Parse time-series data:
│ └─ Build per-pod resource maps from range results
│
├─ Energy estimation per timestamp:
│ └─ Use historical node profile at each timestamp
│
├─ Carbon calculation:
│ └─ Historical intensity lookup
│
└─ Generate CombinedMetrics for chunk
Collect all chunks → Filter by namespace → Return
Read CombinedMetrics from repository (last N days)
RecommenderV2.generate_recommendations()
├─ Aggregate pod metrics by stable workload owner when available
├─ Calculate statistics (weighted mean, observed max, percentile, CV)
├─ Apply thresholds:
│ ├─ Zombie detection
│ ├─ Rightsizing analysis using average and retained maximum usage
│ ├─ Autoscaling candidates
│ └─ Carbon-aware opportunities
├─ Upsert active recommendations by full target identity
├─ Mark previously active recommendations as stale when absent from the latest generation
└─ Calculate savings (cost + CO2e)
Return List[Recommendation] with:
├─ Type and severity
├─ Affected resources
├─ Current vs. recommended
├─ Estimated savings
└─ Actionable commands
All configuration flows through src/greenkube/core/config.py:
Database:
DB_TYPE — postgres |
sqlite |
DB_CONNECTION_STRING — PostgreSQL connection URLDB_PATH — SQLite file pathExternal Services:
PROMETHEUS_URL — Override auto-discoveryOPENCOST_URL — Override auto-discoveryELECTRICITYMAPS_TOKEN — API token (optional)BOAVIZTA_TOKEN — API token (optional)Collection:
PROMETHEUS_QUERY_RANGE_STEP — Default: 5mNODE_ANALYSIS_INTERVAL — Default: 5mNODE_DATA_MAX_AGE_DAYS — Default: 30Carbon:
DEFAULT_ZONE — Fallback carbon zoneDEFAULT_INTENSITY — Fallback intensity (gCO2e/kWh)NORMALIZATION_GRANULARITY — hour |
day | none |
DEFAULT_EMBODIED_EMISSIONS_KG — Fallback embodied emissions when Boavizta API returns no data (default: 350 kg CO₂e)Recommendations:
RECOMMENDATION_LOOKBACK_DAYS — Default: 7RIGHTSIZING_CPU_THRESHOLD — Default: 0.3ZOMBIE_COST_THRESHOLD — Default: 0.01helm-chart/
├── Chart.yaml # Chart metadata
├── values.yaml # Default configuration
└── templates/
├── deployment.yaml # GreenKube deployment
├── service.yaml # API service (LoadBalancer/ClusterIP)
├── configmap.yaml # Non-secret config
├── secret.yaml # Tokens and credentials
├── postgres-*.yaml # PostgreSQL StatefulSet (optional)
├── serviceaccount.yaml # K8s RBAC
├── clusterrole.yaml # Read permissions for nodes/pods
└── post-install-hook.yaml # Schema initialization
Dockerfile (3 stages):
1. Frontend Builder (Node 20):
├─ npm ci (install deps)
├─ npm run build (SvelteKit SSG)
└─ Output: static files in build/
2. Python Builder (Python 3.14):
├─ pip install build
├─ Build wheel from pyproject.toml
└─ pip install to /install prefix
3. Final Image (Python 3.14-slim):
├─ Copy Python packages from builder
├─ Copy frontend build from frontend-builder
├─ Run as non-root user 'greenkube'
└─ ENTRYPOINT: greenkube CLI
Controlled by NORMALIZATION_GRANULARITY:
2024-02-21T14:37:22Z → 2024-02-21T14:00:00Z2024-02-21T14:37:22Z → 2024-02-21T00:00:00ZCarbonCalculator Cache:
(zone, normalized_timestamp_iso)Repository Cache:
Boavizta Cache:
EmbodiedRepository)is_fallback=True is injected using DEFAULT_EMBODIED_EMISSIONS_KG; the resulting metric is flagged is_estimated=TrueDashboard Summary Cache:
metrics_summary + metrics_timeseries_cache)SummaryRefresherasyncio.gatherrun_range() processes 1 day at a timedel statements for large intermediate structuressrc/greenkube/corestorage/base_repository.py)utils/ or energy/ mapping fileslogging.getLogger(__name__)async def for I/O operationsasyncio.gather for concurrent operationsruff format before commits (pre-commit hook)ruff check --fix to auto-fix issuesFollow conventional commits:
feat: — New featurefix: — Bug fixdocs: — Documentation onlytest: — Test additions/changesrefactor: — Code restructuringperf: — Performance improvementsMulti-Cluster Support:
Advanced Analytics:
Real-Time Streaming:
Extended Metrics:
Enhanced Reporting:
New Collectors:
core/factory.pyDataProcessor to use itNew Storage Backend:
CarbonIntensityRepository abstractcore/factory.py mappingNew Cloud Provider:
utils/region_mapping.pyenergy/instance_profiles.pycore/config.pyApache 2.0 - See LICENSE file for details.