Crossplane configuration that installs OpenCost for Kubernetes cost monitoring and visibility.
Without OpenCost:
- No visibility into resource costs by namespace, workload, or team
- Manual spreadsheet calculations to understand Kubernetes spending
- Difficulty justifying infrastructure costs or implementing chargeback
- No historical cost data for capacity planning decisions
- Blind optimization - can't identify which workloads drive costs
With OpenCost:
- Real-time cost allocation by namespace, pod, deployment, or label
- Historical cost tracking for trend analysis and forecasting
- Integration with Prometheus for accurate resource usage metrics
- Open-source alternative to cloud vendor cost tools
- Foundation for cost-aware autoscaling and optimization
Minimal viable configuration for cost monitoring in a single cluster.
Why start here?
- Establishes cost visibility from day one, even for small teams
- Default Prometheus integration works with common setups
- No migration pain - start tracking costs immediately
- UI included for easy exploration and reporting
apiVersion: helm.hops.ops.com.ai/v1alpha1
kind: OpenCost
metadata:
name: opencost
namespace: example-env
spec:
clusterName: example-clusterThis creates an OpenCost installation with:
- Default Prometheus endpoint:
prometheus-server.prometheus-system:80 - Default cluster ID:
example-cluster(used for multi-cluster cost aggregation) - UI enabled at
http://opencost.opencost:9090 - Default namespace:
opencost
Adapt OpenCost to your monitoring stack's Prometheus endpoint.
Why customize?
- Your Prometheus might use a different service name or namespace
- Different port configurations for security or legacy reasons
- Need to connect to external Prometheus instances
apiVersion: helm.hops.ops.com.ai/v1alpha1
kind: OpenCost
metadata:
name: opencost
namespace: example-env
spec:
clusterName: production-us-east-1
labels:
team: platform
environment: production
namespace: opencost
values:
opencost:
prometheus:
internal:
serviceName: kube-prometheus-stack-prometheus
namespaceName: monitoring
port: 9090This example:
- Connects to a kube-prometheus-stack Prometheus installation
- Applies custom labels for organizational tracking
- Uses a more descriptive cluster name for multi-cluster deployments
Full control over Helm values for enterprise requirements.
Why this matters at scale?
- Custom resource limits and requests for production SLAs
- Integration with organizational RBAC policies
- Custom ingress, storage, or networking requirements
- Advanced features like cloud cost integration (AWS, GCP, Azure)
apiVersion: helm.hops.ops.com.ai/v1alpha1
kind: OpenCost
metadata:
name: opencost
namespace: platform-prod
spec:
clusterName: prod-us-east-1
providerConfigRef:
name: prod-helm-provider
kind: ProviderConfig
labels:
team: platform
environment: production
cost-center: engineering
namespace: opencost
values:
opencost:
exporter:
defaultClusterId: prod-us-east-1
cloudProviderApiKey: "{{ .Values.cloudApiKey }}"
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi
prometheus:
internal:
serviceName: prometheus-operated
namespaceName: monitoring
port: 9090
ui:
enabled: true
ingress:
enabled: true
hosts:
- cost.example.com
tls:
- secretName: opencost-tls
hosts:
- cost.example.com
persistence:
enabled: true
size: 10GiReplace all default Helm values for specialized deployments.
Why override?
- Migration from existing OpenCost installation
- Highly customized configurations that conflict with defaults
- Testing or development scenarios requiring specific chart values
apiVersion: helm.hops.ops.com.ai/v1alpha1
kind: OpenCost
metadata:
name: opencost-dev
namespace: dev-env
spec:
clusterName: dev-cluster
overrideAllValues:
fullnameOverride: opencost-dev
opencost:
exporter:
defaultClusterId: dev-testing
prometheus:
external:
enabled: true
url: https://prometheus.external.example.com
ui:
enabled: falseReference the OpenCost status in downstream resources or for monitoring:
apiVersion: example.com/v1alpha1
kind: Dashboard
spec:
opencostRef:
name: opencost
# Wait for OpenCost to be ready before creating dashboard
waitForRefs:
- kind: OpenCost
name: opencost
statusField: status.readyAccess the OpenCost UI:
# Port-forward to the UI
kubectl port-forward -n opencost svc/opencost 9090:9090
# Open browser to http://localhost:9090Query the OpenCost API:
# Get allocation data for the last 7 days
curl http://opencost.opencost:9003/allocation \
-d window=7d \
-d aggregate=namespaceThe OpenCost XRD exposes the following status fields:
status:
ready: true # Overall readiness (true when Helm release is ready)
release:
name: opencost # Name of the Helm release
namespace: opencost # Namespace where release is deployed
ready: true # Helm release readiness statusUse status.ready in dependencies and health checks. The XRD is considered ready when the Helm release reports ready status (all pods running and healthy).
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
clusterName |
string | Yes | - | Name of the target cluster. Used as default for provider config and default cluster ID. |
namespace |
string | No | "opencost" |
Namespace for the Helm release. |
name |
string | No | XR metadata.name | Helm release name. |
labels |
object | No | See below | Custom labels applied to all managed resources. Merged with default labels. |
providerConfigRef |
object | No | {name: clusterName, kind: "ProviderConfig"} |
Reference to the Helm ProviderConfig. |
providerConfigRef.name |
string | No | Value of clusterName |
Name of the ProviderConfig. |
providerConfigRef.kind |
string | No | "ProviderConfig" |
Kind of the ProviderConfig. Valid values: ProviderConfig, ClusterProviderConfig. |
values |
object | No | {} |
Helm values merged with defaults. See Default Values below. |
overrideAllValues |
object | No | - | Helm values that replace all defaults. Use when you need complete control. |
managementPolicies |
array | No | ["*"] |
Crossplane management policies for all composed resources. |
All resources receive these labels (merged with custom labels):
hops.ops.com.ai/managed: "true"
hops.ops.com.ai/opencost: <metadata.name>When using spec.values, these defaults are applied first, then your values are merged:
fullnameOverride: opencost
nameOverride: opencost
opencost:
exporter:
defaultClusterId: <spec.clusterName>
prometheus:
internal:
enabled: true
serviceName: prometheus-server
namespaceName: prometheus-system
port: 80
ui:
enabled: trueYour spec.values are merged using YAML merge semantics. To completely replace defaults, use spec.overrideAllValues instead.
Refer to the OpenCost Helm chart documentation for all available values. Common customizations:
Prometheus Configuration:
values:
opencost:
prometheus:
internal:
enabled: true
serviceName: prometheus-server
namespaceName: monitoring
port: 9090External Prometheus:
values:
opencost:
prometheus:
external:
enabled: true
url: https://prometheus.example.comCloud Cost Integration:
values:
opencost:
exporter:
cloudProviderApiKey: "YOUR_API_KEY"
aws:
accessKeyId: "AWS_ACCESS_KEY"
secretAccessKey: "AWS_SECRET_KEY"Resource Limits:
values:
opencost:
exporter:
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512MiUI Ingress:
values:
opencost:
ui:
enabled: true
ingress:
enabled: true
hosts:
- cost.example.comThis XRD creates a single Helm release:
- Helm Release (
helm.m.crossplane.io/v1beta1/Release) - Deploys the OpenCost chart (v2.5.5) from the official OpenCost Helm repository (https://opencost.github.io/opencost-helm-chart).
The Helm release manages:
- Deployment - OpenCost exporter and optional UI
- Service - Exposes OpenCost API (port 9003) and UI (port 9090)
- ServiceAccount - RBAC for reading cluster resources
- ClusterRole/ClusterRoleBinding - Permissions for cost allocation
This configuration requires:
- provider-helm (>=v1.0.6) - Manages Helm releases
- function-auto-ready (>=v0.6.0) - Automatic readiness detection
The Helm provider must be configured with credentials for the target cluster. Typically this is a ProviderConfig referencing a kubeconfig secret or using in-cluster authentication.
OpenCost XR
|
v
Helm Release (opencost chart v2.5.5)
|
+-- Deployment (exporter + UI)
+-- Service (API + UI ports)
+-- ServiceAccount + RBAC
+-- ConfigMap (Prometheus config)
Deploy OpenCost with all defaults:
apiVersion: helm.hops.ops.com.ai/v1alpha1
kind: OpenCost
metadata:
name: opencost
namespace: example-env
spec:
clusterName: example-clusterSee examples/opencosts/minimal.yaml
Deploy with custom Prometheus integration:
apiVersion: helm.hops.ops.com.ai/v1alpha1
kind: OpenCost
metadata:
name: opencost
namespace: example-env
spec:
clusterName: example-cluster
labels:
team: platform
namespace: opencost
values:
opencost:
prometheus:
internal:
serviceName: kube-prometheus-stack-prometheus
namespaceName: monitoring
port: 9090See examples/opencosts/standard.yaml
- up CLI - Upbound's Crossplane tooling
- crossplane CLI - Crossplane validation
- kubectl - For E2E testing
Render examples to YAML:
make render # Render all examples
make render:minimal # Render specific example
make render:standardValidate rendered output:
make validate # Validate all examples
make validate:minimal # Validate specific exampleRun unit tests:
make test # Run KCL render testsRun E2E tests:
make e2e # Run E2E tests (requires cluster)Build package:
make build # Build Crossplane packagePublish package:
make publish tag=v1.0.0 # Build and push to registryClean build artifacts:
make clean # Remove _output and .up directories- Modify templates in
functions/render/ - Run
make renderto see generated output - Run
make validateto verify schema compliance - Run
make testto run unit tests - Apply to a test cluster and verify behavior
.
├── apis/
│ └── opencosts/
│ ├── composition.yaml # Pipeline composition
│ ├── configuration.yaml # Package metadata
│ └── definition.yaml # XRD schema
├── examples/
│ └── opencosts/
│ ├── minimal.yaml # Minimal example
│ └── standard.yaml # Standard example
├── functions/
│ └── render/
│ ├── 000-state-init.yaml.gotmpl
│ ├── 001-state-observed-helm.yaml.gotmpl
│ ├── 005-state-opencost.yaml.gotmpl
│ ├── 010-state-status.yaml.gotmpl
│ ├── 200-helm-release-opencost.yaml.gotmpl
│ └── 999-status.yaml.gotmpl
├── tests/ # KCL and E2E tests
├── Makefile # Build and test commands
└── upbound.yaml # Project configuration
Apache-2.0