While ClusterIP services provide internal-only stable endpoints, they use a load balancer by default. When you send traffic to a ClusterIP service, Kubernetes (kube-proxy) automatically distributes that traffic among all matching backend pods. This prevents direct pod-to-pod communication since you can't target a specific pod.
NodePort services expose pods on a specific port across every node in the cluster, making them accessible both internally and externally. This goes beyond your requirement of internal-only communication and adds unnecessary security exposure.
Headless Service with selector
It provides a stable DNS entry (service-name.namespace.svc.cluster.local
)
It's only accessible within the cluster
Most importantly, it returns the individual IP addresses of all pods matching the selector instead of a single virtual IP
When you create a headless service (by setting clusterIP: None
), DNS queries for the service name return the IPs of all matching pods instead of a single virtual IP. This allows client pods to connect directly to specific backend pods without an intermediary load balancer.
Your client applications can then use Kubernetes DNS to discover all backend pod IPs and connect to specific ones as needed.
This pattern is particularly useful for stateful applications where clients need to connect to specific instances, such as database clusters where different pods have different roles (primary/replica).