Skip to main content

Helm Chart Wrapper

A standardized Helm chart wrapper that provides consistent deployment patterns, configuration management, and best practices for Kubernetes applications across different environments.

Overview

This archetype creates a wrapper Helm chart that encapsulates common deployment patterns and configurations, making it easier to deploy applications consistently across multiple environments while maintaining flexibility and customization options.

Technology Stack

  • Helm 3.x: Kubernetes package manager
  • Kubernetes: Container orchestration platform
  • YAML: Configuration and templating
  • Go Templates: Helm templating engine
  • Chart Testing: Automated chart validation
  • ArgoCD/Flux: GitOps deployment (optional)

Key Features

Standardized Deployment

  • Common Patterns: Deployment, Service, Ingress, ConfigMap, Secret
  • Multi-Environment: Environment-specific configurations
  • Resource Management: CPU, memory, and storage specifications
  • Security Policies: Pod security standards and RBAC

Configuration Management

  • Values Hierarchy: Global, environment, and application-specific values
  • Secret Management: External secret integration
  • Feature Flags: Toggle functionality through configuration
  • Environment Promotion: Consistent configuration across environments

Operational Excellence

  • Health Checks: Readiness and liveness probes
  • Monitoring: ServiceMonitor and alerting integration
  • Scaling: HPA and resource management
  • Backup: Persistent volume backup configurations

Project Structure

helm-chart-wrapper/
├── Chart.yaml # Chart metadata
├── values.yaml # Default values
├── values/ # Environment-specific values
│ ├── development.yaml
│ ├── staging.yaml
│ └── production.yaml
├── templates/
│ ├── deployment.yaml # Application deployment
│ ├── service.yaml # Service definition
│ ├── ingress.yaml # Ingress configuration
│ ├── configmap.yaml # Configuration data
│ ├── secret.yaml # Secret data
│ ├── serviceaccount.yaml # Service account
│ ├── rbac.yaml # RBAC configuration
│ ├── hpa.yaml # Horizontal Pod Autoscaler
│ ├── pdb.yaml # Pod Disruption Budget
│ ├── networkpolicy.yaml # Network policies
│ ├── servicemonitor.yaml # Prometheus monitoring
│ ├── _helpers.tpl # Template helpers
│ └── tests/
│ ├── test-connection.yaml
│ └── test-deployment.yaml
├── charts/ # Subcharts directory
├── crds/ # Custom Resource Definitions
├── docs/ # Documentation
│ ├── README.md
│ ├── configuration.md
│ └── examples/
├── ci/ # CI test values
│ ├── default-values.yaml
│ └── test-values.yaml
└── .helmignore # Helm ignore patterns

Chart Configuration

Chart.yaml

apiVersion: v2
name: application-wrapper
description: A standardized Helm chart wrapper for application deployment
type: application
version: 1.0.0
appVersion: "1.0.0"
home: https://github.com/your-org/helm-chart-wrapper
sources:
- https://github.com/your-org/helm-chart-wrapper
maintainers:
- name: DevOps Team
email: devops@your-org.com
keywords:
- application
- deployment
- wrapper
annotations:
category: Infrastructure
dependencies:
- name: postgresql
version: "12.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: postgresql.enabled
- name: redis
version: "17.x.x"
repository: "https://charts.bitnami.com/bitnami"
condition: redis.enabled

Default Values (values.yaml)

# Global configuration
global:
imageRegistry: ""
imagePullSecrets: []
storageClass: ""

# Application configuration
app:
name: myapp
version: "1.0.0"

image:
registry: docker.io
repository: myorg/myapp
tag: "1.0.0"
pullPolicy: IfNotPresent
pullSecrets: []

# Deployment configuration
deployment:
replicaCount: 3
strategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1
maxSurge: 1

# Security context
securityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001

# Pod security context
podSecurityContext:
runAsNonRoot: true
runAsUser: 1001
fsGroup: 1001

# Resource management
resources:
requests:
cpu: 100m
memory: 128Mi
limits:
cpu: 500m
memory: 512Mi

# Horizontal Pod Autoscaler
hpa:
enabled: true
minReplicas: 3
maxReplicas: 10
targetCPUUtilizationPercentage: 70
targetMemoryUtilizationPercentage: 80

# Service configuration
service:
type: ClusterIP
port: 80
targetPort: 8080
annotations: {}

# Ingress configuration
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: letsencrypt-prod
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-tls
hosts:
- myapp.example.com

# Health checks
healthChecks:
readinessProbe:
httpGet:
path: /health/ready
port: 8080
initialDelaySeconds: 10
periodSeconds: 5
livenessProbe:
httpGet:
path: /health/live
port: 8080
initialDelaySeconds: 30
periodSeconds: 10

# Environment variables
env:
- name: APP_ENV
value: production
- name: LOG_LEVEL
value: info

# Configuration and secrets
configMap:
enabled: true
data:
app.properties: |
server.port=8080
logging.level.root=INFO

secret:
enabled: true
type: Opaque
data:
database-password: ""
api-key: ""

# Persistent volumes
persistence:
enabled: false
accessMode: ReadWriteOnce
size: 1Gi
storageClass: ""

# ServiceAccount and RBAC
serviceAccount:
create: true
annotations: {}
name: ""

rbac:
create: true
rules: []

# Pod Disruption Budget
podDisruptionBudget:
enabled: true
minAvailable: 1

# Network policies
networkPolicy:
enabled: false
ingress: []
egress: []

# Monitoring
monitoring:
enabled: true
serviceMonitor:
enabled: true
interval: 30s
path: /metrics
port: metrics

# Node selector and affinity
nodeSelector: {}
tolerations: []
affinity: {}

# Dependencies
postgresql:
enabled: false
auth:
postgresPassword: ""
database: myapp

redis:
enabled: false
auth:
password: ""

Template Examples

Deployment Template (templates/deployment.yaml)

apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "chart.fullname" . }}
labels:
{{- include "chart.labels" . | nindent 4 }}
spec:
{{- if not .Values.hpa.enabled }}
replicas: {{ .Values.deployment.replicaCount }}
{{- end }}
strategy:
{{- toYaml .Values.deployment.strategy | nindent 4 }}
selector:
matchLabels:
{{- include "chart.selectorLabels" . | nindent 6 }}
template:
metadata:
annotations:
checksum/config: {{ include (print $.Template.BasePath "/configmap.yaml") . | sha256sum }}
checksum/secret: {{ include (print $.Template.BasePath "/secret.yaml") . | sha256sum }}
labels:
{{- include "chart.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.image.pullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "chart.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.deployment.podSecurityContext | nindent 8 }}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.deployment.securityContext | nindent 12 }}
image: "{{ include "chart.image" . }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
{{- if .Values.monitoring.enabled }}
- name: metrics
containerPort: 9090
protocol: TCP
{{- end }}
{{- if .Values.healthChecks.livenessProbe }}
livenessProbe:
{{- toYaml .Values.healthChecks.livenessProbe | nindent 12 }}
{{- end }}
{{- if .Values.healthChecks.readinessProbe }}
readinessProbe:
{{- toYaml .Values.healthChecks.readinessProbe | nindent 12 }}
{{- end }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
env:
{{- toYaml .Values.env | nindent 12 }}
{{- if .Values.configMap.enabled }}
volumeMounts:
- name: config
mountPath: /app/config
readOnly: true
{{- end }}
{{- if .Values.persistence.enabled }}
- name: data
mountPath: /app/data
{{- end }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
volumes:
{{- if .Values.configMap.enabled }}
- name: config
configMap:
name: {{ include "chart.fullname" . }}
{{- end }}
{{- if .Values.persistence.enabled }}
- name: data
persistentVolumeClaim:
claimName: {{ include "chart.fullname" . }}
{{- end }}

Service Template (templates/service.yaml)

apiVersion: v1
kind: Service
metadata:
name: {{ include "chart.fullname" . }}
labels:
{{- include "chart.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: http
{{- if .Values.monitoring.enabled }}
- port: 9090
targetPort: 9090
protocol: TCP
name: metrics
{{- end }}
selector:
{{- include "chart.selectorLabels" . | nindent 4 }}

Helper Templates (templates/_helpers.tpl)

{{/*
Expand the name of the chart.
*/}}
{{- define "chart.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Create a default fully qualified app name.
*/}}
{{- define "chart.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "chart.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
Common labels
*/}}
{{- define "chart.labels" -}}
helm.sh/chart: {{ include "chart.chart" . }}
{{ include "chart.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{/*
Selector labels
*/}}
{{- define "chart.selectorLabels" -}}
app.kubernetes.io/name: {{ include "chart.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
Create the name of the service account to use
*/}}
{{- define "chart.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "chart.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}

{{/*
Create image name
*/}}
{{- define "chart.image" -}}
{{- $registry := .Values.global.imageRegistry | default .Values.image.registry -}}
{{- $repository := .Values.image.repository -}}
{{- $tag := .Values.image.tag | default .Chart.AppVersion -}}
{{- printf "%s/%s:%s" $registry $repository $tag -}}
{{- end }}

Environment-Specific Configurations

Development (values/development.yaml)

deployment:
replicaCount: 1

hpa:
enabled: false

resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 256Mi

ingress:
hosts:
- host: myapp-dev.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-dev-tls
hosts:
- myapp-dev.example.com

env:
- name: APP_ENV
value: development
- name: LOG_LEVEL
value: debug

monitoring:
enabled: false

postgresql:
enabled: true
auth:
postgresPassword: devpassword
database: myapp_dev

redis:
enabled: true
auth:
password: devpassword

Production (values/production.yaml)

deployment:
replicaCount: 5

hpa:
enabled: true
minReplicas: 5
maxReplicas: 20

resources:
requests:
cpu: 200m
memory: 256Mi
limits:
cpu: 1000m
memory: 1Gi

ingress:
annotations:
nginx.ingress.kubernetes.io/rate-limit: "100"
nginx.ingress.kubernetes.io/rate-limit-window: "1m"
hosts:
- host: myapp.example.com
paths:
- path: /
pathType: Prefix
tls:
- secretName: myapp-prod-tls
hosts:
- myapp.example.com

env:
- name: APP_ENV
value: production
- name: LOG_LEVEL
value: warn

monitoring:
enabled: true
serviceMonitor:
enabled: true

podDisruptionBudget:
enabled: true
minAvailable: 2

networkPolicy:
enabled: true
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080

postgresql:
enabled: false # Use external database

redis:
enabled: false # Use external cache

Deployment Commands

Basic Deployment

# Install the chart
helm install myapp ./helm-chart-wrapper \
--namespace myapp \
--create-namespace \
--values values/production.yaml

# Upgrade the deployment
helm upgrade myapp ./helm-chart-wrapper \
--namespace myapp \
--values values/production.yaml

# Rollback to previous version
helm rollback myapp 1 --namespace myapp

Advanced Deployment

# Deploy with custom values
helm install myapp ./helm-chart-wrapper \
--namespace myapp \
--create-namespace \
--values values/production.yaml \
--set image.tag=2.0.0 \
--set deployment.replicaCount=10

# Deploy with secrets from external source
helm install myapp ./helm-chart-wrapper \
--namespace myapp \
--create-namespace \
--values values/production.yaml \
--set-file secret.data.database-password=/path/to/db-password \
--set-file secret.data.api-key=/path/to/api-key

# Dry run deployment
helm install myapp ./helm-chart-wrapper \
--namespace myapp \
--values values/production.yaml \
--dry-run --debug

Testing and Validation

Chart Testing

# Lint the chart
helm lint ./helm-chart-wrapper

# Template and validate
helm template myapp ./helm-chart-wrapper \
--values values/production.yaml \
--validate

# Test with chart testing tool
ct lint --config .github/ct.yaml
ct install --config .github/ct.yaml

Test Configuration (.github/ct.yaml)

remote: origin
target-branch: main
chart-dirs:
- .
chart-repos:
- bitnami=https://charts.bitnami.com/bitnami
helm-extra-args: --timeout 600s
check-version-increment: true
debug: true

GitOps Integration

ArgoCD Application

apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: myapp
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/your-org/helm-chart-wrapper
targetRevision: main
path: .
helm:
valueFiles:
- values/production.yaml
parameters:
- name: image.tag
value: "2.0.0"
destination:
server: https://kubernetes.default.svc
namespace: myapp
syncPolicy:
automated:
prune: true
selfHeal: true
syncOptions:
- CreateNamespace=true

Flux Kustomization

apiVersion: kustomize.toolkit.fluxcd.io/v1beta2
kind: Kustomization
metadata:
name: myapp
namespace: flux-system
spec:
interval: 5m
sourceRef:
kind: GitRepository
name: helm-chart-wrapper
path: "./environments/production"
prune: true
wait: true
timeout: 5m
postBuild:
substitute:
image_tag: "2.0.0"

Quick Start

  1. Generate the chart:

    archetect render git@github.com:p6m-archetypes/helm-chart-wrapper.archetype.git
  2. Customize values:

    # Edit values.yaml and environment-specific files
    vim values/production.yaml
  3. Validate chart:

    helm lint .
    helm template myapp . --values values/production.yaml
  4. Deploy application:

    helm install myapp . \
    --namespace myapp \
    --create-namespace \
    --values values/production.yaml
  5. Verify deployment:

    kubectl get pods -n myapp
    kubectl get svc -n myapp
    kubectl get ingress -n myapp

Best Practices

Security

  • Use non-root containers and security contexts
  • Implement network policies for traffic control
  • Use RBAC with least privilege principle
  • Store secrets securely using external secret management

Performance

  • Configure appropriate resource requests and limits
  • Use HPA for automatic scaling
  • Implement proper health checks
  • Configure Pod Disruption Budgets

Maintainability

  • Use semantic versioning for chart releases
  • Document all configuration options
  • Implement comprehensive testing
  • Follow Helm best practices and conventions