Hand-write the Kubernetes manifests for a microservice platform and you end up with a pile of near-identical files: eight Deployments that differ only in name, image, port, replicas, and a couple of env values, plus eight matching Services. That duplication is exactly what Helm removes. The trick is to split the manifests into two halves — the shape goes into templates, the data goes into values.yaml — and let one template range over the data.
The data half: a services map
values.yaml holds everything the flat YAML used to hard-code, now as values. The star is the services: map, keyed by service name, with the shared probes factored out once instead of repeated eight times:
probes: # ONE copy, stamped on all 8 apps
readiness: { path: /actuator/health/readiness, initialDelaySeconds: 10, periodSeconds: 10 }
liveness: { path: /actuator/health/liveness, initialDelaySeconds: 20, periodSeconds: 15 }
services: # ← the map the templates range over
order-service:
image: orderhub/order-service
tag: "0.1.0"
port: 8082
replicas: 2
profile: "prod,k8s"
resources: { requests: {cpu: 250m, memory: 448Mi}, limits: {cpu: "1", memory: 768Mi} }
# + config-server, eureka-server, api-gateway, inventory, payment, shipping, notification
The shape half: one range renders all eight
This is the heart of the templatization. {{- range $name, $svc := .Values.services }} loops the entire Deployment block once per service:
{{- range $name, $svc := .Values.services }}
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $name }}
labels:
{{- include "orderhub.selectorLabels" (dict "name" $name) | nindent 4 }}
{{- include "orderhub.commonLabels" $ | nindent 4 }}
spec:
replicas: {{ $svc.replicas }}
template:
spec:
containers:
- name: {{ $name }}
image: "{{ include "orderhub.image" (dict "svc" $svc "root" $) }}"
ports: [ { containerPort: {{ $svc.port }} } ]
envFrom:
- configMapRef: { name: {{ $.Values.config.name }} }
- secretRef: { name: {{ $.Values.secrets.name }} }
readinessProbe: { httpGet: { path: {{ $.Values.probes.readiness.path }}, port: {{ $svc.port }} } }
livenessProbe: { httpGet: { path: {{ $.Values.probes.liveness.path }}, port: {{ $svc.port }} } }
{{- end }}
Two Helm mechanics make it work. Inside a range the dot rebinds to the current service, so you reach the release/chart/global values through $ — the root — as in $.Values.config.name and $.Values.probes, and the per-item values through $svc / $name. The Day-43 probes are kept verbatim but parameterised: their paths come from .Values.probes, their port from $svc.port. A service.yaml uses the identical loop to render all eight ClusterIP Services, their selectors sharing the Deployment's labels via a helper so they can never drift.
The new front door: an Ingress that routes by path
The flat manifests left every Service ClusterIP — internal only. The Ingress finally opens the stack to the world, gated by a single value:
{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
spec:
ingressClassName: {{ .Values.ingress.className }} # nginx
defaultBackend: # catch-all → gateway
service: { name: api-gateway, port: { number: 8080 } }
rules:
- host: {{ .Values.ingress.host | quote }} # orderhub.local
http:
paths:
{{- range .Values.ingress.routes }}
- path: {{ .path }} # /api/orders, /api/inventory, …
pathType: {{ .pathType | default "Prefix" }}
backend:
service: { name: {{ .service }}, port: { number: {{ .port }} } }
{{- end }}
{{- end }}
Each /api/* prefix maps to its service by Prefix match; anything unmatched falls through to api-gateway as the default backend. Because the whole object is wrapped in {{- if .Values.ingress.enabled }}, flipping the front door off is a one-value --set, not YAML surgery.
The payoff
helm install orderhub ./helm/orderhub renders 18 objects from about seven template files, and retuning anything is now a value override:
helm upgrade orderhub ./helm/orderhub --set services.order-service.replicas=4
helm rollback orderhub 1 -n orderhub # undo, atomically
The topology hasn't changed from the hand-written manifests — but it's now generated, versioned, and roll-back-able as a single release.
Run helm install and watch one template range over the services map, then curl the Ingress to see it route, live at: https://dev48v.infy.uk/orderhub/day44-helm-ingress.html
Top comments (0)