Lab Guide — Services & Networking
Three exercises covering Service type switching, headless Services for StatefulSets, and multi-port configuration with traffic policies.
Prerequisites
A running Kubernetes cluster (Minikube, kind, or cloud-managed), helm 3.x, and kubectl configured. All exercises build on a single chart that you will create in Exercise 1.
Exercise 1 — ClusterIP and NodePort Service Template
Build a Service template that switches type from values.yaml
Create a Helm chart with a Service whose type, port, and nodePort are all driven by values.
Step 1: Scaffold the chart
helm create svc-lab
cd svc-lab
Step 2: Edit values.yaml
Replace the existing service section with:
# values.yaml (service section)
service:
type: ClusterIP
port: 80
targetPort: 80
nodePort: ""
annotations: {}
Step 3: Edit templates/service.yaml
Replace the entire file with a conditional template:
apiVersion: v1
kind: Service
metadata:
name: {{ include "svc-lab.fullname" . }}
labels:
{{- include "svc-lab.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
selector:
{{- include "svc-lab.selectorLabels" . | nindent 4 }}
ports:
- port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
protocol: TCP
name: http
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
Step 4: Verify with helm template
# Default: ClusterIP
helm template . | grep -A 15 "kind: Service"
# Override to NodePort
helm template . --set service.type=NodePort --set service.nodePort=31080 \
| grep -A 20 "kind: Service"
# Override to LoadBalancer
helm template . --set service.type=LoadBalancer \
| grep -A 15 "kind: Service"
Step 5: Install and test
# Install with ClusterIP (default)
helm install svc-demo .
# Verify the Service
kubectl get svc svc-demo-svc-lab
# Test connectivity from inside the cluster
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- \
curl -s http://svc-demo-svc-lab:80
# Upgrade to NodePort
helm upgrade svc-demo . --set service.type=NodePort --set service.nodePort=31080
kubectl get svc svc-demo-svc-lab
✓ Checkpoint: helm template renders valid YAML for all three Service types. The nodePort field only appears when type=NodePort. The installed Service is reachable from a test Pod.
Exercise 2 — Headless Service for a StatefulSet
Add a headless Service and verify per-Pod DNS
Extend the chart with a headless Service and a minimal StatefulSet to demonstrate stable Pod DNS names.
Step 1: Add a headless Service template
Create templates/headless-service.yaml:
{{- if .Values.headless.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ include "svc-lab.fullname" . }}-headless
labels:
{{- include "svc-lab.labels" . | nindent 4 }}
spec:
clusterIP: None
selector:
{{- include "svc-lab.selectorLabels" . | nindent 4 }}
ports:
- name: http
port: {{ .Values.service.port }}
targetPort: {{ .Values.service.targetPort }}
{{- end }}
Step 2: Add a StatefulSet template
Create templates/statefulset.yaml:
{{- if .Values.headless.enabled }}
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ include "svc-lab.fullname" . }}-sts
spec:
serviceName: {{ include "svc-lab.fullname" . }}-headless
replicas: {{ .Values.headless.replicas | default 3 }}
selector:
matchLabels:
{{- include "svc-lab.selectorLabels" . | nindent 6 }}
template:
metadata:
labels:
{{- include "svc-lab.selectorLabels" . | nindent 8 }}
spec:
containers:
- name: nginx
image: nginx:stable-alpine
ports:
- containerPort: 80
name: http
{{- end }}
Step 3: Update values.yaml
Add the headless section:
# values.yaml (append)
headless:
enabled: false
replicas: 3
Step 4: Deploy and verify DNS
# Upgrade the release with headless enabled
helm upgrade svc-demo . --set headless.enabled=true
# Wait for StatefulSet Pods
kubectl rollout status statefulset svc-demo-svc-lab-sts
# Check the headless Service has no ClusterIP
kubectl get svc svc-demo-svc-lab-headless
# Verify per-Pod DNS resolution
kubectl run dns-test --image=busybox:1.36 --rm -it --restart=Never -- \
nslookup svc-demo-svc-lab-sts-0.svc-demo-svc-lab-headless
# Should return an individual Pod IP, not a virtual ClusterIP
✓ Checkpoint: The headless Service shows ClusterIP: None. DNS lookup for *-sts-0.<headless-svc> returns the Pod's IP address. The headless template only renders when headless.enabled=true.
Exercise 3 — Multi-Port Service with Traffic Policies
Configure named ports, externalTrafficPolicy, and sessionAffinity
Extend the Service template to support multiple ports and traffic policies.
Step 1: Update values.yaml for multi-port
# values.yaml (replace the service section)
service:
type: ClusterIP
ports:
- name: http
port: 80
targetPort: 8080
protocol: TCP
- name: metrics
port: 9090
targetPort: 9090
protocol: TCP
nodePort: ""
externalTrafficPolicy: ""
sessionAffinity: ""
annotations: {}
Step 2: Update templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ include "svc-lab.fullname" . }}
labels:
{{- include "svc-lab.labels" . | nindent 4 }}
{{- with .Values.service.annotations }}
annotations:
{{- toYaml . | nindent 4 }}
{{- end }}
spec:
type: {{ .Values.service.type }}
{{- if .Values.service.externalTrafficPolicy }}
externalTrafficPolicy: {{ .Values.service.externalTrafficPolicy }}
{{- end }}
{{- if .Values.service.sessionAffinity }}
sessionAffinity: {{ .Values.service.sessionAffinity }}
{{- end }}
selector:
{{- include "svc-lab.selectorLabels" . | nindent 4 }}
ports:
{{- range .Values.service.ports }}
- name: {{ .name }}
port: {{ .port }}
targetPort: {{ .targetPort }}
protocol: {{ .protocol | default "TCP" }}
{{- end }}
Step 3: Test with helm template
# Render with multi-port defaults
helm template . | grep -A 25 "kind: Service"
# Render with traffic policy and session affinity
helm template . \
--set service.type=LoadBalancer \
--set service.externalTrafficPolicy=Local \
--set service.sessionAffinity=ClientIP \
| grep -A 30 "kind: Service"
Step 4: Deploy and verify
# Upgrade with multi-port
helm upgrade svc-demo .
# Verify both ports appear
kubectl describe svc svc-demo-svc-lab
# Check endpoints for both ports
kubectl get endpoints svc-demo-svc-lab
# Test the metrics port from inside the cluster
kubectl run curl-test --image=curlimages/curl --rm -it --restart=Never -- \
curl -s http://svc-demo-svc-lab:9090
Step 5: Clean up
helm uninstall svc-demo
✓ Checkpoint: The rendered Service YAML contains both http and metrics named ports. The externalTrafficPolicy and sessionAffinity fields only appear when set to non-empty values. kubectl describe svc confirms both ports are active.
Tip
Use helm template . | kubectl apply --dry-run=client -f - to validate that your rendered YAML is accepted by the Kubernetes API without actually creating resources.