How Pod-to-Pod Communication Works in Kubernetes?

Prasad Waghamare
podscommunication.png

1. How Two Pods Talk to Each Other?

Before diving into Kubernetes at all, let us look at what communication actually looks like in the simplest possible setup  because Kubernetes builds directly on top of it.

When you run two applications on the same machine, they communicate through the machine's network stack. Application A binds to a port, say port 5432, and Application B connects to localhost:5432. They share the same IP address, the same network interface, and the same set of ports. The operating system handles delivery internally, without packets ever leaving the machine.

Now imagine taking those two applications and moving them to separate machines. Suddenly they need real IP addresses to find each other. Machine A might be at 10.0.0.4 and Machine B at 10.0.0.7. Application B now connects to 10.0.0.4:5432 instead of localhost. As long as both machines stay running at those addresses, everything works perfectly. This is traditional networking — and it has worked reliably for decades.

Kubernetes does not throw this model away. It builds on it. But as clusters grow to dozens of nodes and hundreds of running workloads, this simple model reveals a critical weakness and Kubernetes has a deliberate answer for each one.

2. Inside a Pod: Back to Localhost

Kubernetes organises containers into Pods. A Pod is the smallest unit Kubernetes schedules  a logical grouping of one or more containers that are meant to work closely together. And the first thing to understand about how communication works inside a Pod is that it behaves exactly like the single-machine scenario described above.

Every container in the same Pod shares one network namespace. That means they share one IP address and one set of ports just like two processes running on the same machine. Container A running on port 8080 is reachable by Container B as simply localhost:8080. No configuration needed, no service discovery, no DNS lookup. They are, from a networking perspective, the same machine.

Why Kubernetes designed it this way

This design makes tightly coupled containers  ones that genuinely need to be co-located  trivially easy to connect. A common real-world pattern is the sidecar: you run your main application container alongside a second container that handles a supporting concern like log forwarding, metrics collection, or TLS termination. Because both containers share localhost, the sidecar can observe and intercept all traffic without the main application knowing it exists or changing a single line of code.

Istio, one of the most popular service meshes, is built entirely on this idea. It injects an Envoy proxy as a sidecar into every Pod. That proxy intercepts all traffic over localhost, applies security policies, collects telemetry, and handles retries completely transparently to the application.

The one constraint to plan for

Because all containers in a Pod share one port space, no two containers in the same Pod can bind to the same port number. If your application uses port 8080, nothing else in that Pod can also claim 8080. This is the only hard constraint of the shared network namespace model, and it is worth designing around from the start when building multi-container Pods.

3. Pod-to-Pod Communication: Crossing the Node Boundary

Once two containers are in different Pods, the conversation changes. They can no longer rely on localhost. Now each Pod needs its own identity on the network — and this is where Kubernetes makes its first major architectural decision.

Every Pod gets its own IP address

Kubernetes assigns every Pod a unique IP address. Not the node's IP. Not a shared address with a port offset. A real, dedicated IP that belongs to that Pod alone. And every Pod in the cluster must be able to reach every other Pod using that IP directly, without any Network Address Translation (NAT) modifying the addresses along the way.

This is known as the flat network model, and it is a deliberate design guarantee. It means developers can treat Pods like individual machines on the same local network. Pod A at 10.1.0.5 sends a packet to Pod B at 10.1.2.3. That packet arrives at Pod B with the original source IP intact. No rewriting, no port mapping, no tunneling required from the application's perspective.

How traffic flows between Pods on the same node

When two Pods share the same node, their traffic never leaves that machine. Each Pod connects to a virtual network bridge that the node maintains  think of it like a software-based network switch sitting inside the server. Pod A sends a packet to the bridge, the bridge looks up which virtual interface belongs to Pod B's IP, and delivers it. The packet stays entirely within the node's memory. This makes same-node Pod communication fast and inexpensive.

How traffic flows between Pods on different nodes

When Pod A on Node 1 sends a packet to Pod B on Node 2, the path is longer but the principle is the same. The packet leaves Pod A, travels through Node 1's virtual bridge, crosses the underlying physical or virtual network between the two nodes, arrives at Node 2, and is delivered to Pod B through Node 2's bridge. Crucially, Pod A's IP and Pod B's IP are preserved throughout the entire journey no address is rewritten anywhere. This no-NAT guarantee makes debugging and observability dramatically simpler. When you see a source IP in a log, it is the real Pod IP.

The CNI plugin: the component doing all of this work

None of this happens by magic. The component that builds and maintains the flat network is called a CNI plugin — short for Container Network Interface. When Kubernetes creates a new Pod, the CNI plugin assigns it an IP from the cluster's address range, creates the virtual network interface inside the Pod, and programs the routing rules on the node so every other node knows how to reach that new IP.

Kubernetes defines the CNI standard but ships no implementation  you choose one for your cluster. Calico is widely used in production for its strong Network Policy support and BGP-based routing. Flannel is simpler and suited to smaller clusters that do not need advanced policies. Cilium uses eBPF a modern Linux kernel technology for high-performance packet processing and Layer 7 policy enforcement. Weave Net encrypts all Pod traffic by default. Each fulfills the same contract: one IP per Pod, reachable by all other Pods, without NAT.

4. The Instability Problem and How Services Solve It

Pod-to-Pod communication over direct IP addresses works. So what is the problem?

The problem is that Pod IPs are temporary. Every time a Pod crashes and restarts, gets replaced during a rolling deployment, fails a health check, or is rescheduled onto a different node, it comes back with a brand new IP address. Any other Pod that stored the old IP to call directly will now get nothing back. In a healthy, active Kubernetes cluster  where Pods are replaced constantly  hardcoding Pod IPs is not just inadvisable; it guarantees broken communication.

What a Kubernetes Service is

A Service is Kubernetes's answer to this problem. When you create a Service, you attach it to a group of Pods using a label selector for example, "target all Pods labeled app: payments." Kubernetes then assigns the Service a ClusterIP: a stable virtual IP address that never changes for the lifetime of that Service.

Behind the scenes, Kubernetes watches which Pods match the selector and maintains an up-to-date list of their current IPs. When a Pod is replaced, the old IP is removed from the list and the new IP is added. The Service's ClusterIP is never affected by any of this. Callers send traffic to one stable address; Kubernetes quietly manages which actual Pods receive it.

How kube-proxy delivers the traffic

On every node in the cluster, a component called kube-proxy watches for Services and the Pods behind them. When a packet arrives addressed to a Service's ClusterIP, kube-proxy intercepts it and rewrites the destination to one of the current healthy Pod IPs — a process called DNAT (Destination Network Address Translation). By default it distributes requests across all available Pods in round-robin order, giving you basic load balancing automatically.

The calling application knows nothing of any of this. It sends to one address; kube-proxy handles the rest.

Choosing the right Service type

Not all Services are meant for the same audience. ClusterIP the default  makes the Service reachable only inside the cluster, which is correct for any internal database, backend API, or worker process that should never be publicly accessible. NodePort opens a port on every node so external traffic can reach the Service from outside the cluster  useful during development. LoadBalancer tells the cloud provider to provision a public-facing load balancer automatically, assigning a stable external IP the standard approach for user-facing services on AWS, GCP, or Azure.

Selecting the wrong type is a common early mistake. ClusterIP for internal things, LoadBalancer for public things — that rule covers most situations.

5. CoreDNS: Finding Services by Name

A Service's ClusterIP solves the stability problem  but applications still need to discover that IP. The answer Kubernetes provides is a built-in DNS server called CoreDNS, which runs inside every cluster as a set of Pods.

CoreDNS automatically creates a DNS record for every Service. When a Pod calls http://payment-api/process, it does not know or store any IP address. It simply asks CoreDNS to resolve payment-api, receives the Service's ClusterIP, and sends the packet. From a developer's perspective, service addresses are just names that always resolve to wherever the healthy Pods currently are.

How DNS names are structured

Within the same namespace, the short Service name is all you need  payment-api resolves correctly. Across namespaces, the full pattern is <service-name>.<namespace>.svc.cluster.local. So a payment API running in the production namespace is addressable as payment-api.production.svc.cluster.local from anywhere in the cluster.

Because these names are stable they never change regardless of how many times the underlying Pods are replaced  application config files can embed them permanently. A DATABASE_URL environment variable set to postgres.production.svc.cluster.local will keep working through every deployment, scaling event, and node failure without ever being updated.

The full journey of a single request

Putting it together: a frontend Pod calls http://order-service/list. CoreDNS resolves order-service to its ClusterIP. The packet reaches the node, where kube-proxy intercepts it and rewrites the destination to a healthy order Pod IP. The order Pod processes the request and sends a response back through the same path in reverse. Every component in this chain  CoreDNS entries, kube-proxy rules, Pod IP lists  updates itself automatically as Pods come and go.

6. Network Policies: Deciding Who Can Talk to Whom

With every Pod now reachable by every other Pod through Services and DNS, there is a natural next question: should every Pod be allowed to reach every other Pod? By default in Kubernetes, the answer is yes. Any Pod can send traffic to any other Pod, regardless of namespace or sensitivity. For a development cluster, this openness is fine. For a production system handling payments alongside internal tooling, it is a meaningful security gap.

What a Network Policy does

A Network Policy is a Kubernetes resource that acts as a firewall rule set for Pods. You declare which Pods the policy targets, and which incoming and outgoing traffic is permitted. Once a policy selects a Pod, that Pod becomes isolated  only explicitly allowed traffic is accepted, and everything else is silently dropped at the packet level.

A concrete example: a Network Policy on payments Pods that allows inbound traffic only from checkout Pods on port 443, and outbound traffic only to postgres Pods on port 5432. No other Pod in the cluster  regardless of where it runs  can reach the payments service. This is the principle of least privilege applied directly to Pod networking.

Enforcement depends on your CNI plugin

Kubernetes defines the Network Policy API but does not enforce it natively that responsibility falls on the CNI plugin. Calico enforces policies using Linux iptables rules at the packet level. Cilium uses eBPF, which has lower overhead and supports Layer 7 rules blocking or allowing based on HTTP method or URL path, not just port numbers. Flannel does not support Network Policies at all; applying them in a Flannel cluster creates the rules in Kubernetes but they have no effect. This is why CNI choice matters beyond just routing.

The recommended production baseline is a default-deny policy per namespace, with explicit allow rules for each intended communication path. This ensures that a compromised Pod cannot silently probe the rest of the cluster.

7. External Traffic: The Front Door

Every layer so far lives inside the cluster. Real applications also need to receive traffic from browsers, mobile clients, and third-party services.

For HTTP and HTTPS traffic, Kubernetes uses the Ingress resource combined with an Ingress controller. The Ingress resource is a routing table  requests for api.example.com/orders go to the order-service, requests for api.example.com/payments go to payment-service. The Ingress controller typically NGINX, Traefik, or a cloud-native option like AWS ALB reads those rules and acts as the reverse proxy that physically receives external traffic and forwards it.

This means one public IP address can serve dozens of internal Services, with routing determined by hostname and URL path. A user request to api.example.com/orders hits the load balancer, reaches the Ingress controller, is matched to order-service, and kube-proxy delivers it to a healthy order Pod  all automatically, with no manual updates needed as the cluster scales.

8. The Complete Communication Picture

Pod communication in Kubernetes is a sequence of clean, composable layers  each one solving exactly the gap the previous layer leaves open.

Containers inside the same Pod share a network namespace and talk over localhost, just like processes on the same machine. Pods across the cluster communicate over a flat IP network built by the CNI plugin, with real Pod IPs preserved end-to-end and no NAT. Services provide stable virtual IPs that survive Pod replacement, with kube-proxy routing traffic to current healthy backends. CoreDNS resolves Service names to those stable IPs so application code never handles an IP directly. Network Policies define which Pods may speak to which others, enforced at the packet level by the CNI. Ingress controllers bring external traffic through a single governed entry point.

Understanding these layers in sequence  how each one works first, then what problem it exists to solve  is what makes advanced Kubernetes topics like service meshes, zero-trust networking, and multi-cluster federation feel approachable rather than overwhelming. They are not new systems; they are extensions of exactly the model described here.

Tags
KubernetesDevOpsCloud NativeKubernetes networkingPods
Maximize Your Cloud Potential
Streamline your cloud infrastructure for cost-efficiency and enhanced security.
Discover how CloudOptimo optimize your AWS and Azure services.
Request a Demo