Kubernetes networking is the #1 thing that confuses beginners. You deploy your app, but how do users actually reach it? You hear terms like "ClusterIP," "NodePort," "LoadBalancer," "Ingress," and "ALB Controller" — and they all seem to do similar things. This guide explains each one using simple analogies, shows you when to use what, and gives you copy-paste YAML for every scenario.

The Big Picture: How Traffic Reaches Your App

Think of Kubernetes like a large office building. Your application pods are employees working in rooms. The question is: how does someone from outside the building find and talk to the right employee?

Kubernetes Networking: The Office Building Analogy
Internet (The Street)Users, browsers, mobile apps — the outside world trying to reach your app
Load Balancer (The Main Entrance)One public IP address — the front door of your building. Routes traffic inside.
Ingress (The Receptionist)Reads the request and routes to the right department: api.example.com goes to API team, app.example.com goes to Frontend team
Service (The Department Phone Extension)A stable "phone number" for a group of pods. Even if employees (pods) change desks, the extension stays the same.
Pod (The Employee)The actual running instance of your application. Pods come and go — they're ephemeral.

Kubernetes Services: The Foundation

A Service is the most fundamental networking concept in Kubernetes. Pods are temporary — they get created, destroyed, and rescheduled constantly. A Service gives you a stable address that always points to the right pods, no matter how many there are or where they're running.

ClusterIP: Internal Communication Only

Analogy: An internal phone extension. Only people inside the building can call it. Outsiders can't.

Use when: Service A needs to talk to Service B inside the cluster. Your API calling your database. Your backend calling a cache service.

ClusterIP: Internal Cluster Communication
Frontend Pod(Inside cluster)
ClusterIP Service(Stable internal IP)
Backend Pods(Multiple replicas)
1 http://backend-service:8080/api
Load balance across pods (round-robin)
2 Forward to healthy pod (10.244.1.15:8080)
3 Response ✅
# ClusterIP Service — the DEFAULT type
apiVersion: v1
kind: Service
metadata:
  name: backend-service
spec:
  type: ClusterIP          # This is the default, you can omit this line
  selector:
    app: backend            # Find pods with label app=backend
  ports:
    - port: 8080            # Port the service listens on
      targetPort: 8080      # Port the pod listens on
      protocol: TCP

# Now any pod in the cluster can reach the backend at:
#   http://backend-service:8080          (same namespace)
#   http://backend-service.default:8080  (from another namespace)
#   http://backend-service.default.svc.cluster.local:8080  (fully qualified)

# Kubernetes DNS automatically creates these names!

# You can also use it for your database:
apiVersion: v1
kind: Service
metadata:
  name: postgres
spec:
  selector:
    app: postgres
  ports:
    - port: 5432
      targetPort: 5432

# Now your app connects to: postgres://postgres:5432/mydb
# No IP addresses needed — just the service name!

NodePort: Quick External Access (Development Only)

Analogy: Punching a hole in the building wall. Anyone who knows the building's address and the hole number can reach in directly.

Use when: You need quick external access for testing/development. Never in production — it's insecure and limited.

# NodePort Service — opens a port on EVERY node
apiVersion: v1
kind: Service
metadata:
  name: my-app-nodeport
spec:
  type: NodePort
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30080    # Port opened on every node (range: 30000-32767)

# Access your app at:
#   http://<ANY-NODE-IP>:30080
#   e.g., http://10.0.1.5:30080 or http://10.0.1.6:30080

# Problems with NodePort:
# 1. Ugly ports (30000-32767 range only)
# 2. Users need to know a node IP
# 3. If a node dies, that IP stops working
# 4. No SSL termination
# 5. No path-based routing
# Verdict: Fine for dev/testing, never for production

LoadBalancer: Cloud-Native External Access

Analogy: Hiring a professional doorman who stands at the main entrance. They have a public address that never changes, and they route visitors to the right place.

Use when: You need to expose ONE service to the internet with a stable public IP. Works on AWS (NLB/CLB), GCP, Azure.

LoadBalancer Service: One Public IP Per Service
🌐InternetUsers
🏢Cloud LBPublic IP
🔄ServiceLoadBalancer
📦Pod 1Replica
📦Pod 2Replica
# LoadBalancer Service — creates a cloud load balancer
apiVersion: v1
kind: Service
metadata:
  name: my-app-lb
spec:
  type: LoadBalancer
  selector:
    app: my-app
  ports:
    - port: 80
      targetPort: 8080

# On AWS, this creates a Classic Load Balancer (CLB) automatically
# kubectl get svc my-app-lb
# NAME        TYPE           EXTERNAL-IP                              PORT(S)
# my-app-lb   LoadBalancer   a1b2c3d4.us-east-1.elb.amazonaws.com    80:31234/TCP

# Point your DNS to the EXTERNAL-IP and you're live!

# Problem: Each LoadBalancer service creates a NEW cloud LB
# 10 services = 10 load balancers = 10x the cost!
# That's why we use Ingress...

Ingress: The Smart Router (Production Standard)

Analogy: A receptionist at the front desk. One entrance (one load balancer), but the receptionist reads the visitor's request and routes them to the right department:

  • "I'm here for the API" → Route to API service
  • "I'm here for the website" → Route to frontend service
  • "I'm here for the admin panel" → Route to admin service
Ingress: One Load Balancer, Multiple Services
Ingress Controller One LB, smart routing by host/path
Routes based on hostname and URL path
🌐api.example.comAPI Service
🖥app.example.comFrontend Service
🔒admin.example.comAdmin Service
📊grafana.example.comMonitoring Service
# Step 1: Install an Ingress Controller (runs once per cluster)
# The controller IS the actual reverse proxy (nginx, traefik, etc.)

# Option A: nginx Ingress Controller
helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace

# Option B: AWS ALB Controller (see next section)

# Step 2: Create Ingress rules
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt    # Auto TLS certs!
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - api.example.com
        - app.example.com
      secretName: my-tls-cert
  rules:
    # Route by hostname
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80

    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

    # Route by path (same hostname, different paths)
    - host: example.com
      http:
        paths:
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
          - path: /admin
            pathType: Prefix
            backend:
              service:
                name: admin-service
                port:
                  number: 80
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

AWS ALB Controller: The AWS-Native Ingress

The AWS Load Balancer Controller (formerly ALB Ingress Controller) creates AWS Application Load Balancers directly from your Ingress resources. Instead of running nginx inside the cluster, it uses AWS-managed ALBs — which means AWS handles scaling, health checks, and SSL termination for you.

AWS ALB Controller Architecture
Internet → Route53 (DNS)api.example.com → ALB public endpoint
AWS ALB (Application Load Balancer)Managed by AWS — auto-scaling, WAF integration, SSL termination, access logs
Target GroupsALB routes to pods directly (IP mode) or via NodePort — bypasses kube-proxy
PodsYour application containers receive traffic directly from the ALB
# Install AWS Load Balancer Controller on EKS
# Prerequisites: EKS cluster with IRSA (IAM Roles for Service Accounts)

# 1. Create IAM policy
curl -o iam-policy.json https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/main/docs/install/iam_policy.json
aws iam create-policy \
  --policy-name AWSLoadBalancerControllerIAMPolicy \
  --policy-document file://iam-policy.json

# 2. Create service account with IAM role
eksctl create iamserviceaccount \
  --cluster=my-cluster \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --attach-policy-arn=arn:aws:iam::ACCOUNT:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve

# 3. Install via Helm
helm install aws-load-balancer-controller \
  eks/aws-load-balancer-controller \
  -n kube-system \
  --set clusterName=my-cluster \
  --set serviceAccount.create=false \
  --set serviceAccount.name=aws-load-balancer-controller

# 4. Create Ingress with ALB annotations
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-alb-ingress
  annotations:
    # Tell K8s to use the ALB controller (not nginx)
    kubernetes.io/ingress.class: alb

    # Internet-facing (vs internal for private APIs)
    alb.ingress.kubernetes.io/scheme: internet-facing

    # Route directly to pod IPs (faster than NodePort)
    alb.ingress.kubernetes.io/target-type: ip

    # SSL: use ACM certificate
    alb.ingress.kubernetes.io/certificate-arn: arn:aws:acm:us-east-1:ACCOUNT:certificate/xxx

    # Redirect HTTP to HTTPS
    alb.ingress.kubernetes.io/ssl-redirect: "443"

    # Health check path
    alb.ingress.kubernetes.io/healthcheck-path: /health

    # Enable WAF (Web Application Firewall)
    # alb.ingress.kubernetes.io/waf-acl-id: your-waf-id

    # Access logs to S3
    # alb.ingress.kubernetes.io/load-balancer-attributes: access_logs.s3.enabled=true,access_logs.s3.bucket=my-logs

spec:
  ingressClassName: alb
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: api-service
                port:
                  number: 80
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80

nginx Ingress vs ALB Controller: When to Use Which

nginx Ingress Controller vs AWS ALB Controller
nginx Ingress Controller
🌐Runs inside cluster (as pods)
Cloud-agnostic (AWS, GCP, Azure, bare metal)
Full nginx config control (custom headers, rewrites)
💰One NLB for all Ingress rules (cheaper)
🎯Best for: multi-cloud, custom routing, rate limiting
VS
AWS ALB Controller
🌐Runs in AWS (managed ALB, not in cluster)
AWS-only (EKS)
Deep AWS integration (WAF, Cognito, ACM, Shield)
💰One ALB per Ingress by default (can be grouped)
🎯Best for: AWS-native, WAF, Cognito auth, access logs

Network Policies: Firewall Rules for Pods

By default, every pod can talk to every other pod in the cluster. That's dangerous. Network Policies are Kubernetes's firewall — they control which pods can communicate with which.

# Default deny all ingress (lock down first, then whitelist)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all
  namespace: production
spec:
  podSelector: {}    # Apply to ALL pods in this namespace
  policyTypes:
    - Ingress
  # No ingress rules = deny everything!

---
# Allow frontend to talk to backend (and nothing else)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: backend       # Apply to backend pods
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend   # Only frontend pods can connect
      ports:
        - port: 8080
          protocol: TCP

---
# Allow backend to talk to database
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-to-db
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: postgres
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: backend
      ports:
        - port: 5432

The Complete Decision Guide

When to Use What: The Complete Guide
I Want To... Use This Why
Connect microservices internallyClusterIP ServiceStable DNS name, internal only, free
Quick test access from my laptopNodePort or kubectl port-forwardFast, no cloud resources needed
Expose ONE service to internetLoadBalancer ServiceGets a public IP, simple setup
Expose MANY services on one IPIngress (nginx or ALB)Host/path routing, SSL, one LB cost
Use AWS WAF, Cognito, ACMAWS ALB ControllerDeep AWS integration
Run on any cloud or bare metalnginx Ingress ControllerCloud-agnostic, full control
Restrict which pods can communicateNetworkPolicyPod-level firewall rules
gRPC, mTLS, traffic splittingService Mesh (Istio/Linkerd)Advanced L7 features, observability
Expose a TCP/UDP service (not HTTP)LoadBalancer (NLB on AWS)Ingress is HTTP-only, NLB handles TCP/UDP

Common Beginner Mistakes

  • "My pod has an IP, why do I need a Service?" — Pod IPs change every time a pod restarts or moves to another node. Services give you a stable address. Never hardcode pod IPs.
  • "I created a LoadBalancer for every service" — Each LoadBalancer creates a new cloud LB ($). Use one Ingress to route to many services behind a single LB.
  • "I'm using NodePort in production" — NodePort exposes a random high port on every node. No SSL, no path routing, ugly URLs. Use Ingress instead.
  • "My Ingress isn't working" — Most likely you forgot to install an Ingress Controller. Ingress resources are just rules — you need a controller (nginx, traefik, ALB) to actually execute them.
  • "I can't connect from one namespace to another" — Use the full DNS name: service-name.namespace.svc.cluster.local. Or check if a NetworkPolicy is blocking it.

Debugging Kubernetes Networking

# 1. Is my pod running and healthy?
kubectl get pods -o wide
kubectl logs my-pod
kubectl describe pod my-pod

# 2. Does my Service have endpoints?
kubectl get endpoints my-service
# If ENDPOINTS is empty: your selector labels don't match any pods!

# 3. Can I reach the service from inside the cluster?
kubectl run debug --image=nicolaka/netshoot -it --rm -- bash
curl http://my-service:8080/health     # By service name
nslookup my-service                     # DNS resolution

# 4. Is the Ingress controller running?
kubectl get pods -n ingress-nginx
kubectl get ingress                     # Check ADDRESS column

# 5. What does the ALB look like?
kubectl describe ingress my-ingress
# Look for Events: "Successfully reconciled" = ALB created
# Check the ADDRESS field for the ALB URL

# 6. Network Policy blocking traffic?
kubectl get networkpolicy -A
kubectl describe networkpolicy deny-all

Kubernetes networking follows a simple progression: ClusterIP for internal communication, Ingress for external HTTP traffic (with nginx or ALB Controller), LoadBalancer for non-HTTP services, and NetworkPolicy for security. Start with ClusterIP + Ingress — that covers 90% of use cases. Add complexity only when you need it.