โŽˆ 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 TypeAction RequiredFiles to Update
CRD ModifiedRun make helm-sync-crdsAuto-synced to crds/
RBAC ChangedManual template updatetemplates/rbac.yaml
Deployment ChangedManual template updatetemplates/deployment.yaml
New Resource AddedCreate new templatetemplates/<resource>.yaml
Config Option AddedUpdate values & templatesvalues.yaml + relevant template
Version BumpUpdate chart metadataChart.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

  1. Extract resource from kustomize output

  2. 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" . }}
  3. Add conditional rendering if needed:

    {{- if .Values.feature.enabled }}
    # resource definition
    {{- end }}
    
  4. 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:

  1. Update Chart.yaml:

    version: 0.22.0        # Bump chart version
    appVersion: 0.22.0     # Match operator version
    
  2. Sync CRDs (if changed):

    make helm-sync-crds
    
  3. Validate:

    make helm-validate  # Runs syntax, template, CRD sync, and resource validation
    
  4. Package (optional):

    make helm-package
    
  5. 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_NAME and BOT_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:

  1. Updates Chart.yaml version
  2. Packages the chart
  3. Attaches chart to GitHub release
  4. 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


๐Ÿค Getting Help

  • Review existing templates for patterns
  • Check validation errors: make helm-validate provides specific guidance
  • See knowledge base for detailed explanations: tmp/agents/knowledge/helm-deployment.md
  • Ask in project discussions or issues

Happy charting! โ›ต