LabHub

Blog

Helm Architecture Internals: Design Philosophy and Structure of Helm 3

한국어English日本語


1. Helm 3 Architecture Overview

1.1 Evolution from Helm 2 to Helm 3

Helm 2 required a server-side component called Tiller within the cluster. While Tiller managed releases and deployed charts, it had serious security issues.

Tiller's Problems:

Helm 3 completely removed Tiller. Now the Helm CLI communicates directly with the Kubernetes API server, using the authentication credentials from kubeconfig.

1.2 Client-Only Architecture

+------------------+       +---------------------+
|   Helm CLI       | ----> |  Kubernetes API     |
|  (Client)        |       |  Server             |
+------------------+       +---------------------+
        |                           |
        v                           v
  +-----------+              +-----------+
  | Chart     |              | Release   |
  | Repository|              | Storage   |
  +-----------+              | (Secrets) |
                             +-----------+

Helm 3's architecture is simple:

  1. Helm CLI: Performs chart rendering, API calls, and release management entirely client-side
  2. Kubernetes API Server: Actual execution of resource creation/modification/deletion
  3. Release Storage: Stores release metadata as Secrets or ConfigMaps

1.3 Improved Security Model

# Helm 3 uses kubeconfig context directly
# RBAC is naturally applied
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: helm-deployer
  namespace: production
rules:
  - apiGroups: ['', 'apps', 'batch']
    resources: ['deployments', 'services', 'configmaps', 'secrets']
    verbs: ['get', 'list', 'create', 'update', 'patch', 'delete']

In Helm 3, the user's kubeconfig context is applied directly, enabling fine-grained permission control through Kubernetes RBAC.


2. Release Storage

2.1 Secret-Based Storage

Helm 3 stores release information in Kubernetes Secret resources by default.

# Query release Secrets
kubectl get secrets -l owner=helm -n default

# Secret name format: sh.helm.release.v1.RELEASE_NAME.vREVISION
# Example: sh.helm.release.v1.my-app.v1

Each Secret contains the following data, gzip-compressed and base64-encoded:

2.2 ConfigMap-Based Storage

ConfigMap can be used instead of Secrets:

# Use ConfigMap driver
export HELM_DRIVER=configmap
helm install my-app ./my-chart

2.3 Storage Driver Comparison

DriverProsCons
secret (default)etcd encryption support, RBAC access controlSecret resource size limit (1MB)
configmapNo Secret access permissions neededData stored in plaintext
sqlLarge-scale release managementExternal DB required, complex setup
memoryTesting purposesNo persistent storage

3. Chart Structure

3.1 Basic Directory Structure

my-chart/
  Chart.yaml          # Chart metadata (required)
  Chart.lock          # Dependency lock file
  values.yaml         # Default configuration values (required)
  values.schema.json  # Values schema validation
  charts/             # Dependency charts (subcharts)
  crds/               # CRD manifests
  templates/          # Go template files
    deployment.yaml
    service.yaml
    ingress.yaml
    _helpers.tpl      # Named template helpers
    NOTES.txt         # Release notes template
    tests/            # Test Pod definitions
      test-connection.yaml

3.2 Chart.yaml Details

apiVersion: v2 # Must be v2 for Helm 3
name: my-application
description: A Helm chart for my application
type: application # application or library
version: 1.2.3 # Chart version (SemVer)
appVersion: '2.0.0' # Application version
kubeVersion: '>=1.25.0' # Supported Kubernetes version range
home: https://example.com
sources:
  - https://github.com/example/my-app
maintainers:
  - name: developer
    email: dev@example.com
icon: https://example.com/icon.png
keywords:
  - app
  - web
annotations:
  artifacthub.io/changes: |
    - kind: added
      description: Initial release
dependencies:
  - name: postgresql
    version: '12.x.x'
    repository: 'https://charts.bitnami.com/bitnami'
    condition: postgresql.enabled
    tags:
      - database

3.3 CRD Directory

Files in the crds/ directory receive special treatment:

# crds/my-crd.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: myresources.example.com
spec:
  group: example.com
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
  scope: Namespaced
  names:
    plural: myresources
    singular: myresource
    kind: MyResource

4. Template Engine Basics

4.1 Go Template Basic Syntax

Helm is based on Go's text/template package with additional Sprig library functions.

# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: { { include "my-chart.fullname" . } }
  labels: { { - include "my-chart.labels" . | nindent 4 } }
spec:
  replicas: { { .Values.replicaCount } }
  selector:
    matchLabels: { { - include "my-chart.selectorLabels" . | nindent 6 } }
  template:
    metadata:
      labels: { { - include "my-chart.selectorLabels" . | nindent 8 } }
    spec:
      containers:
        - name: { { .Chart.Name } }
          image: '{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}'
          ports:
            - containerPort: { { .Values.service.port } }

4.2 Built-in Objects

Key built-in objects available in Helm templates:

ObjectDescription
.ReleaseRelease info (Name, Namespace, Revision, IsUpgrade, IsInstall)
.ValuesMerged result of values.yaml and user-provided values
.ChartContents of Chart.yaml
.CapabilitiesCluster capability info (API versions, Kubernetes version)
.TemplateCurrent template info (Name, BasePath)
.FilesAccess to chart files
# .Release object usage example
metadata:
  annotations:
    helm.sh/release-name: {{ .Release.Name }}
    helm.sh/release-namespace: {{ .Release.Namespace }}
    helm.sh/revision: {{ .Release.Revision | quote }}

# .Capabilities usage example
{{- if .Capabilities.APIVersions.Has "networking.k8s.io/v1" }}
apiVersion: networking.k8s.io/v1
{{- else }}
apiVersion: networking.k8s.io/v1beta1
{{- end }}

4.3 Sprig Function Examples

# String functions
name: {{ .Values.name | upper }}           # Uppercase
name: {{ .Values.name | lower }}           # Lowercase
name: {{ .Values.name | title }}           # Title case
name: {{ .Values.name | trim }}            # Trim whitespace
name: {{ .Values.name | trunc 63 }}        # Truncate to 63 chars

# Default values
image: {{ .Values.image.tag | default "latest" }}

# Conditional required values
name: {{ required "A name is required" .Values.name }}

# Encoding
data: {{ .Values.secret | b64enc }}

# Date
timestamp: {{ now | date "2006-01-02T15:04:05Z07:00" }}

5. Dependency Management

5.1 Declaring Dependencies in Chart.yaml

# Chart.yaml
dependencies:
  - name: redis
    version: '17.x.x'
    repository: 'https://charts.bitnami.com/bitnami'
    condition: redis.enabled
    tags:
      - cache
  - name: postgresql
    version: '12.x.x'
    repository: 'https://charts.bitnami.com/bitnami'
    condition: postgresql.enabled
    tags:
      - database
  - name: common
    version: '2.x.x'
    repository: 'https://charts.bitnami.com/bitnami'
    import-values:
      - child: image
        parent: defaultImage

5.2 Chart.lock File

# Download dependencies and generate lock file
helm dependency update ./my-chart

# Build with exact versions from existing Chart.lock
helm dependency build ./my-chart

The Chart.lock file ensures reproducible builds:

# Chart.lock
dependencies:
  - name: redis
    repository: https://charts.bitnami.com/bitnami
    version: 17.15.2
  - name: postgresql
    repository: https://charts.bitnami.com/bitnami
    version: 12.10.0
digest: sha256:abc123def456...
generated: '2026-03-20T10:00:00.000000000Z'

5.3 condition and tags

condition: Enables subcharts only when a specific value is true

# values.yaml
redis:
  enabled: true
postgresql:
  enabled: false # Disable PostgreSQL subchart

tags: Enable/disable subcharts by tag group

# values.yaml
tags:
  cache: true # Enable all dependencies with cache tag
  database: false # Disable all dependencies with database tag

condition takes precedence over tags.

5.4 Subchart Value Overrides

# Override subchart values from parent values.yaml
redis:
  enabled: true
  architecture: standalone
  auth:
    enabled: false
  master:
    persistence:
      size: 1Gi

postgresql:
  enabled: true
  auth:
    postgresPassword: 'my-password'
    database: mydb

5.5 Global Values

# values.yaml
global:
  imageRegistry: myregistry.io
  imagePullSecrets:
    - name: my-pull-secret
  storageClass: fast-ssd

Values under global are accessible from all subcharts.


6. OCI Registry Support

6.1 OCI-Based Chart Management

OCI registry support is GA (Generally Available) since Helm 3.8:

# Login to registry
helm registry login registry.example.com

# Package and push chart as OCI artifact
helm package ./my-chart
helm push my-chart-1.0.0.tgz oci://registry.example.com/charts

# Install chart from OCI registry
helm install my-release oci://registry.example.com/charts/my-chart --version 1.0.0

# Show chart info
helm show chart oci://registry.example.com/charts/my-chart --version 1.0.0

7. Summary

Helm 3's architecture centers on security, simplicity, and extensibility:

  1. Tiller Removal: Operates with CLI only, no server component in the cluster
  2. RBAC Native: kubeconfig-based auth naturally integrates with Kubernetes RBAC
  3. Secret-Based Release Storage: etcd encryption support, release history management
  4. Structured Charts: Clear separation of concerns with Chart.yaml, values.yaml, templates/
  5. OCI Registry Support: Manage charts on the same infrastructure as container images

The next post will analyze the internals of the Helm template engine in greater depth.

Comments

No comments yet.

Sign in to leave a comment