Creating and Hosting Helm Charts for Kubernetes Operators

Build, package, and distribute your Helm charts using GitHub Pages

Featured image



Overview

Helm charts provide a powerful way to package, distribute, and deploy Kubernetes applications. This guide focuses on creating Helm charts specifically for Kubernetes Operators and hosting them on GitHub Pages, creating a free, reliable chart repository accessible to anyone.

What You'll Learn

This guide covers the complete workflow for creating and hosting Helm charts:

  • Creating a Helm chart structure for Kubernetes Operators
  • Properly packaging Helm charts for distribution
  • Setting up GitHub Pages as a Helm chart repository
  • Automating chart updates with GitHub Actions
  • Best practices for chart maintenance and versioning
graph LR A[Create Helm Chart] --> B[Package Chart] B --> C[Setup GitHub Pages] C --> D[Publish Chart] D --> E[Use Chart] style A fill:#bbdefb,stroke:#333,stroke-width:1px style B fill:#90caf9,stroke:#333,stroke-width:1px style C fill:#64b5f6,stroke:#333,stroke-width:1px style D fill:#42a5f5,stroke:#333,stroke-width:1px,color:#fff style E fill:#1976d2,stroke:#333,stroke-width:1px,color:#fff


Helm Chart Creation

Installation

Before you begin, make sure you have Helm installed:

# macOS
brew install helm

# Linux
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-3
chmod 700 get_helm.sh
./get_helm.sh

# Windows (using Chocolatey)
choco install kubernetes-helm

# Verify installation
helm version

Chart Structure for Operators

Creating a Helm chart for a Kubernetes Operator requires special consideration for CRDs and operator-specific resources.

graph TD A[Chart Root] --> B[Chart.yaml] A --> C[values.yaml] A --> D[templates/] A --> E[crds/] A --> F[README.md] D --> G[deployment.yaml] D --> H[rbac.yaml] D --> I[service.yaml] D --> J[serviceaccount.yaml] D --> K[customresource/] E --> L[crd-definitions.yaml] style A fill:#e3f2fd,stroke:#333,stroke-width:1px style B fill:#bbdefb,stroke:#333,stroke-width:1px style C fill:#bbdefb,stroke:#333,stroke-width:1px style D fill:#bbdefb,stroke:#333,stroke-width:1px style E fill:#f06292,stroke:#333,stroke-width:1px style F fill:#bbdefb,stroke:#333,stroke-width:1px

You can create a new chart using the helm create command, but for an operator, you’ll likely need to customize it:

# Create basic chart structure
helm create my-operator

# Clean up default templates we don't need
rm -rf my-operator/templates/*

Here’s a complete structure for a Kubernetes Operator chart, based on a real example (helios-lb):

helm/
├── README.md                  # Chart usage guide and parameter documentation
└── helios-lb/                 # Main chart directory
    ├── Chart.yaml            # Chart metadata (version, dependencies, etc.)
    ├── crds/                 # CRD definition files
    │   └── heliosconfig.yaml # HeliosConfig CRD specification
    ├── templates/            # Kubernetes resource templates
    │   ├── NOTES.txt        # Usage notes displayed after installation
    │   ├── _helpers.tpl     # Common template functions
    │   ├── crd-cleanup-hook.yaml    # Helm hook for CRD cleanup
    │   ├── customresource/   # CR templates
    │   │   └── config.yaml   # Example CR instance
    │   ├── deployment.yaml   # Controller deployment
    │   ├── namespace.yaml    # Namespace definition
    │   ├── rbac.yaml        # Permission settings (Role, RoleBinding)
    │   ├── service.yaml     # Service definition
    │   └── serviceaccount.yaml # ServiceAccount definition
    ├── values/              # Values files for different use cases
    │   ├── basic-values.yaml  # Basic configuration
    │   ├── port-values.yaml   # Port-based configuration
    │   └── weight-values.yaml # Weight-based configuration
    └── values.yaml          # Default values file

Key Files Explained

Chart.yaml

This file contains metadata about your chart:

apiVersion: v2
name: helios-lb
description: A Helm chart for Helios Load Balancer Operator
type: application
version: 0.1.0  # Chart version
appVersion: "1.0.0"  # Application version
maintainers:
  - name: somaz
    email: somaz@example.com
keywords:
  - kubernetes
  - operator
  - loadbalancer
sources:
  - https://github.com/somaz94/helios-lb

values.yaml

Default configuration values for your chart:

# Default values for helios-lb
# This is a YAML-formatted file.

# Operator configuration
operator:
  image:
    repository: somaz/helios-lb
    tag: latest
    pullPolicy: Always
  resources:
    limits:
      cpu: 200m
      memory: 256Mi
    requests:
      cpu: 100m
      memory: 128Mi
  nodeSelector: {}
  tolerations: []
  affinity: {}

# CRD configuration
crd:
  install: true
  cleanup: false

# Default CR configuration
defaultConfig:
  enabled: false
  name: "default-config"
  type: "weight"  # weight or port
  services:
    - name: service-a
      weight: 80
    - name: service-b
      weight: 20

RBAC Configuration (rbac.yaml)

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: {{ include "helios-lb.fullname" . }}-role
rules:
- apiGroups:
  - ""
  resources:
  - services
  - endpoints
  verbs:
  - get
  - list
  - watch
- apiGroups:
  - apps
  resources:
  - deployments
  verbs:
  - get
  - list
  - watch
  - create
  - update
  - patch
  - delete
- apiGroups:
  - lb.helios.com
  resources:
  - heliosconfigs
  - heliosconfigs/status
  verbs:
  - get
  - list
  - watch
  - create
  - update
  - patch
  - delete
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: {{ include "helios-lb.fullname" . }}-rolebinding
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: {{ include "helios-lb.fullname" . }}-role
subjects:
- kind: ServiceAccount
  name: {{ include "helios-lb.serviceAccountName" . }}
  namespace: {{ .Release.Namespace }}

CRD Definition Example

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: heliosconfigs.lb.helios.com
spec:
  group: lb.helios.com
  names:
    kind: HeliosConfig
    listKind: HeliosConfigList
    plural: heliosconfigs
    singular: heliosconfig
    shortNames:
      - hlb
  scope: Namespaced
  versions:
  - name: v1
    served: true
    storage: true
    schema:
      openAPIV3Schema:
        type: object
        properties:
          apiVersion:
            type: string
          kind:
            type: string
          metadata:
            type: object
          spec:
            type: object
            required: ["type", "services"]
            properties:
              type:
                type: string
                enum: ["weight", "port"]
              services:
                type: array
                items:
                  type: object
                  required: ["name"]
                  properties:
                    name:
                      type: string
                    weight:
                      type: integer
                      minimum: 0
                      maximum: 100
                    port:
                      type: integer
                      minimum: 1
                      maximum: 65535


Packaging Helm Charts

Once your chart structure is complete, you can package it for distribution.

sequenceDiagram participant Developer participant Local as Local Repository participant GitHub as GitHub Pages participant User Developer->>Local: helm package ./chart Note over Local: Creates chart-version.tgz Developer->>Local: helm repo index Note over Local: Creates/updates index.yaml Developer->>GitHub: Push to gh-pages branch Note over GitHub: GitHub Pages activates User->>GitHub: helm repo add myrepo https://... User->>GitHub: helm install myrepo/chart

Package Your Chart

# Navigate to your chart's parent directory
cd path/to/helm/

# Package the chart
helm package ./helios-lb

# This creates a file like helios-lb-0.1.0.tgz
Important:

Make sure your Chart.yaml has the correct version information before packaging. Every time you make changes to your chart, you should update the version and repackage it.


Hosting on GitHub Pages

GitHub Pages provides a simple way to host your Helm chart repository.

Setup Process

Step Description
Create Repository Create a GitHub repository for your chart (if you haven't already)
Create gh-pages Branch Create a special branch to host your chart repository
Add Packaged Charts Add your .tgz chart packages to this branch
Create Index Create an index.yaml file to make it a valid Helm repository
Push to GitHub Push the branch to GitHub to activate GitHub Pages
Configure GitHub Pages Ensure GitHub Pages is configured to use the gh-pages branch

Create a Clean gh-pages Branch

It’s important to create a clean gh-pages branch that contains only the repository files:

# Create and check out a new orphan branch (no history)
git checkout --orphan gh-pages

# Remove all tracked files
git rm -rf .

# Create a helm-repo directory for organization (optional)
mkdir helm-repo
cd helm-repo

# Copy your packaged chart here
cp ../../path/to/helm/helios-lb-0.1.0.tgz .

# Create the repository index
helm repo index . --url https://username.github.io/repo-name/helm-repo

# Go back to root
cd ..

# Add all files
git add .

# Commit changes
git commit -m "Add Helm chart repository"

# Push to GitHub
git push origin gh-pages

After pushing, GitHub will automatically set up GitHub Pages for your repository. You can check this in your repository settings under “Pages.”

Best Practice

You can organize your repository by creating subdirectories:

  • /helm-repo - For chart packages and index
  • /docs - For additional documentation
  • /examples - For usage examples


Adding Multiple Chart Versions

When you update your chart, you’ll want to make new versions available while maintaining older versions:

# Update Chart.yaml with new version (e.g., 0.2.0)
# Package the new version
helm package ./path/to/helm/helios-lb

# Copy to your gh-pages repo
cp helios-lb-0.2.0.tgz path/to/gh-pages/helm-repo/

# Update the index to include both versions
cd path/to/gh-pages/helm-repo/
helm repo index . --url https://username.github.io/repo-name/helm-repo

# Commit and push
git add .
git commit -m "Add chart version 0.2.0"
git push origin gh-pages


Automating with GitHub Actions

You can automate chart publishing with GitHub Actions. Here’s a workflow example:

name: Release Charts

on:
  push:
    branches:
      - main
    paths:
      - 'helm/**'

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2
        with:
          fetch-depth: 0

      - name: Configure Git
        run: |
          git config user.name "$GITHUB_ACTOR"
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"

      - name: Install Helm
        uses: azure/setup-helm@v1
        with:
          version: v3.8.1

      - name: Package Chart
        run: |
          mkdir -p .cr-release-packages
          helm package helm/helios-lb -d .cr-release-packages

      - name: Checkout gh-pages
        uses: actions/checkout@v2
        with:
          ref: gh-pages
          path: gh-pages

      - name: Update Repository
        run: |
          mkdir -p gh-pages/helm-repo
          cp .cr-release-packages/* gh-pages/helm-repo/
          cd gh-pages/helm-repo
          helm repo index . --url https://$.github.io/$/helm-repo
          
      - name: Publish Repository
        run: |
          cd gh-pages
          git add .
          git commit -m "Release new chart version" || echo "No changes to commit"
          git push origin gh-pages

This workflow automatically packages and publishes your chart when changes are made to the helm directory in the main branch.


Using Your Chart Repository

Once your repository is set up, others can use it by adding it to their Helm:

# Add the repository
helm repo add helios-lb https://username.github.io/repo-name/helm-repo

# Update repositories
helm repo update

# Search for available charts
helm search repo helios-lb

# Install the chart
helm install my-release helios-lb/helios-lb

# Install with custom values
helm install my-release helios-lb/helios-lb --values custom-values.yaml


Best Practices for Helm Charts

Chart Structure and Documentation:
  • Keep your gh-pages branch clean, containing only repository files
  • Use semantic versioning (MAJOR.MINOR.PATCH) for your charts
  • Include comprehensive documentation in README.md
  • Document all values with descriptions and defaults
  • Add NOTES.txt with post-installation instructions
  • Include examples for common use cases
Chart Maintenance:
  • Test charts thoroughly before publishing
  • Maintain backward compatibility when possible
  • Update appVersion and version in Chart.yaml appropriately
  • Include a CHANGELOG.md to track changes
  • Set appropriate dependencies and version constraints
CRD Handling:
  • Place CRDs in the crds/ directory (not templates/)
  • Consider hooks for careful CRD lifecycle management
  • Add safeguards to prevent accidental CRD deletion
  • Make CRD installation optional with values

CRD Handling Special Considerations

CRDs require special handling in Helm charts:

  1. Placement: CRDs should be placed in the crds/ directory, not in templates/. This ensures they are installed before any resources that depend on them.

  2. Upgrade Challenges: Helm doesn’t automatically upgrade CRDs to prevent data loss. Consider using a pre-upgrade hook.

  3. Deletion Protection: Add a cleanup hook to control CRD deletion:

# templates/crd-cleanup-hook.yaml
{{- if .Values.crd.cleanup }}
apiVersion: batch/v1
kind: Job
metadata:
  name: {{ include "helios-lb.fullname" . }}-crd-cleanup
  annotations:
    "helm.sh/hook": pre-delete
    "helm.sh/hook-weight": "0"
    "helm.sh/hook-delete-policy": hook-succeeded
spec:
  template:
    spec:
      serviceAccountName: {{ include "helios-lb.serviceAccountName" . }}
      containers:
      - name: kubectl
        image: bitnami/kubectl:latest
        command:
        - kubectl
        - delete
        - crd
        - heliosconfigs.lb.helios.com
      restartPolicy: Never
  backoffLimit: 1
{{- end }}


Complete Example: Helios Load Balancer

Let’s look at a complete example for using our Helios Load Balancer operator chart:

# Add repository
helm repo add helios-lb https://somaz94.github.io/helios-lb/helm-repo

# Update helm repositories
helm repo update

# Check available chart versions
helm search repo helios-lb -l

# Install with default values
helm install my-lb helios-lb/helios-lb

# Install with custom configuration
helm install my-lb helios-lb/helios-lb \
  --set operator.resources.limits.memory=512Mi \
  --set defaultConfig.enabled=true \
  --set defaultConfig.type=weight

# Install using a specific values file
helm install my-lb helios-lb/helios-lb -f values/weight-values.yaml

# Upgrade existing installation
helm upgrade my-lb helios-lb/helios-lb

# Verify installation
kubectl get pods
kubectl get heliosconfigs

# Uninstall
helm uninstall my-lb

Example Custom Values File

# weight-config.yaml
operator:
  resources:
    limits:
      cpu: 300m
      memory: 512Mi

defaultConfig:
  enabled: true
  name: "production-lb"
  type: "weight"
  services:
    - name: service-v1
      weight: 90
    - name: service-v2
      weight: 10



References