Pods come and go. Their IP addresses change. Services provide stable endpoints that persist beyond pod lifetimes. Ingress routes external traffic to services. In this lesson, you'll master the networking layer that makes Kubernetes applications accessible and resilient.

1. Learning Objectives

By the end of this lesson, you will be able to:

  • Create and use different Service types (ClusterIP, NodePort, LoadBalancer)
  • Understand Service discovery and DNS in Kubernetes
  • Configure Ingress for HTTP/HTTPS routing
  • Implement Network Policies for security
  • Use Headless Services for StatefulSets
  • Troubleshoot network connectivity issues

2. Why This Matters

Real-world scenario: Your backend pods restart, getting new IP addresses. Your frontend needs to find them without hardcoding IPs. Services solve this with stable DNS names and load balancing across pods.

3. Core Concepts

Service Types Overview

Service Types:
1. ClusterIP (default) - Internal only, accessible within cluster
2. NodePort - Exposes on each node's IP at high port (30000-32767)
3. LoadBalancer - Creates cloud load balancer (AWS ELB, Azure LB, GCP LB)
4. ExternalName - Maps to external DNS name
5. Headless - No cluster IP, direct pod DNS (for StatefulSets)
Service types summary

ClusterIP (Internal Service)

# clusterip-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP
  selector:
    app: backend
    tier: api
  ports:
  - port: 8080
    targetPort: 80
    protocol: TCP
  sessionAffinity: ClientIP

# Access within cluster:
# curl http://backend-service:8080
# curl http://backend-service.default.svc.cluster.local:8080

kubectl apply -f clusterip-service.yaml
kubectl get svc
kubectl describe svc backend-service
kubectl get endpoints backend-service
ClusterIP service for internal communication

NodePort (External Access via Node IP)

# nodeport-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
spec:
  type: NodePort
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
    nodePort: 30080  # Optional, Kubernetes assigns if omitted
    protocol: TCP

# Access: http://<node-ip>:30080
# For minikube: minikube service web-nodeport

kubectl get svc web-nodeport
# Output: web-nodeport   NodePort   10.96.0.1   <none>   80:30080/TCP
NodePort service

LoadBalancer (Cloud Load Balancer)

# loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: web-loadbalancer
  annotations:
    # AWS specific
    service.beta.kubernetes.io/aws-load-balancer-type: "nlb"
    service.beta.kubernetes.io/aws-load-balancer-internal: "false"
    # Azure specific
    service.beta.kubernetes.io/azure-load-balancer-internal: "false"
    # GCP specific
    cloud.google.com/load-balancer-type: "External"
spec:
  type: LoadBalancer
  selector:
    app: web
  ports:
  - port: 80
    targetPort: 8080
  externalTrafficPolicy: Local

kubectl apply -f loadbalancer-service.yaml
kubectl get svc web-loadbalancer -w
# Wait for EXTERNAL-IP to be assigned

# For minikube (tunnel required):
# minikube tunnel
# kubectl get svc web-loadbalancer
LoadBalancer service for cloud deployments

Service Discovery (DNS)

# Kubernetes DNS format:
# <service-name>.<namespace>.svc.cluster.local

# Example DNS names:
# backend-service.default.svc.cluster.local
# frontend-service.production.svc.cluster.local

# Test DNS resolution from a pod
kubectl run dns-test --image=busybox -it --rm --restart=Never -- nslookup kubernetes.default

# Access service by short name (same namespace)
kubectl run curl --image=curlimages/curl -it --rm --restart=Never -- curl http://backend-service:8080

# Access service from different namespace
kubectl run curl --image=curlimages/curl -it --rm --restart=Never -- curl http://backend-service.production:8080

# Environment variables (old style, still works)
kubectl exec backend-pod -- env | grep SERVICE
# BACKEND_SERVICE_SERVICE_HOST=10.96.0.1
# BACKEND_SERVICE_SERVICE_PORT=8080
Service discovery and DNS

Ingress (HTTP/HTTPS Routing)

# ingress.yaml - HTTP/HTTPS routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: main-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    secretName: app-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 8080
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 8080

# Install Ingress Controller (nginx)
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.9.0/deploy/static/provider/cloud/deploy.yaml

# For minikube
minikube addons enable ingress

# Apply ingress
kubectl apply -f ingress.yaml
kubectl get ingress
kubectl describe ingress main-ingress
Ingress for HTTP/HTTPS routing

Network Policies (Security)

# network-policy.yaml - Restrict traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-network-policy
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: frontend
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: database
    ports:
    - protocol: TCP
      port: 5432
  - to:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          app: dns
    ports:
    - protocol: UDP
      port: 53

# Deny all traffic to pods in default namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
spec:
  podSelector: {}
  policyTypes:
  - Ingress

# Allow all traffic from specific namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-from-monitoring
spec:
  podSelector: {}
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: monitoring

kubectl apply -f network-policy.yaml
kubectl get networkpolicies
Network Policies for security

4. Complete Project: Production Ingress Setup

# complete-ingress.yaml
---
# Certificate Issuer (Let's Encrypt)
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-prod
spec:
  acme:
    server: https://acme-v02.api.letsencrypt.org/directory
    email: admin@example.com
    privateKeySecretRef:
      name: letsencrypt-prod
    solvers:
    - http01:
        ingress:
          class: nginx

---
# Main Ingress with TLS
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: production-ingress
  annotations:
    nginx.ingress.kubernetes.io/rate-limit: "10r/s"
    nginx.ingress.kubernetes.io/proxy-body-size: "10m"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "60"
    nginx.ingress.kubernetes.io/enable-cors: "true"
    nginx.ingress.kubernetes.io/cors-allow-origin: "https://example.com"
    cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
  ingressClassName: nginx
  tls:
  - hosts:
    - app.example.com
    - api.example.com
    secretName: example-com-tls
  rules:
  - host: app.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: frontend-service
            port:
              number: 80
      - path: /api
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 8080
      - path: /socket.io
        pathType: Prefix
        backend:
          service:
            name: websocket-service
            port:
              number: 3000
  - host: api.example.com
    http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: backend-service
            port:
              number: 8080

---
# Internal services (not exposed externally)
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
  - port: 8080
    targetPort: 8080

---
# Network Policy: Only allow ingress traffic
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: backend-allow-ingress
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector: {}
      podSelector:
        matchLabels:
          app: nginx-ingress
    ports:
    - protocol: TCP
      port: 8080

# Apply all resources
kubectl apply -f complete-ingress.yaml

# Wait for certificate
kubectl get certificate -w

# Test ingress
curl https://app.example.com
curl https://api.example.com/health
Production ingress with TLS and security

5. Common Errors & Solutions

6. Summary Checklist

7. Next Steps

Next lesson: Kubernetes Lesson 6.4: ConfigMaps & Secrets

Gataya Med

DevOps Engineer & Backend Developer. Sharing insights on cloud, automation, and scalable systems.

Comments (0)

Sarah Chen February 9, 2025

This is exactly what I needed! The initContainer approach solved our migration issues completely. Thanks for the detailed guide!

Reply

Leave a Comment