4.2 Services Deep Dive: ClusterIP, NodePort, LoadBalancer & EndpointSlices
Key Takeaways
- A Kubernetes Service provides a stable, persistent virtual IP (VIP) and DNS name abstraction over a dynamic, ephemeral pool of backend Pod IPs.
- ClusterIP is the default service type providing internal cluster-only reachability; Headless Services (clusterIP: None) bypass VIP allocation to return direct Pod IPs via DNS for stateful clustering.
- NodePort opens a dedicated high-order port (default 30000-32767) across all cluster nodes, while LoadBalancer provisions an external cloud load balancer pointing to those NodePorts.
- The EndpointSlice API (discovery.k8s.io/v1) splits large endpoint sets into scalable 100-endpoint chunks, eliminating the O(N) etcd write amplification and control plane churn of legacy Endpoints.
- Setting externalTrafficPolicy: Local preserves client source IP addresses and avoids second-hop routing at the cost of potential load imbalance and dropped traffic on nodes lacking local backend pods.
4.2 Services Deep Dive: ClusterIP, NodePort, LoadBalancer & EndpointSlices
Pods in Kubernetes are ephemeral; they are dynamically created, scaled, rescheduled, and terminated, resulting in constantly changing IP addresses. Applications cannot reliably connect directly to individual Pod IPs. The Service abstraction provides a persistent, deterministic network endpoint—encapsulating a stable Virtual IP (ClusterIP) and a canonical DNS name—that load-balances traffic across a dynamic set of backend Pods matching a label selector.
1. Service Types & Mechanics
Kubernetes defines four primary Service types in spec.type:
+-----------------------------------------------------------------------------------------+
| KUBERNETES SERVICE TYPES |
| |
| 1. ClusterIP (Default) 2. NodePort 3. LoadBalancer |
| +-----------------------+ +-----------------------+ +--------------------+ |
| | Internal Clients Only | | External Client | | External Internet | |
| +-----------+-----------+ +-----------+-----------+ +---------+----------+ |
| | | (NodeIP:30080) | (Public IP)|
| v v v |
| [ ClusterIP VIP ] [ NodePort 30080 ] [ Cloud Provider ] |
| | | [ Load Balancer ] |
| | v | |
| | [ ClusterIP VIP ] v |
| | | [ NodePort 30080 ] |
| v v | |
| +-------+-------+ +-------+-------+ v |
| | Matching Pods | | Matching Pods | [ ClusterIP VIP ] |
| +---------------+ +---------------+ | |
| v |
| 4. ExternalName: Returns CNAME DNS record (e.g., db.corp.com) +-------+-------+ |
| 5. Headless (clusterIP: None): Direct DNS to Pod IPs | Matching Pods | |
| +---------------+ |
+-----------------------------------------------------------------------------------------+
1. ClusterIP (Default)
- Allocates an immutable virtual IP from the cluster's
--service-cluster-ip-rangeCIDR (e.g.,10.96.0.0/12). - Reachable only from within the cluster (by Pods and node host processes).
kube-proxyprograms Netfilter/IPVS rules on every node to intercept traffic to this VIP and translate the destination IP (DNAT) to one of the healthy backend Pod IPs.
2. NodePort
- Builds on top of
ClusterIP. In addition to allocating a ClusterIP, it allocates a dedicated port from the node port range (default:30000–32767, defined by--service-node-port-range). - Every worker and control plane node opens this port on all network interfaces (
0.0.0.0) and forwards incoming traffic to the underlyingClusterIP.
3. LoadBalancer
- Builds on top of
NodePortandClusterIP. - Integrates with the cloud provider (via
cloud-controller-manager) to provision an external hardware/cloud load balancer (e.g., AWS NLB/ALB, Google Cloud Network Load Balancer, Azure Load Balancer). - The cloud load balancer routes external traffic to the worker nodes on the allocated
NodePort.
4. ExternalName
- A special Service type that does not use label selectors, virtual IPs, or proxying.
- Configures CoreDNS to return a
CNAMErecord pointing to an external FQDN (e.g.,external-db.rds.amazonaws.com).
apiVersion: v1
kind: Service
metadata:
name: my-database-svc
spec:
type: ExternalName
externalName: prod-db.postgres.database.azure.com
5. Headless Services (clusterIP: None)
- When
spec.clusterIPis explicitly set toNone, Kubernetes disables virtual IP allocation and proxying. - CoreDNS returns direct
A/AAAArecords containing the actual IP addresses of all ready matching backend Pods. - Indispensable for stateful distributed databases (Cassandra, MongoDB, Kafka, Elasticsearch, StatefulSets) where clients must establish direct peer-to-peer connections to specific instances.
2. Endpoints vs. EndpointSlices (discovery.k8s.io/v1)
Historically, the control plane maintained a single Endpoints resource per Service. When a Service had 2,000 backend Pod replicas, the single Endpoints object stored all 2,000 IP addresses in a massive list.
+-----------------------------------------------------------------------------------------+
| ENDPOINTS VS. ENDPOINTSLICE SCALABILITY |
| |
| LEGACY ENDPOINTS (Monolithic) MODERN ENDPOINTSLICE (Chunked) |
| +---------------------------------+ +------------------------------------+ |
| | Service: payment-svc | | Service: payment-svc | |
| | Endpoints: | | | |
| | - 10.244.1.2, 10.244.1.3 | | EndpointSlice-01 (100 Endpoints) | |
| | - 10.244.1.4, 10.244.2.10 | | EndpointSlice-02 (100 Endpoints) | |
| | ... [2,000 IP addresses] | | EndpointSlice-03 (100 Endpoints) | |
| +---------------------------------+ +------------------------------------+ |
| * Problem: Single Pod rollout * Solution: Only 1 slice updated |
| re-writes 1.5MB to etcd & sends (Tiny JSON diff sent to kube-proxy) |
| full object to ALL kube-proxies! |
+-----------------------------------------------------------------------------------------+
The EndpointSlice Architecture:
- Introduced to solve the $O(N)$ control plane scalability bottleneck.
- The
endpointslice-controllerbreaks down large sets of network endpoints into multiple smallerEndpointSliceresources (default: 100 endpoints per slice). - When a single Pod restarts or updates its IP address, only the single affected
EndpointSliceobject is mutated inetcdand transmitted across the cluster network, drastically reducing memory usage and API server load. - Supports Dual-Stack IPv4/IPv6 addressing and topological routing hints (
topology.kubernetes.io/zone).
# Inspect EndpointSlices for a service:
kubectl get endpointslices -l kubernetes.io/service-name=payment-svc
kubectl describe endpointslice payment-svc-abc12
3. Selectorless Services and Manual Endpoints
Services can be created without a spec.selector. In this scenario, the control plane does not automatically generate Endpoints or EndpointSlices, allowing administrators to manually create them to point to external systems (e.g., on-premises Oracle database, external REST API, or staging environments):
# Step 1: Create Service without selector
apiVersion: v1
kind: Service
metadata:
name: legacy-oracle-db
spec:
ports:
- protocol: TCP
port: 1521
targetPort: 1521
---
# Step 2: Manually create matching Endpoints object
apiVersion: v1
kind: Endpoints
metadata:
name: legacy-oracle-db # MUST match Service name
subsets:
- addresses:
- ip: 192.168.50.100
- ip: 192.168.50.101
ports:
- port: 1521
4. Source IP Preservation & externalTrafficPolicy
When external traffic enters a Kubernetes cluster via a NodePort or LoadBalancer Service, the standard behavior can obscure the client's genuine source IP address.
| Traffic Policy | Configuration | Source IP Preserved? | Routing Hop Mechanics | Potential Drawbacks |
|---|---|---|---|---|
Cluster (Default) | externalTrafficPolicy: Cluster | NO (Client IP replaced with Node IP via SNAT) | Traffic landing on Node A can be forwarded across the internal overlay to Node B if Node B hosts backend Pods. | Extra network hop introduces latency; security logs see Node IP instead of real client IP. |
Local | externalTrafficPolicy: Local | YES (Direct Client IP preserved) | Node A only routes traffic to Pods running locally on Node A. No internal forwarding hop. | If Node A has zero local backend Pods, traffic sent to Node A is dropped. Cloud LBs must use node health checks. Uneven load distribution. |
apiVersion: v1
kind: Service
metadata:
name: web-ingress-svc
spec:
type: LoadBalancer
externalTrafficPolicy: Local # Preserves real client source IP
selector:
app: web
ports:
- port: 80
targetPort: 8080
[!NOTE] Session Affinity: To ensure requests from the same client IP are consistently directed to the same backend Pod, set
spec.sessionAffinity: ClientIPand tunespec.sessionAffinityConfig.clientIP.timeoutSeconds(default: 10800 seconds / 3 hours).
An administrator configures a Kubernetes Service of type NodePort with 'externalTrafficPolicy: Local'. When an external client sends a request to Worker Node 03, the request hangs and times out. However, requests sent to Worker Node 01 and Node 02 succeed immediately and display the true client IP in the application logs. What is the root cause?
A team is designing a distributed Cassandra database cluster deployed via a StatefulSet. The database client application requires direct TCP communication with individual Cassandra member Pods rather than load-balanced proxying through a single virtual IP. How should the Service be configured?
Why did Kubernetes introduce the EndpointSlice API ('discovery.k8s.io/v1') to replace the monolithic 'Endpoints' API in large-scale production clusters?