Lesson 08 Services & Networking
Theme:
Lesson 08 · Slide 1

Services & Networking

Kubernetes Services provide stable network endpoints for ephemeral Pods. This lesson covers how to template every Service type in Helm, wire up DNS-based discovery, and build flexible values.yaml patterns for production charts.

Learning Objectives

Lesson 08 · Slide 2

Service Types at a Glance

TypeAccessible FromUse Case
ClusterIPInside the cluster onlyInternal microservice communication
NodePortAny node IP + static port (30000–32767)Dev/testing, on-prem without LB
LoadBalancerExternal IP via cloud providerProduction internet-facing services
ExternalNameDNS CNAME aliasProxy to external systems (RDS, SaaS APIs)
# ClusterIP (default)
apiVersion: v1
kind: Service
metadata:
  name: my-app
spec:
  type: ClusterIP
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
      protocol: TCP
Note: If you omit type, Kubernetes defaults to ClusterIP.
Lesson 08 · Slide 3

NodePort & LoadBalancer

NodePort

spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080

Exposes the service on every node at a static port. Traffic arriving at <NodeIP>:30080 is forwarded to port 8080 on matching Pods.

LoadBalancer

spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 443
      targetPort: 8443
      protocol: TCP

Cloud provider provisions an external load balancer. The LB forwards traffic to the NodePort range automatically.

Cost alert: Each LoadBalancer Service typically creates a cloud LB resource with its own hourly cost. Use an Ingress controller to consolidate multiple services behind one LB.

Lesson 08 · Slide 4

Headless Services & Multi-Port

Headless Service (clusterIP: None)

Returns individual Pod IPs instead of a single virtual IP. Essential for StatefulSets where each Pod needs a stable DNS name.

apiVersion: v1
kind: Service
metadata:
  name: {{ include "mychart.fullname" . }}-headless
spec:
  clusterIP: None
  selector:
    {{- include "mychart.selectorLabels" . | nindent 4 }}
  ports:
    - name: gossip
      port: 7946
      targetPort: gossip
    - name: client
      port: 9042
      targetPort: client

DNS result: Each Pod gets pod-0.myapp-headless.namespace.svc.cluster.local. Clients can address individual replicas directly.

Note: Always use named ports (name: gossip) in multi-port Services. Unnamed ports cause ambiguity errors.
Lesson 08 · Slide 5

Traffic Policies & Session Affinity

externalTrafficPolicy

ValueBehaviourTrade-off
Cluster (default)Traffic may hop between nodes; source IP is masked (SNAT)Even load distribution, but lose client IP
LocalTraffic stays on the receiving node; source IP is preservedPreserves client IP, but uneven distribution if Pods are not on every node

sessionAffinity

spec:
  sessionAffinity: ClientIP
  sessionAffinityConfig:
    clientIP:
      timeoutSeconds: 10800   # 3 hours

Routes repeat requests from the same client IP to the same Pod. Useful for stateful applications that store session data in memory.

Best practice: Prefer stateless Pods with external session stores (Redis). Use sessionAffinity only as a short-term migration strategy.

Lesson 08 · Slide 6

Service Discovery & EndpointSlices

DNS-Based Discovery

Every Service gets a DNS entry automatically:

<service>.<namespace>.svc.cluster.local

# Examples
my-app.default.svc.cluster.local        # full FQDN
my-app.default                           # within the cluster
my-app                                   # within the same namespace

Environment Variables

Kubelet injects MY_APP_SERVICE_HOST and MY_APP_SERVICE_PORT into every Pod that starts after the Service is created. DNS is preferred because it handles ordering and updates dynamically.

EndpointSlices

Lesson 08 · Slide 7

Templating Services in Helm

A flexible values.yaml pattern that covers all Service types:

# values.yaml
service:
  type: ClusterIP
  port: 80
  targetPort: 8080
  nodePort: ""          # only used when type=NodePort
  annotations: {}
# templates/service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "mychart.fullname" . }}
  labels:
    {{- include "mychart.labels" . | nindent 4 }}
  {{- with .Values.service.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  type: {{ .Values.service.type }}
  {{- if eq .Values.service.type "ClusterIP" }}
  {{- if .Values.service.clusterIP }}
  clusterIP: {{ .Values.service.clusterIP }}
  {{- end }}
  {{- end }}
  selector:
    {{- include "mychart.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 }}
Note: The nodePort field is only valid when type: NodePort. The conditional guard prevents rendering errors for other types.
Lesson 08 · Slide 8

Lab & Homework

Lab Exercises

  1. ClusterIP + NodePort — template a Service that switches type from values
  2. Headless Service — add a headless Service for a StatefulSet with DNS verification
  3. Multi-port + traffic policy — configure named ports, externalTrafficPolicy, and sessionAffinity

Homework Tasks

  1. Build a dual-Service chart (public LB + internal ClusterIP)
  2. Add ExternalName Service for an external database
  3. Implement conditional headless Service toggle

Key takeaway: A well-designed Helm Service template should let operators switch between ClusterIP, NodePort, and LoadBalancer with nothing more than a values.yaml change.

1 / ?