Part ofKubernetes Foundations·Step 2 of 4
DevOps & Platform

Kubernetes Networking & Ingress

Intermediate55 min to complete16 min readJune 1, 2026Updated August 19, 2026

Quick answer

Understand how traffic flows inside a Kubernetes cluster and into it — pod networking, Service DNS, Network Policies, Ingress controllers, and TLS termination.

intermediate · 55 min

Before you begin

  • Kubernetes core concepts — Pods, Deployments, Services
  • Basic networking — IP addresses, DNS, HTTP/HTTPS
Kubernetes
Networking
Ingress
Network Policies
CNI
DNS

Kubernetes Networking & Ingress

Understanding Kubernetes networking means understanding how three layers work together: pod-to-pod networking across nodes, Service-based load balancing and discovery, and Ingress for routing external traffic. Each layer builds on the previous one.


How Pod Networking Works

Kubernetes enforces a simple networking model:

  • Every Pod gets a unique IP address
  • Pods on the same node and Pods on different nodes can all communicate directly — no NAT, no port mapping
  • Pods can reach each other's IP without knowing which node they're on

This flat network model is implemented by a CNI plugin (Container Network Interface). The cluster admin installs one when the cluster is set up:

CNI PluginNotes
CalicoMost widely deployed; supports Network Policies
CiliumeBPF-based; best performance and observability
FlannelSimple overlay network; does NOT support Network Policies
WeaveNetEasy setup; supports Network Policies

If your cluster uses Flannel, NetworkPolicy objects are accepted by the API server but have no effect — there's no enforcement. Use Calico or Cilium if you need Network Policies.

Why pods have their own IPs

In Docker, containers share the host network by default unless you create a user-defined bridge. In Kubernetes, each Pod gets its own network namespace with its own IP. This means:

  • Port 8080 on pod-a is different from port 8080 on pod-b — no conflicts
  • Two containers inside the same Pod share an IP — they communicate via localhost
  • No NAT between pods — Pod A can connect to Pod B's IP directly

Services Internals

A Service provides a stable virtual IP (ClusterIP) and DNS name for a group of Pods. When you create a Service, kube-proxy on each node installs iptables (or IPVS) rules that forward traffic to one of the matching Pod IPs.

bash
kubectl get endpoints api       # See the actual pod IPs behind a service
# NAME   ENDPOINTS                         AGE
# api    10.244.0.5:80,10.244.1.3:80       5m

If a Pod fails its readiness probe, its IP is removed from the Endpoints list automatically. Traffic stops reaching it without you doing anything.

Service DNS

CoreDNS runs in every Kubernetes cluster and provides in-cluster DNS. Every Service gets a DNS record:

<service>.<namespace>.svc.cluster.local

From any Pod in the cluster:

bash
1# These all resolve to the same ClusterIP
2curl http://api                              # Same namespace only
3curl http://api.default                      # Any namespace
4curl http://api.default.svc.cluster.local   # Fully qualified
5
6# Check what DNS settings a pod sees
7kubectl exec <pod> -- cat /etc/resolv.conf
8# search default.svc.cluster.local svc.cluster.local cluster.local
9# nameserver 10.96.0.10   ← CoreDNS ClusterIP

The search domains are why curl http://api works — the DNS client appends the search suffixes automatically.

Headless Services

A headless Service (no ClusterIP) returns the individual Pod IPs directly from DNS instead of a single virtual IP. Used by StatefulSets so each replica has a stable DNS name.

yaml
1apiVersion: v1
2kind: Service
3metadata:
4  name: db
5spec:
6  clusterIP: None      # Headless — no ClusterIP
7  selector:
8    app: db
9  ports:
10    - port: 5432

DNS for a headless Service returns all Pod IPs. StatefulSet pods get individual DNS names:

db-0.db.default.svc.cluster.local
db-1.db.default.svc.cluster.local

Network Policies — Restricting Traffic

By default, all Pods in a cluster can talk to all other Pods. Network Policies restrict this.

A Network Policy selects Pods using podSelector and defines allowed ingress (inbound) and/or egress (outbound) traffic.

Default deny all ingress

yaml
1apiVersion: networking.k8s.io/v1
2kind: NetworkPolicy
3metadata:
4  name: deny-all-ingress
5  namespace: production
6spec:
7  podSelector: {}    # Selects ALL Pods in the namespace
8  policyTypes:
9    - Ingress
10  # No ingress rules = deny all inbound traffic

After applying this, nothing can reach any Pod in the production namespace unless another NetworkPolicy explicitly allows it.

Allow only from within the same namespace

yaml
1apiVersion: networking.k8s.io/v1
2kind: NetworkPolicy
3metadata:
4  name: allow-same-namespace
5  namespace: production
6spec:
7  podSelector: {}
8  policyTypes:
9    - Ingress
10  ingress:
11    - from:
12        - podSelector: {}    # Any pod in the same namespace

Allow the database to accept connections only from the API

yaml
1apiVersion: networking.k8s.io/v1
2kind: NetworkPolicy
3metadata:
4  name: db-policy
5  namespace: production
6spec:
7  podSelector:
8    matchLabels:
9      app: db
10  policyTypes:
11    - Ingress
12  ingress:
13    - from:
14        - podSelector:
15            matchLabels:
16              app: api     # Only pods with app=api can reach db
17      ports:
18        - protocol: TCP
19          port: 5432

Allow egress to DNS only

yaml
1apiVersion: networking.k8s.io/v1
2kind: NetworkPolicy
3metadata:
4  name: allow-dns-egress
5spec:
6  podSelector:
7    matchLabels:
8      app: api
9  policyTypes:
10    - Egress
11  egress:
12    - ports:
13        - protocol: UDP
14          port: 53          # DNS
15        - protocol: TCP
16          port: 53

Always include a DNS egress rule when writing egress policies. Without it, the Pod can't resolve hostnames.


Ingress — Routing External Traffic

A Service of type LoadBalancer creates a separate cloud load balancer per service — expensive at scale. An Ingress routes HTTP/HTTPS traffic from a single load balancer to multiple Services based on hostname and path.

Internet → Load Balancer → Ingress Controller → Service → Pods

Ingress Controller

An Ingress object is just configuration. You need an Ingress Controller — a running Pod that reads Ingress objects and actually routes traffic.

bash
1# Install nginx Ingress Controller via Helm
2helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
3helm repo update
4helm install ingress-nginx ingress-nginx/ingress-nginx \
5  --namespace ingress-nginx \
6  --create-namespace
7
8# Verify
9kubectl get pods -n ingress-nginx
10kubectl get service ingress-nginx-controller -n ingress-nginx

Popular Ingress controllers:

ControllerNotes
ingress-nginxMost widely used; rich annotation support
TraefikBuilt-in Let's Encrypt; good for small clusters
AWS ALB controllerNative AWS Application Load Balancer
ContourEnvoy-based; good performance

A basic Ingress

yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: api-ingress
5spec:
6  ingressClassName: nginx
7  rules:
8    - host: api.example.com
9      http:
10        paths:
11          - path: /
12            pathType: Prefix
13            backend:
14              service:
15                name: api
16                port:
17                  number: 80

Path-based routing

yaml
1spec:
2  ingressClassName: nginx
3  rules:
4    - host: example.com
5      http:
6        paths:
7          - path: /api
8            pathType: Prefix
9            backend:
10              service:
11                name: api
12                port:
13                  number: 80
14          - path: /
15            pathType: Prefix
16            backend:
17              service:
18                name: frontend
19                port:
20                  number: 80

Multiple hostnames

yaml
1spec:
2  ingressClassName: nginx
3  rules:
4    - host: api.example.com
5      http:
6        paths:
7          - path: /
8            pathType: Prefix
9            backend:
10              service:
11                name: api
12                port:
13                  number: 80
14    - host: admin.example.com
15      http:
16        paths:
17          - path: /
18            pathType: Prefix
19            backend:
20              service:
21                name: admin
22                port:
23                  number: 80

TLS Termination

TLS is terminated at the Ingress Controller. The Ingress references a kubernetes.io/tls Secret containing the certificate and key.

bash
# Create a TLS secret from a certificate file
kubectl create secret tls api-tls-cert \
  --cert=tls.crt \
  --key=tls.key
yaml
1apiVersion: networking.k8s.io/v1
2kind: Ingress
3metadata:
4  name: api-ingress
5  annotations:
6    nginx.ingress.kubernetes.io/ssl-redirect: "true"
7spec:
8  ingressClassName: nginx
9  tls:
10    - hosts:
11        - api.example.com
12      secretName: api-tls-cert    # The Secret created above
13  rules:
14    - host: api.example.com
15      http:
16        paths:
17          - path: /
18            pathType: Prefix
19            backend:
20              service:
21                name: api
22                port:
23                  number: 80

cert-manager — automatic certificate management

For production, use cert-manager to automatically issue and renew Let's Encrypt certificates:

bash
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
  --namespace cert-manager \
  --create-namespace \
  --set crds.enabled=true

With cert-manager, add a single annotation to the Ingress:

yaml
metadata:
  annotations:
    cert-manager.io/cluster-issuer: "letsencrypt-prod"

cert-manager handles the ACME challenge, issues the certificate, and rotates it automatically.


Debugging Networking

bash
1# Check if a service has endpoints (pods are passing readiness probes)
2kubectl get endpoints <service-name>
3
4# Test connectivity from inside the cluster
5kubectl run debug --image=busybox --rm -it --restart=Never -- sh
6# Inside the debug pod:
7wget -O- http://api                    # Test Service DNS
8nslookup api.default.svc.cluster.local # Test DNS resolution
9wget -O- http://10.244.0.5:80          # Test direct pod IP
10
11# Check Ingress events
12kubectl describe ingress <name>
13
14# Check Ingress controller logs
15kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginx

Frequently Asked Questions

How does a Service actually route traffic?

The Service is a stable virtual address; the routing is done by kube-proxy or an eBPF dataplane programming rules on every node to distribute connections across the Pod IPs currently in the endpoint list. That list is maintained by the EndpointSlice controller from the Service's selector, which is why readiness affects routing.

Why does my Service have no endpoints?

The selector matches no running Pod's labels, or the matching Pods are not ready. Compare the selector against the Pod labels rather than the Deployment's, since the Pod template produces them. If labels match, check readiness — a Pod that is not ready is deliberately excluded.

When do I need ClusterIP versus NodePort versus LoadBalancer?

ClusterIP for internal traffic, which is most Services. LoadBalancer for external traffic on a cloud that provisions one. NodePort mostly as a building block or for bare metal without a load balancer — exposing a NodePort directly to the internet is rarely what you want.

Do NetworkPolicies work on any cluster?

Only where the CNI enforces them. NetworkPolicy is an API any CNI may implement, and some do not, so the objects are accepted and silently ignored. Confirm your CNI enforces policy before relying on it — silent acceptance is the failure mode that catches people.

What's Next

Official References

Next in Kubernetes Foundations

Kubernetes Storage, ConfigMaps & Secrets

Continue

We built Podscape to simplify Kubernetes workflows like this — logs, events, and cluster state in one interface, without switching tools.

Struggling with this in production?

We help teams fix these exact issues. Our engineers have deployed these patterns across production environments at scale.