Lesson 08 Services & Networking
Theme:

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.