Kubernetes Networking: Services (ClusterIP, NodePort, LoadBalancer) and Ingress
📅 Published: Feb 2026
⏱️ Estimated Reading Time: 18 minutes
🏷️ Tags: Kubernetes, K8s Networking, Services, Ingress, Load Balancing
Introduction: The Networking Challenge
In Kubernetes, Pods are ephemeral. They come and go. They restart. They scale up and down. Each time a Pod restarts, it gets a new IP address. This creates a fundamental problem: How do you reliably communicate with a set of Pods that are constantly changing?
Kubernetes solves this problem with Services. A Service provides a stable network endpoint (IP address and DNS name) that never changes, even as Pods come and go. It also load-balances traffic across healthy Pods.
Additionally, Services and Ingress work together to expose your applications to users outside the cluster, providing everything from simple port forwarding to sophisticated HTTP routing and TLS termination.
This guide explains how Services work, the different types you can use, and how Ingress provides advanced HTTP routing.
Part 1: The Pod Communication Problem
Pod IPs Are Ephemeral
Every Pod gets its own IP address. But these IP addresses are not stable:
When a Pod restarts, it gets a new IP
When a Deployment scales up, new Pods get new IPs
When a Pod moves to a different node, it gets a new IP
Before restart: ┌─────────────┐ │ Pod A │ │ IP: 10.0.1.5│ └─────────────┘ After restart: ┌─────────────┐ │ Pod A │ │ IP: 10.0.2.8│ ← completely different! └─────────────┘
The Solution: Services
A Service sits in front of a set of Pods and provides:
Stable IP address: Never changes
Stable DNS name: Consistent for the life of the Service
Load balancing: Distributes traffic across healthy Pods
Health checking: Only sends traffic to ready Pods
Before Service: Client → Pod IP (unstable) After Service: Client → Service IP (stable) → Pod A, Pod B, Pod C
Part 2: Services
What is a Service?
A Service is an abstraction that defines a logical set of Pods and a policy to access them. It enables loose coupling between dependent Pods.
Think of a Service as a stable "front desk" for your Pods. You tell clients to call the front desk. The front desk knows which Pods are available and directs traffic accordingly.
Service YAML Structure
apiVersion: v1 kind: Service metadata: name: my-service spec: selector: app: myapp # Which Pods to target ports: - port: 80 # Port this Service listens on targetPort: 8080 # Port Pods are listening on protocol: TCP type: ClusterIP # Service type
How Services Work
Selector: The Service uses labels to find which Pods to target
Endpoints: The Service maintains a list of Pod IPs matching the selector
kube-proxy: On each node, kube-proxy creates network rules to forward traffic
Load balancing: Traffic is distributed across healthy endpoints
┌─────────────────────────────────────────────────────────────┐ │ Service │ │ selector: app=myapp │ │ ClusterIP: 10.96.0.1 │ │ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │ Pod A │ │ Pod B │ │ Pod C │ │ │ │ app=myapp│ │ app=myapp│ │ app=myapp│ │ │ │ 10.0.1.5│ │ 10.1.2.3│ │ 10.2.3.4│ │ │ └─────────┘ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────────────────┘
Part 3: Service Types
Kubernetes offers four types of Services, each serving a different use case.
Service Type Comparison
| Type | Cluster Access | External Access | Use Case |
|---|---|---|---|
| ClusterIP | Yes | No | Internal services, backend APIs |
| NodePort | Yes | Yes (static port on each node) | Basic external access, testing |
| LoadBalancer | Yes | Yes (cloud LB) | Production external access |
| ExternalName | N/A | Via DNS alias | Accessing external services |
Type 1: ClusterIP (Default)
ClusterIP is the default Service type. It creates an internal IP address accessible only within the cluster.
Use when: You need communication between Pods, but external access is not required.
apiVersion: v1 kind: Service metadata: name: backend-service spec: type: ClusterIP # Default, can be omitted selector: app: backend ports: - port: 8080 targetPort: 8080
┌─────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────┐ ┌─────────┐ │
│ │ Pod A │ │ Pod B │ │
│ └────┬────┘ └────┬────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌─────────────────────┐ │
│ │ Service (ClusterIP)│ │
│ │ 10.96.0.1 │ │
│ └──────────┬──────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────┐ │
│ │ Frontend Pod │ │
│ └─────────────────────┘ │
│ │
└──────────────────────────────┘
No external accessType 2: NodePort
NodePort builds on ClusterIP and adds external access. It opens a static port (30000-32767) on every node. Traffic to NodeIP:NodePort is forwarded to the Service.
Use when: You need simple external access, testing, or don't have a cloud load balancer.
apiVersion: v1 kind: Service metadata: name: web-service spec: type: NodePort selector: app: web ports: - port: 80 targetPort: 8080 nodePort: 30080 # Optional (Kubernetes assigns if omitted)
External User
│
│ http://any-node:30080
▼
┌────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Node 1 │ │ Node 2 │ │
│ │ │ │ │ │
│ │ ┌─────────┐ │ │ ┌─────────┐ │ │
│ │ │ Pod A │ │ │ │ Pod B │ │ │
│ │ └─────────┘ │ │ └─────────┘ │ │
│ │ │ │ │ │
│ │ NodePort │ │ NodePort │ │
│ │ 30080 │ │ 30080 │ │
│ └─────────────┘ └─────────────┘ │
│ │ │ │
│ └──────┬───────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Service │ │
│ └─────────────┘ │
└────────────────────────────────────────────┘Important: The nodePort range (30000-32767) is configurable and can be changed in the API server.
Type 3: LoadBalancer
LoadBalancer builds on NodePort and adds a cloud provider load balancer (AWS ELB, Azure LB, GCP LB). This is the standard way to expose services to the internet in production.
Use when: You need production-grade external access with a single IP address.
apiVersion: v1 kind: Service metadata: name: web-service spec: type: LoadBalancer selector: app: web ports: - port: 80 targetPort: 8080
External User
│
│ http://load-balancer-ip
▼
┌─────────────────────┐
│ Cloud Load Balancer│
│ (AWS ELB / GCP LB)│
└──────────┬──────────┘
│
▼
┌────────────────────────────────────────────┐
│ Kubernetes Cluster │
│ │
│ ┌─────────────┐ ┌─────────────┐ │
│ │ Node 1 │ │ Node 2 │ │
│ │ NodePort │ │ NodePort │ │
│ │ 30080 │ │ 30080 │ │
│ └──────┬──────┘ └──────┬──────┘ │
│ │ │ │
│ └───────┬────────┘ │
│ ▼ │
│ ┌─────────────┐ │
│ │ Service │ │
│ └─────────────┘ │
│ │ │
│ ┌───────┴────────┐ │
│ ▼ ▼ │
│ ┌───────────┐ ┌───────────┐ │
│ │ Pod A │ │ Pod B │ │
│ └───────────┘ └───────────┘ │
└────────────────────────────────────────────┘External IP allocation:
# Get the external IP (may take a moment) kubectl get service web-service NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) web-service LoadBalancer 10.96.0.1 203.0.113.50 80:30080/TCP # Now access your app at http://203.0.113.50
Type 4: ExternalName
ExternalName maps a Service to a DNS name outside the cluster. It does not have a selector or endpoints.
Use when: You want internal Pods to access external services using a consistent name.
apiVersion: v1 kind: Service metadata: name: external-database spec: type: ExternalName externalName: database.example.com
Now Pods can connect to external-database.default.svc.cluster.local and be routed to database.example.com.
Part 4: Ingress
What is Ingress?
While Services handle simple routing, Ingress provides sophisticated HTTP/HTTPS routing based on hostnames and URL paths. It acts as a "smart router" for external traffic entering your cluster.
Think of Ingress as the traffic controller at the entrance to your cluster. It looks at each request and decides: "This request for api.example.com/v1 should go to the API service. This request for example.com should go to the web service."
Ingress vs Service
| Aspect | Service | Ingress |
|---|---|---|
| Protocol | TCP/UDP | HTTP/HTTPS only |
| Routing | IP:Port | Hostname + path |
| SSL/TLS | Manual | Native support |
| Single IP | One per LoadBalancer | Many services one IP |
| Cost | Higher (multiple LBs) | Lower (one LB) |
Before Ingress (multiple LoadBalancers): ┌─────────────────────────────────────────────────────────────┐ │ ┌─────────────┐ ┌─────────────┐ │ │ │ AWS ELB │ │ AWS ELB │ │ │ │ (Cost $) │ │ (Cost $) │ │ │ └──────┬──────┘ └──────┬──────┘ │ │ │ │ │ │ ┌──────▼──────┐ ┌──────▼──────┐ │ │ │ api-service │ │ web-service │ │ │ └─────────────┘ └─────────────┘ │ └─────────────────────────────────────────────────────────────┘ After Ingress (one LoadBalancer): ┌─────────────────────────────────────────────────────────────┐ │ ┌─────────────┐ │ │ │ AWS ELB │ │ │ │ (Cost $) │ │ │ └──────┬──────┘ │ │ │ │ │ ┌──────▼──────┐ │ │ │ Ingress │ │ │ └──┬───────┬──┘ │ │ │ │ │ │ ┌──────▼──┐ ┌──▼──────┐ │ │ │ api- │ │ web- │ │ │ │ service │ │ service │ │ │ └─────────┘ └─────────┘ │ └─────────────────────────────────────────────────────────────┘
Ingress YAML Example
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: my-ingress annotations: nginx.ingress.kubernetes.io/rewrite-target: / spec: ingressClassName: nginx tls: - hosts: - example.com secretName: example-tls rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80 - path: /api pathType: Prefix backend: service: name: api-service port: number: 8080
How Traffic Flows with Ingress
External User
│
│ https://example.com/api/users
▼
┌─────────────────────────────────────────────────────────────┐
│ Cloud Load Balancer │
│ (AWS ELB) │
└─────────────────────────────┬───────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Ingress Controller │
│ (Nginx / AWS ALB) │
│ │
│ Checks: host = example.com, path = /api → api-service │
│ host = example.com, path = / → web-service │
└──────────────┬──────────────────────────────┬───────────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ api-service │ │ web-service │
│ (ClusterIP) │ │ (ClusterIP) │
└────────┬─────────┘ └────────┬─────────┘
│ │
▼ ▼
┌──────────────────┐ ┌──────────────────┐
│ api-pod-1 │ │ web-pod-1 │
│ api-pod-2 │ │ web-pod-2 │
└──────────────────┘ └──────────────────┘Ingress Controllers
Ingress is just a specification. You need an Ingress Controller to implement it. Popular options:
| Controller | Best For | Features |
|---|---|---|
| NGINX Ingress | General purpose | Most common, flexible |
| AWS ALB Ingress | AWS environments | Native AWS integration |
| GCE Ingress | Google Cloud | Native GCP integration |
| Traefik | Microservices | Dynamic configuration |
| HAProxy Ingress | Performance | High throughput, low latency |
| Contour | Envoy-based | Modern, high performance |
Installing NGINX Ingress Controller
# Install using Helm helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm install ingress-nginx ingress-nginx/ingress-nginx # Or using kubectl kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.10.0/deploy/static/provider/cloud/deploy.yaml
Part 5: Service Discovery
DNS in Kubernetes
Kubernetes has a built-in DNS service (CoreDNS). Every Service gets a DNS name automatically.
DNS naming pattern:
<service-name>.<namespace>.svc.cluster.local
Examples:
web-service.default.svc.cluster.local api-service.production.svc.cluster.local database.staging.svc.cluster.local
Pod-to-Service Communication
Pods can communicate with Services using the Service name:
# From any Pod in the default namespace curl http://web-service # From any Pod in any namespace curl http://web-service.default.svc.cluster.local
Headless Services
For stateful applications that need direct Pod access, create a headless Service (set clusterIP: None):
apiVersion: v1 kind: Service metadata: name: stateful-service spec: clusterIP: None selector: app: stateful-app ports: - port: 8080
Headless Services return Pod IPs instead of a single Service IP. Useful for StatefulSets.
Part 6: Network Policies
What are Network Policies?
Network Policies control traffic flow between Pods and external endpoints. They act as a firewall for your Pods.
By default, all Pods can communicate with all other Pods. Network Policies let you restrict this.
apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: backend-policy spec: podSelector: matchLabels: app: backend policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080
This policy only allows frontend Pods to access backend Pods on port 8080. All other traffic is denied.
Real-World Scenarios
Scenario 1: Three-Tier Web Application
A web application has three tiers: web, API, and database. Traffic flow: internet → web → API → database.
# Web Service (external access) apiVersion: v1 kind: Service metadata: name: web-service spec: type: LoadBalancer selector: app: web ports: - port: 80 targetPort: 3000 --- # API Service (internal only) apiVersion: v1 kind: Service metadata: name: api-service spec: type: ClusterIP selector: app: api ports: - port: 8080 targetPort: 8080 --- # Database Service (internal only) apiVersion: v1 kind: Service metadata: name: database-service spec: type: ClusterIP selector: app: database ports: - port: 5432 targetPort: 5432
Scenario 2: Multiple Applications with Ingress
A team runs three applications on one cluster: example.com (main site), api.example.com (API), shop.example.com (store).
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: multi-app-ingress spec: ingressClassName: nginx tls: - hosts: - example.com - api.example.com - shop.example.com secretName: wildcard-tls rules: - host: example.com http: paths: - path: / pathType: Prefix backend: service: name: web-service port: number: 80 - host: api.example.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 8080 - host: shop.example.com http: paths: - path: / pathType: Prefix backend: service: name: shop-service port: number: 3000
Scenario 3: Internal API with Strict Access
An internal API should only be accessible from the frontend namespace.
# Ingress (internal, not exposed to internet) apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: internal-api-ingress annotations: nginx.ingress.kubernetes.io/whitelist-source-range: "10.0.0.0/8" spec: ingressClassName: nginx rules: - host: api.internal.company.com http: paths: - path: / pathType: Prefix backend: service: name: api-service port: number: 8080 # Network Policy to restrict access apiVersion: networking.k8s.io/v1 kind: NetworkPolicy metadata: name: api-network-policy spec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: name: frontend - ipBlock: cidr: 10.0.0.0/8 ports: - protocol: TCP port: 8080
Summary
| Component | Purpose | When to Use |
|---|---|---|
| Service (ClusterIP) | Internal load balancing | Pod-to-Pod communication |
| Service (NodePort) | Basic external access | Testing, no cloud LB |
| Service (LoadBalancer) | Production external access | Cloud environments |
| Service (ExternalName) | External service alias | Accessing external services |
| Ingress | HTTP/HTTPS routing | Multi-service web applications |
| NetworkPolicy | Traffic restriction | Security, compliance |
Decision Flow
Do you need external access?
├─ No → ClusterIP Service
└─ Yes
├─ Is it HTTP/HTTPS traffic?
│ ├─ Yes → Ingress + ClusterIP Service
│ └─ No → NodePort or LoadBalancer Service
└─ Are you in the cloud?
├─ Yes → LoadBalancer Service
└─ No → NodePort ServicePractice Questions
Why do Kubernetes Pods need Services for stable communication?
What is the difference between ClusterIP, NodePort, and LoadBalancer Service types?
When would you use Ingress instead of a LoadBalancer Service?
How do Pods discover the IP address of a Service?
What is the purpose of Network Policies?
Learn More
Practice Kubernetes networking with hands-on exercises in our interactive labs:
https://devops.trainwithsky.com/
Comments
Post a Comment