DEV Community

Cover image for Building Deployment-Ready Helm Charts
Manjunath Kotabal
Manjunath Kotabal

Posted on • Edited on

Building Deployment-Ready Helm Charts

Helm is a powerful tool for managing Kubernetes applications, enabling you to package, configure, and deploy resources efficiently. In this guide, we’ll walk you through creating deployment-ready Helm charts by building reusable templates for common Kubernetes components.


Recap of Previous Blogs

  1. Building Your First Helm Chart:

    • Explained the basics of Helm, including installation, chart structure, and deploying your first application.
  2. Advanced Helm Chart Components:

    • Covered advanced templating techniques, conditional logic, and usage of values.yaml to make charts dynamic and flexible.

Common Components for Deployment

To deploy an application using Kubernetes, you typically need the following resources:

  1. Deployment:

    • Manages the desired state of application pods, ensures scalability, and enables rolling updates.
  2. Service:

    • Exposes your application within the cluster or externally.
  3. Ingress:

    • Handles HTTP/S traffic routing to services using custom domains and paths.
  4. ConfigMap:

    • Stores non-sensitive configuration data in key-value format.
  5. Secrets:

    • Securely stores sensitive information like database credentials or API keys.
  6. Service Account:

    • Provides an identity for application pods to interact with the cluster or external systems.

Steps to Build a Deployment-Ready Helm Chart

Step 1: Create a Helm Chart

Run the following command to generate a Helm chart structure:

helm create myapp
Enter fullscreen mode Exit fullscreen mode

Delete all files in the myapp/templates/ folder to start fresh.


Step 2: Define Templates

Use the following templates to define reusable and configurable resources.

_helpers.tpl

{{- define "namespace" -}}
{{- printf "%s-%s-%s" .Values.productName .Values.clientName .Values.envName -}}
{{- end -}}

{{- define "serviceAccountName" -}}
{{- printf "%s-%s-%s-sa" .Values.productName .Values.clientName .Values.envName -}}
{{- end -}}
Enter fullscreen mode Exit fullscreen mode

These helpers define reusable patterns for naming resources dynamically based on values.yaml.

namespace: Constructs a namespace identifier by combining productName, clientName, and envName.
serviceAccountName: Generates a unique name for the service account using the same components.

  • Purpose: These functions dynamically generate names for namespaces and service accounts based on values defined in values.yaml.
  • Usage: The template keyword invokes these functions in other templates.

Deployment Template

{{- range $key, $service := .Values.services }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ $key }}-deploy
  labels:
    app: {{ $key }}
spec:
  replicas: {{ $service.replicas | default 2 }}
  selector:
    matchLabels:
      app: {{ $key }}
  template:
    metadata:
      labels:
        app: {{ $key }}
    spec:
      serviceAccountName: {{ template "serviceAccountName" $ }}
      containers:
      - name: {{ $key }}
        image: {{ $.Values.ecr }}/{{ $key }}:{{ $service.version }}
        ports:
        - containerPort: {{ $service.port }}
        resources:
          requests:
            memory: {{ $service.requestMemory | default $.Values.defaultRequestMemory | default "1024Mi"}}
{{- end }}
Enter fullscreen mode Exit fullscreen mode
  • For Loop: The range function iterates over each item in the .Values.services object from values.yaml.
    • For each service key-value pair, a Deployment resource is created.
  • Dynamic Values: {{ $key }} is the name of the service. {{ $service.<property> }} retrieves properties (like replicas, port, etc.) from the corresponding entry in values.yaml.

ConfigMap Template

{{- range $key, $cm := .Values.configmaps }}
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ $key }}
  annotations:
    ssm-loader:  "true"
    ssm-parameter-name: {{ $cm | default "null" }}
    region: {{ $.Values.region | default "us-west-2" }}
{{- end -}}
Enter fullscreen mode Exit fullscreen mode
  • This template loops through .Values.configmaps to create Configmap resources for each application.
  • You can use annotations.ssm-parameter-name for fetching the config from aws parameter store or any other configuration management system.
  • And add other annotations to serve your application needs

Service Template

{{- range $key, $service := .Values.services }}
---
apiVersion: v1
kind: Service
metadata:
  name: {{ $key }}-service
spec:
  selector:
    app: {{ $key }}
  ports:
  - protocol: TCP
    port: {{ $service.port }}
    targetPort: {{ $service.port }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode
  • This template loops through .Values.services to create a Service resource for each application.
  • The selector ensures the Service routes traffic to the correct pods (matched by app: {{ $key }}).

Ingress Template

{{ $c := 1 | int }}
{{- range $key, $service := .Values.services }}
{{- $albdomain := printf "%s-%s-%s.%s" $key $.Values.clientName $.Values.envName $.Values.topLevelDomain -}}

{{- if $service.alb }}
---
# ALB Ingress Configuration
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ $key }}-alb-ingress
  annotations:
    kubernetes.io/ingress.class: "alb"
    alb.ingress.kubernetes.io/group.name: {{ template "namespace" $}}-ingress-group
    alb.ingress.kubernetes.io/group.order: '{{ $c }}'
    alb.ingress.kubernetes.io/scheme: internet-facing
    alb.ingress.kubernetes.io/target-type: ip
    alb.ingress.kubernetes.io/listen-ports: '[{"HTTPS":443}]'
    alb.ingress.kubernetes.io/certificate-arn: {{ $.Values.certArn }}
    alb.ingress.kubernetes.io/healthcheck-protocol: HTTP
    alb.ingress.kubernetes.io/healthcheck-port: '{{ $service.port }}'
    alb.ingress.kubernetes.io/healthcheck-path: {{ $service.healthCheck | default "/util/health" }}
    alb.ingress.kubernetes.io/healthcheck-interval-seconds: {{ $service.healthCheckInterval | default "'30'" }}
    alb.ingress.kubernetes.io/healthcheck-timeout-seconds: {{ $service.healthCheckTimeout | default "'5'" }}
    alb.ingress.kubernetes.io/success-codes: {{ $service.healthCheckSuccessCode | default "'200'" }}
    alb.ingress.kubernetes.io/healthy-threshold-count: '2'
    alb.ingress.kubernetes.io/unhealthy-threshold-count: '2'
    alb.ingress.kubernetes.io/load-balancer-attributes: 'routing.http2.enabled=true,idle_timeout.timeout_seconds=600'
    alb.ingress.kubernetes.io/target-group-attributes: 'deregistration_delay.timeout_seconds=30, slow_start.duration_seconds=0'
    alb.ingress.kubernetes.io/ssl-policy: ELBSecurityPolicy-TLS-1-2-Ext-2018-06
spec:
  # ALB Ingress specification
  rules:
  - host: {{ default $albdomain $service.domain }}
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ $key }}-service
            port:
              number: {{ $service.port }}
    # ALB spec continues...
{{ $c = add1 $c }}
{{- end }}

{{- if $service.nlb }}
---
# NLB Ingress Configuration
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ $key }}-nlb-ingress
  annotations:
    kubernetes.io/ingress.class: "nginx"
    nginx.ingress.kubernetes.io/use-forwarded-headers: "true"
    nginx.ingress.kubernetes.io/ssl-redirect: "false"
    nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
  # NLB Ingress specification
  rules:
  - host: {{ $service.nlbdomain }} 
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: {{ $key }}-service
            port:
              number: {{ $service.port }}
    # NLB spec continues...
{{ $c = add1 $c }}
{{- end }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode
  • Creates an Ingress for each service.
  • Routes HTTP traffic to the correct Service.

Secrets Template

{{- range $key, $secret := .Values.secrets }}
---
apiVersion: v1
kind: Secret
type: Opaque
metadata:
  name: {{ $key }}
  annotations:
    secret-loader: "true"
    secret-name: {{ $secret | default "null" }}
    region: {{ $.Values.region | default "us-west-2" }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode
  • This template loops through .Values.secrets to create Secret resources for each application.
  • You can use annotations.secret-name for fetching the secrets from aws secrets manager or any other secrets management system.
  • And add other annotations to serve your application needs

ServiceAccount Template

{{- if .Values.createServiceAccount }}
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: {{ template "serviceAccountName" . }}
  annotations:
    eks.amazonaws.com/role-arn: {{ .Values.serviceAccountRole }}
{{- end }}
Enter fullscreen mode Exit fullscreen mode

Step 3: Customize values.yaml

Here’s an example values.yaml for reference:

productName: myapp
clientName: generic
envName: dev
ecr: mydockerhub.io
certArn: arn:aws:acm:<region>:<account_id>:certificate/<certificate_id>
region: us-west-2
defaultRequestMemory: 512Mi
services:
  app-service:
    replicas: 3
    port: 8080
    healthCheck : /actuator/health
    version: latest
    configList:
      - app-config
    secretList:
      - db-secret
    nlb: true
    nlbdomain: app-service.example.com
  frontend-service:
    replicas: 3
    port: 8080
    healthCheck : /actuator/health
    version: latest
    configList:
      - app-config
    secretList:
      - db-secret
    alb:true
configmaps:
  app-config: myapp-config
secrets:
  db-secret: myapp-db-credentials
createServiceAccount: true
serviceAccountRole: arn:aws:iam::123456789012:role/myapp-role
Enter fullscreen mode Exit fullscreen mode

How the Templates Work

  • Dynamic Loops: The range function loops over .Values.<key> to generate resources for each defined service in values.yaml.
  • Placeholders: Variables like {{ $key }} dynamically inject values.
  • Defaults: | default ensures fallback values if specific properties are not set in values.yaml.

Step 4: Testing Templates Without Deployment

You can validate and render templates without applying them using helm template or helm install --dry-run:

  • View Rendered Templates:
  helm template myapp/
Enter fullscreen mode Exit fullscreen mode

This outputs the fully rendered Kubernetes manifests for inspection.

  • Dry Run Installation:
  helm install myapp ./myapp --values external_values.yaml --dry-run --debug
Enter fullscreen mode Exit fullscreen mode

Simulates the deployment and provides rendered manifests and potential issues.

NOTE : here external_values.yaml can be used to override you default values.yaml

Step 5: Deploying the Chart

Once verified, deploy the chart to your Kubernetes cluster:

helm install myapp ./myapp --values values.yaml
Enter fullscreen mode Exit fullscreen mode

This will:

Render the templates using values from values.yaml.
Apply the resulting Kubernetes manifests to the cluster.

To upgrade an existing deployment:

helm upgrade myapp ./myapp --values values.yaml
Enter fullscreen mode Exit fullscreen mode

Conclusion

This guide has shown how to build a reusable Helm chart, explaining the templates and their dynamic behavior. By using loops and placeholders, you can define a single, flexible chart that scales with your application needs. Use helm template and --dry-run to validate before deploying, ensuring a smooth rollout to your Kubernetes environment.

Checkout my article on packaging and installing helm charts with argocd : https://dev.to/manjunath_kotabal_3d1e736/packaging-and-installing-helm-chart-with-argocd-3n7a

Top comments (0)