Install it with one Helm command.
Both charts are published from this site. greenshift-agents goes into each customer cluster; greenshift-core runs the GreenShift Master — the attribution pipeline and its PostgreSQL, Kafka and Redis — in the operator's own cluster.
# 1. Add the GreenShift chart repository helm repo add greenshift https://greenshift-registry.online/charts helm repo update # 2. Install, supplying the registry and MinIO credentials and the # Master connection (described below) # (sent to you through our secure delivery channel) helm install greenshift-agents greenshift/greenshift-agents \ -n greenshift-system --create-namespace \ --set registry.password="<provided separately>" \ --set minio.rootUser="<choose a value>" \ --set minio.rootPassword="<choose a value>" \ --set master.url="https://gateway.example.com" \ --set master.token="<provided separately>" \ --set master.clusterInstanceId="<provided separately>" # 3. Verify kubectl get pods -n greenshift-system
# The Registration Service in each customer cluster registers the cluster # with the GreenShift Master and keeps its node list current there; the # Attributor Service pulls the cluster's power back from it. # master.url — scheme and host of the Master's public Gateway, no path. # The same value the operator set as global.gatewayPublicUrl. https:// only. --set master.url=https://gateway.example.com # master.token — this cluster's credential, a bearer token. The operator # puts only its SHA-256 fingerprint in the Master's cluster credential # registry: printf %s "$TOKEN" | sha256sum # Or keep it in your own Secret (key credential) in greenshift-system: # --set master.existingSecret=<name> --set master.token="<provided separately>" # master.clusterInstanceId — the ID the operator provisioned for this # cluster, so the Master recognises it on first registration. --set master.clusterInstanceId="<provided separately>" # master.ca — only if the Gateway's certificate comes from a private CA: # that CA (PEM). It is added to the public roots, never replaces them. # Or name a ConfigMap you already have: --set master.caConfigMap=<name> # --set master.caConfigMapKey=<key> --set-file master.ca=gateway-ca.crt # Optional: how the cluster names itself to the Master. --set cluster.localName=prod-eu-1 --set cluster.environment=production --set cluster.site=dc-1 # Leave master.* out to run without a Master: nodes still register locally # and nothing leaves the cluster. A missing or rejected credential never # stops a workload; registration is retried. Rotate the token with # helm upgrade --reuse-values --set master.token=... (no restart needed).
Seeing your figures. The chart installs the App Owner
Dashboard and the App Owner API beside the agents. Neither is published
outside your cluster: anyone allowed to port-forward in
greenshift-system can open them, and no one else can.
# Forward the dashboard to your workstation, then open http://localhost:8090 kubectl -n greenshift-system port-forward svc/greenshift-dashboard 8090:8090 # Where your cluster's energy goes: the share drawn by your applications, the # host overhead, and the idle draw of machines that are powered on but busy # with nothing. Pick 24h, 7d or the month; live power refreshes every 10 s, # energy every minute. Break it down by machine, namespace, deployment and pod. # The dashboard calls the API for you, with a token that stays in the cluster # (Secret greenshift-dashboard-api-token, generated on install). To choose it: # --set dashboard.apiToken=... or --set dashboard.existingSecret=<name>
# The same figures as JSON, for your own reports and tooling. In the cluster # it answers at http://greenshift-app-owner-api.greenshift-system:8080; from a # workstation, forward it first: kubectl -n greenshift-system port-forward svc/greenshift-app-owner-api 8080:8080 # Every /v1 call needs an Authorization: Bearer header. The pilot carries one # tenant per cluster and does not check the token yet, but it rejects a call # without one. curl -H 'Authorization: Bearer <token>' http://localhost:8080/v1/namespaces # Energy over a period. scope: cluster, nodes, namespaces, deployments, pods. # period: day, week, month, year (default day), or an explicit window with # from= and to= in RFC3339 UTC. namespace= narrows the result. GET /v1/energy/{scope}?period=week GET /v1/energy/pods?from=2026-10-01T00:00:00Z&to=2026-10-02T00:00:00Z&namespace=shop # One entity GET /v1/energy/namespaces/{name} GET /v1/energy/deployments/{namespace}/{name} GET /v1/energy/nodes/{name} # Power right now, same scopes GET /v1/power/{scope} # What there is to ask about: what has drawn power in the last day GET /v1/namespaces GET /v1/deployments GET /v1/pods # Energy is in energy_microjoules and energy_kwh, power in microwatts. Only # CPU energy is measured (measured_components: ["cpu"]). cluster and nodes # also report the unattributed host-process and idle energy. A response # with status "partial" covers only part of the window, e.g. one reaching # back before the cluster began collecting; the missing part is never a zero. # Set that start with --set appOwnerApi.dataStart=2026-07-29T14:16:00Z if # metrics were collected before the chart was installed.
# 1. A pull secret for our images, in the Master namespace kubectl create namespace greenshift-master kubectl -n greenshift-master create secret docker-registry greenshift-registry-credentials \ --docker-server=greenshift-registry.online --docker-username=greenshift \ --docker-password="<provided separately>" # 2. Install with your four site inputs (described below) helm install greenshift-core greenshift/greenshift-core -n greenshift-master \ --set global.imageRegistry=greenshift-registry.online \ --set 'global.imagePullSecrets[0]=greenshift-registry-credentials' \ --set global.gatewayPublicUrl=https://gateway.example.com \ --set-file ingestionApi.hostCredentialRegistry.content=host-credentials.yaml \ --set-file clusterCredentialRegistry.content=cluster-credentials.yaml \ --set-file vmMapper.metadata.content=pilot-metadata.yaml \ --wait --wait-for-jobs --timeout 15m # On a bare cluster with a NodePort Gateway, only the node running the # Gateway pod answers: pin it to the node your public URL names with # --set gateway.nodeSelector."kubernetes\.io/hostname"=<node> # 3. Verify: database, migrations, service discovery, gateway helm test greenshift-core -n greenshift-master
Site inputs. The Master needs four things only you know. Keep all four files out of version control: they bind credentials to identities. They hold SHA-256 fingerprints, never a token or password, so a leaked file cannot be replayed — but it still names your hosts and clusters.
# global.gatewayPublicUrl — an absolute https:// URL. # The address at which customer clusters and hypervisor hosts reach the # GreenShift Gateway from outside the Master cluster. Registration hands it # to every customer cluster as the base URL for its power queries, so it must # be the name on the Gateway's TLS certificate, not an internal address. --set global.gatewayPublicUrl=https://gateway.example.com
# Which hypervisor host may push power telemetry, and as whom. Read by the # Ingestion API; a host may only ever submit data for its own host_id. # Reloaded every 30 s when its content changes — revoke by editing. schema_version: "1.0" registry_revision: 1 # increment on every edit updated_at: "2026-10-01T09:00:00Z" # RFC3339 UTC, ends in Z credentials: # 1 to 1000 entries - credential_id: "cred-host-001-a" # your label, shown in audit logs credential_type: "host_bearer_token" # lowercase hex SHA-256 of the bearer token alone (no "Bearer "); # 64 characters, unique across the file: printf %s "$TOKEN" | sha256sum fingerprint_sha256: "<64 hex characters>" host_id: "host-001" # the host this token belongs to identity_status: "active" # active | revoked | expired not_after: "2027-10-01T00:00:00Z" # Rotate a credential by adding the new one as active and marking the old # one revoked, both under the same host_id. Never put a token, password, # secret or private_key field in this file: the whole file is rejected. # Or name a Secret you created (key registry.yaml): # --set ingestionApi.hostCredentialRegistry.existingSecret=<name>
# Which customer cluster may register and query power, and as whom. One # entry per customer cluster, binding the SHA-256 fingerprint of that # cluster's credential to its Master-allocated cluster ID, which always has # the form gsc- followed by 8 lowercase hex characters (gsc-1a2b3c4d). # One file serves both the Registration Manager and the Customer Attributor. # Fingerprints only, never the credential itself. The exact fields are in # the greenshift-core chart README (helm pull greenshift/greenshift-core --untar). # Or name a ConfigMap you created (key registry.yaml): # --set clusterCredentialRegistry.existingConfigMap=<name>
# Which VMs are in scope, and which customer cluster each belongs to. Read # by the VM Mapper, which writes power only for the VMs listed here. schema_version: "1.0" greenshift_cluster_id: "gsc-1a2b3c4d" # default cluster for every VM below metadata_confidence: 0.95 # optional, defaults to 0.95 vms: # at least one; vm_uuid unique - vm_uuid: "550e8400-e29b-41d4-a716-446655440000" node_uid: "node-uid-1" # optional Kubernetes node UID - vm_uuid: "6ba7b810-9dad-11d1-80b4-00c04fd430c8" greenshift_cluster_id: "gsc-9f8e7d6c" # this VM is in another cluster # Every cluster ID here must already be in the cluster credential registry, # or that VM's power is written where no one reads it. This file says # nothing about tenants: VM ownership comes from the tenant assignments. # Or name a ConfigMap you created (key pilot_metadata.yaml): # --set vmMapper.metadata.existingConfigMap=<name>
Power source. The Master reads each hypervisor host's
power at the PDU that feeds it, splits it across the host's VMs, and
hands every customer cluster its own share — which is what the App
Owner Dashboard and API then show. For an Eaton Rack PDU G4 one value
chooses the source at install time, and the chart writes the
host-to-PDU inventory for you: pdu.source=eaton for a real
device, or pdu.source=emulator for a test cluster that has
none. Add the values below to the helm install of step 2,
or apply them later with helm upgrade --reuse-values.
Needs greenshift-core 0.3.0 or later.
# Test only: the power it serves is synthetic. Never select it on a # deployment that serves a customer. It runs one emulated Eaton Rack PDU G4 # inside the Master cluster, reachable only by the Infra Mapper, and wires # the Infra Mapper to it; its passphrase is generated and never shown. --set pdu.source=emulator \ --set 'pdu.emulator.hosts[0]=host-001' # Exactly one host. host-001 must be the GS_HOST_ID of the Host Mapper on the # hypervisor that runs your customer-cluster node VMs, with an entry for it # in the host credential registry (site input 2). # Its outlets and their watts, one per PSU of that host. Default 1:410,2:420, # i.e. 830 W in all. Write outlet ids without leading zeros. To change them: --set eatonEmulator.config.GS_EATON_EMULATOR_OUTLETS='1:820\,2:840' # The emulator restarts and the Infra Mapper logs in again on its next read.
# 1. The password of the PDU's REST API account, in a Secret you create. # The chart never takes it as a value. Use a dedicated account: the # Infra Mapper only reads measurements. kubectl -n greenshift-master create secret generic eaton-pdu-password \ --from-literal=password='<PDU account password>' # 2. Point the Infra Mapper at the device --set pdu.source=eaton \ --set pdu.eaton.address=192.0.2.10 \ --set pdu.eaton.username=greenshift \ --set pdu.eaton.passwordSecret.name=eaton-pdu-password \ --set 'pdu.eaton.hosts[0].hostId=host-001' \ --set 'pdu.eaton.hosts[0].outlets={1,2}' # address — the management address, or address:port; no https://, no path. # The Master cluster must be able to reach it over HTTPS. # hostId — as for the emulator: the GS_HOST_ID of that host's Host Mapper. # outlets — the outlets feeding that host's PSUs, by the number in the # PDU's REST path .../powerDistributions/1/outlets/<n> (the web UI shows # physical names such as A1). An outlet feeds one host only. More hosts: # --set 'pdu.eaton.hosts[1].hostId=host-002' --set 'pdu.eaton.hosts[1].outlets={3,4}' # TLS is verified. A G4 ships with a self-signed certificate: install a # CA-signed one on the PDU, or, where you cannot, --set pdu.eaton.tlsVerify=false. # Several PDUs: write the inventory yourself (fields in the chart README) and # pass it with --set-file infraMapper.inventory.content=inventory.yaml.
# Master: one window per host every 10 s, read from the PDU or the emulator kubectl -n greenshift-master logs deploy/greenshift-infra-mapper -c infra-mapper \ | grep 'Closed window' # Closed window for host-001: status=valid, avg_power_microwatts=830124000 ... # Hypervisor: the Host Mapper reports that same host_id, with Scaphandre on # 127.0.0.1:9999. Without it the host's power is not split across its VMs and # no customer cluster sees any of it. # Customer cluster, a minute or two later: power per node, above zero, in the # App Owner API and in the Dashboard's live power. curl -H 'Authorization: Bearer <token>' http://localhost:8080/v1/power/nodes # With the emulator, raise its outlet watts (panel 5) with helm upgrade # --reuse-values: the figures rise within a few minutes.