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
-
Building Your First Helm Chart:
- Explained the basics of Helm, including installation, chart structure, and deploying your first application.
-
Advanced Helm Chart Components:
- Covered advanced templating techniques, conditional logic, and usage of
values.yaml
to make charts dynamic and flexible.
- Covered advanced templating techniques, conditional logic, and usage of
Common Components for Deployment
To deploy an application using Kubernetes, you typically need the following resources:
-
Deployment:
- Manages the desired state of application pods, ensures scalability, and enables rolling updates.
-
Service:
- Exposes your application within the cluster or externally.
-
Ingress:
- Handles HTTP/S traffic routing to services using custom domains and paths.
-
ConfigMap:
- Stores non-sensitive configuration data in key-value format.
-
Secrets:
- Securely stores sensitive information like database credentials or API keys.
-
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
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 -}}
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 }}
- 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 invalues.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 -}}
- 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 }}
- 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 }}
- 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 }}
- 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 }}
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
How the Templates Work
-
Dynamic Loops: The
range
function loops over.Values.<key>
to generate resources for each defined service invalues.yaml
. -
Placeholders: Variables like
{{ $key }}
dynamically inject values. -
Defaults:
| default
ensures fallback values if specific properties are not set invalues.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/
This outputs the fully rendered Kubernetes manifests for inspection.
- Dry Run Installation:
helm install myapp ./myapp --values external_values.yaml --dry-run --debug
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
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
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)