Time to build something real. The Book Info app has four services — a productpage
frontend plus details, ratings, and reviews backends. Each needs a Deployment, a
Service, and a ServiceAccount. That’s twelve Kubernetes objects. Instead of twelve
files, we’ll write one templated set driven by values.
The finished chart lives at
05-charting-book-info/book-info
and uses the current Book Info images (1.20.3).
The plan
All four services share the same shape — only the name, image, replica count, and
service name differ. That’s a textbook case for a range over a map of services.
We describe the services in values.yaml and let one template generate all of them.
book-info/
├── Chart.yaml
├── values.yaml
├── values-dev.yaml
├── values-prod.yaml
└── templates/
├── _helpers.tpl
├── serviceaccount.yaml
├── service.yaml
├── deployment.yaml
└── NOTES.txt
Chart.yaml
apiVersion: v2
name: book-info
description: A Helm chart for the Book Info microservices sample (Helm 4)
type: application
version: 0.1.0
appVersion: "1.20.3"
values.yaml — describe the services as data
The key idea: the configuration is data. Shared image settings live at the top, then a map where each entry is one service.
image:
registry: docker.io/istio
tag: "1.20.3"
pullPolicy: IfNotPresent
services:
productpage:
repository: examples-bookinfo-productpage-v1
serviceName: products
account: bookinfo-productpage
replicas: 1
port: 9080
details:
repository: examples-bookinfo-details-v1
serviceName: details
account: bookinfo-details
replicas: 1
port: 9080
ratings:
repository: examples-bookinfo-ratings-v1
serviceName: ratings
account: bookinfo-ratings
replicas: 1
port: 9080
reviews:
repository: examples-bookinfo-reviews-v3
serviceName: reviews
account: bookinfo-reviews
replicas: 1
port: 9080
Book Info ships three versions of
reviews. To keep the chart approachable we deploy one version at a time (v3 by default) and switch it per environment with a values override — you’ll see that in the next chapter.
_helpers.tpl — shared labels
{{- define "book-info.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{- end -}}
serviceaccount.yaml — one per service
We range over the services map. Remember from chapter 4: inside the loop . becomes
the current item, so we use $ to reach the root context (needed by the labels helper).
{{- range $name, $svc := .Values.services }}
apiVersion: v1
kind: ServiceAccount
metadata:
name: {{ $svc.account }}
labels:
{{- include "book-info.labels" $ | nindent 4 }}
account: {{ $name }}
---
{{- end }}
service.yaml — one Service per app
The serviceName matters: the productpage frontend talks to backends by their service
names (details, reviews, ratings), so those must be exact.
{{- range $name, $svc := .Values.services }}
apiVersion: v1
kind: Service
metadata:
name: {{ $svc.serviceName }}
labels:
{{- include "book-info.labels" $ | nindent 4 }}
app: {{ $name }}
spec:
ports:
- port: {{ $svc.port }}
name: http
selector:
app: {{ $name }}
---
{{- end }}
deployment.yaml — the workhorse
One template, four Deployments. Note how the image is assembled from the shared
$.Values.image settings and the per-service repository:
{{- range $name, $svc := .Values.services }}
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ $name }}
labels:
{{- include "book-info.labels" $ | nindent 4 }}
app: {{ $name }}
spec:
replicas: {{ $svc.replicas | default 1 }}
selector:
matchLabels:
app: {{ $name }}
template:
metadata:
labels:
{{- include "book-info.labels" $ | nindent 8 }}
app: {{ $name }}
spec:
serviceAccountName: {{ $svc.account }}
containers:
- name: {{ $name }}
image: "{{ $.Values.image.registry }}/{{ $svc.repository }}:{{ $.Values.image.tag }}"
imagePullPolicy: {{ $.Values.image.pullPolicy }}
ports:
- containerPort: {{ $svc.port }}
---
{{- end }}
NOTES.txt — tell the user what to do next
Book Info deployed as release "{{ .Release.Name }}" in namespace "{{ .Release.Namespace }}".
Open the product page locally:
kubectl port-forward svc/{{ (index .Values.services "productpage").serviceName }} 9080:9080
Then visit http://localhost:9080/productpage
Render and check
Before installing, always render locally and eyeball the output:
helm template book-info ./book-info | grep 'image:'
image: "docker.io/istio/examples-bookinfo-details-v1:1.20.3"
image: "docker.io/istio/examples-bookinfo-productpage-v1:1.20.3"
image: "docker.io/istio/examples-bookinfo-ratings-v1:1.20.3"
image: "docker.io/istio/examples-bookinfo-reviews-v3:1.20.3"
Lint it too:
helm lint ./book-info
Install it
helm install book-info ./book-info --namespace book-info --create-namespace
Watch the pods come up, then open the app:
kubectl get pods -n book-info
kubectl port-forward -n book-info svc/products 9080:9080
Visit http://localhost:9080/productpage and you’ll see Book Info running — all four
services deployed from a single, DRY chart.
The complete Book Info chart is in 05-charting-book-info.
Adding a fifth service would now mean adding four lines to values.yaml — no new
templates. That’s the payoff of data-driven charts. In the
next chapter we’ll use that same design to deploy
dev and prod from one chart.