โ Helm Chart Maintenance Guide
October 8, 2025 ยท View on GitHub
This guide explains how to maintain and update the Helm chart for the Kepler Operator.
๐ Overview
The Helm chart uses a hybrid automation approach:
- Manual: Templates are hand-crafted for full control and customization
- Automated: CRDs are automatically synced from
config/crd/bases/ - Validated: Automated checks ensure consistency with kustomize deployment
This approach balances maintainability with flexibility.
๐๏ธ Chart Structure
manifests/helm/kepler-operator/
โโโ Chart.yaml # Chart metadata (version, appVersion)
โโโ values.yaml # Default configuration values
โโโ README.md # User-facing installation guide
โโโ .helmignore # Files excluded from packaging
โโโ crds/ # CRDs (auto-synced from config/crd/bases/)
โ โโโ kepler.system...powermonitors.yaml
โ โโโ kepler.system...powermonitorinternals.yaml
โโโ templates/
โโโ _helpers.tpl # Template helper functions
โโโ NOTES.txt # Post-install instructions
โโโ serviceaccount.yaml
โโโ rbac.yaml # All RBAC resources
โโโ deployment.yaml
โโโ services.yaml # Metrics + webhook services
โโโ certificate.yaml # cert-manager resources (conditional)
โโโ webhooks.yaml # Webhook configurations (conditional)
โโโ servicemonitor.yaml # Prometheus ServiceMonitor (conditional)
๐ When to Update the Helm Chart
| Change Type | Action Required | Files to Update |
|---|---|---|
| CRD Modified | Run make helm-sync-crds | Auto-synced to crds/ |
| RBAC Changed | Manual template update | templates/rbac.yaml |
| Deployment Changed | Manual template update | templates/deployment.yaml |
| New Resource Added | Create new template | templates/<resource>.yaml |
| Config Option Added | Update values & templates | values.yaml + relevant template |
| Version Bump | Update chart metadata | Chart.yaml (version, appVersion) |
๐ ๏ธ Update Workflow
1. Make Changes
# If CRDs changed, sync them
make helm-sync-crds
# If templates changed, edit manually
vim manifests/helm/kepler-operator/templates/<file>.yaml
# If configuration changed, update values
vim manifests/helm/kepler-operator/values.yaml
2. Validate Changes
# Run all validation tests (recommended)
make helm-validate # Full validation (syntax, templates, CRD sync, resources)
# Or preview rendered manifests:
make helm-template # Preview rendered manifests
3. Test Locally (Optional)
# Full end-to-end test (recommended)
./tests/helm.sh
# Or manual testing:
make helm-install # Install to cluster
kubectl get all -n kepler-operator # Verify deployment
make helm-uninstall # Clean up
# Advanced: test with existing image
./tests/helm.sh --no-build --version=0.21.0
โ๏ธ Creating/Updating Templates
Use Kustomize as Reference
Important: Always use config/default/k8s as your source of truth, NOT config/manifests.
# Generate reference manifest
make manifests
kustomize build config/default/k8s > /tmp/kustomize-ref.yaml
# Extract specific resources
./tmp/bin/yq 'select(.kind == "Deployment")' /tmp/kustomize-ref.yaml
./tmp/bin/yq 'select(.kind == "Service")' /tmp/kustomize-ref.yaml
Why config/default/k8s?
config/default/k8s: Standard Kubernetes deployment (matches Helm use case)config/manifests: OLM-specific with ClusterServiceVersion (different model)
Template Creation Steps
-
Extract resource from kustomize output
-
Replace hardcoded values with template helpers:
- Names:
{{ include "kepler-operator.fullname" . }}-<suffix> - Namespace:
{{ include "kepler-operator.namespace" . }} - Labels:
{{ include "kepler-operator.labels" . | nindent 4 }} - Images:
{{ include "kepler-operator.image" . }}
- Names:
-
Add conditional rendering if needed:
{{- if .Values.feature.enabled }} # resource definition {{- end }} -
Use values from
values.yaml:replicas: {{ .Values.replicaCount }} resources: {{- toYaml .Values.resources | nindent 12 }}
Helper Function Reference
Common helpers available in templates/_helpers.tpl:
# Chart name
{{ include "kepler-operator.name" . }}
# Full name (release-name + chart-name)
{{ include "kepler-operator.fullname" . }}
# Namespace
{{ include "kepler-operator.namespace" . }}
# Standard labels
{{ include "kepler-operator.labels" . | nindent 4 }}
# Selector labels (stable, for pod selectors)
{{ include "kepler-operator.managerLabels" . | nindent 6 }}
# Image references
{{ include "kepler-operator.image" . }} # Operator image
{{ include "kepler-operator.keplerImage" . }} # Kepler image
{{ include "kepler-operator.kubeRbacProxyImage" . }} # Kube RBAC Proxy image
# Service account name
{{ include "kepler-operator.serviceAccountName" . }}
๐งช Validation Details
The make helm-validate command runs three layers of checks:
Layer 1: Syntax Validation
helm lint manifests/helm/kepler-operator
- Validates Chart.yaml structure
- Checks template syntax
- Verifies values.yaml schema
Layer 2: Template Rendering
helm template kepler-operator manifests/helm/kepler-operator \
--set metrics.serviceMonitor.enabled=true
- Ensures templates render without errors
- Tests value substitution
- Validates conditional logic
Layer 3: Consistency Checks
./hack/helm/validate.sh
- Verifies CRD sync status (CRDs match
config/crd/bases/) - Validates all expected resources present
- Checks project-local tools available
๐ก Common Patterns
Conditional Resources
Use feature flags in values.yaml:
# values.yaml
webhooks:
enabled: true
certManager:
enabled: true
Then wrap entire templates:
# templates/certificate.yaml
{{- if .Values.webhooks.certManager.enabled }}
# Certificate and Issuer resources
{{- end }}
Multi-Resource Templates
Group related resources in single file with --- separator:
# templates/rbac.yaml
# Role
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
...
---
# RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
...
Image Configuration
Use full image paths for simplicity:
# values.yaml
operator:
image: quay.io/sustainable_computing_io/kepler-operator:0.21.0
pullPolicy: IfNotPresent
kepler:
image: quay.io/sustainable_computing_io/kepler:v0.11.0
kube-rbac-proxy:
image: quay.io/brancz/kube-rbac-proxy:v0.19.0
# _helpers.tpl
{{- define "kepler-operator.image" -}}
{{- .Values.operator.image }}
{{- end }}
{{- define "kepler-operator.keplerImage" -}}
{{- .Values.kepler.image }}
{{- end }}
{{- define "kepler-operator.kubeRbacProxyImage" -}}
{{- index .Values "kube-rbac-proxy" "image" }}
{{- end }}
This approach is simpler and allows overriding with:
helm install kepler-operator ./chart \
--set operator.image=localhost:5001/kepler-operator:dev
โ ๏ธ Common Pitfalls
โ Wrong Kustomize Overlay
kustomize build config/manifests # OLM-specific, wrong!
โ Use:
kustomize build config/default/k8s # Vanilla K8s, correct!
โ Hardcoded Names
name: kepler-operator-controller
namespace: kepler-operator
โ Use helpers:
name: {{ include "kepler-operator.fullname" . }}-controller
namespace: {{ include "kepler-operator.namespace" . }}
โ Validation Without Optional Resources
helm template kepler-operator manifests/helm/kepler-operator
# ServiceMonitor missing!
โ Enable all optionals:
helm template kepler-operator manifests/helm/kepler-operator \
--set metrics.serviceMonitor.enabled=true
โ Mutable Selector Labels
selector:
matchLabels:
{{- include "kepler-operator.labels" . | nindent 4 }}
# Includes version, breaks on upgrade!
โ Use stable selectors:
selector:
matchLabels:
{{- include "kepler-operator.managerLabels" . | nindent 4 }}
โ Namespace Template + --create-namespace Flag
# templates/namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: {{ include "kepler-operator.namespace" . }}
AND using --create-namespace flag causes conflict:
Error: namespaces "kepler-operator" already exists
โ
Use only --create-namespace flag (standard Helm practice):
helm install kepler-operator ./chart \
--namespace kepler-operator \
--create-namespace # Let Helm create namespace
Rationale: The --create-namespace flag is simpler and follows standard Helm conventions. Template-based namespace creation adds unnecessary complexity and potential conflicts.
๐ฆ Release Process
When releasing a new version:
-
Update Chart.yaml:
version: 0.22.0 # Bump chart version appVersion: 0.22.0 # Match operator version -
Sync CRDs (if changed):
make helm-sync-crds -
Validate:
make helm-validate # Runs syntax, template, CRD sync, and resource validation -
Package (optional):
make helm-package -
Commit changes:
git add manifests/helm/kepler-operator/ git commit -m "chore(helm): bump chart version to 0.22.0"
๐ Manual Build and Publish
For manual chart publishing (outside of automated release workflow):
Prerequisites
- Helm >=3.0.0
- Access to Quay.io registry (
BOT_NAMEandBOT_TOKEN) - Chart version updated in
Chart.yaml
Build and Publish Process
1. Update Chart Version:
VERSION=0.22.0
# Update Chart.yaml with the release version
sed -i "s/^version:.*/version: $VERSION/" manifests/helm/kepler-operator/Chart.yaml
sed -i "s/^appVersion:.*/appVersion: \"v$VERSION\"/" manifests/helm/kepler-operator/Chart.yaml
# Verify changes
cat manifests/helm/kepler-operator/Chart.yaml
2. Validate Chart:
make helm-validate
3. Package Chart:
# Create output directory
mkdir -p ./tmp/helm-releases
# Package the chart
helm package manifests/helm/kepler-operator -d ./tmp/helm-releases
# Optional: Rename with -helm- identifier for clarity
mv ./tmp/helm-releases/kepler-operator-${VERSION}.tgz \
./tmp/helm-releases/kepler-operator-helm-${VERSION}.tgz
4. Login to OCI Registry:
# Login to Quay.io
helm registry login quay.io/sustainable_computing_io \
--username "$BOT_NAME" \
--password "$BOT_TOKEN"
5. Push to OCI Registry:
# Push to Quay.io OCI registry
helm push ./tmp/helm-releases/kepler-operator-helm-${VERSION}.tgz \
oci://quay.io/sustainable_computing_io/charts
6. Verify Publication:
# Pull the chart to verify it's available
helm pull oci://quay.io/sustainable_computing_io/charts/kepler-operator \
--version ${VERSION} \
-d /tmp
# Inspect the downloaded chart
tar -tzf /tmp/kepler-operator-${VERSION}.tgz | head -20
Install from OCI Registry
Users can install the published chart directly from the OCI registry:
# Install latest version
helm install kepler-operator \
oci://quay.io/sustainable_computing_io/charts/kepler-operator \
--namespace kepler-operator \
--create-namespace
# Install specific version
helm install kepler-operator \
oci://quay.io/sustainable_computing_io/charts/kepler-operator \
--version 0.22.0 \
--namespace kepler-operator \
--create-namespace
Automated Release
The GitHub release workflow (.github/workflows/release.yaml) automatically:
- Updates Chart.yaml version
- Packages the chart
- Attaches chart to GitHub release
- Pushes to OCI registry at
oci://quay.io/sustainable_computing_io/charts
To trigger automated release, use the workflow dispatch with the desired version tag.
๐ Additional Resources
- Helm Best Practices: https://helm.sh/docs/chart_best_practices/
- Knowledge Base:
tmp/agents/knowledge/helm-deployment.md - Chart README:
manifests/helm/kepler-operator/README.md(user guide) - Kustomize Docs: https://kubectl.docs.kubernetes.io/references/kustomize/
๐ค Getting Help
- Review existing templates for patterns
- Check validation errors:
make helm-validateprovides specific guidance - See knowledge base for detailed explanations:
tmp/agents/knowledge/helm-deployment.md - Ask in project discussions or issues
Happy charting! โต