Core concepts · Networking

Services & Cluster Networking: How Traffic Finds Your Pods

Pods come and go with new IP addresses, so nothing should talk to a pod directly. How Services give a stable name and address, how cluster DNS and kube-proxy route each request, the Service types, and how an Ingress brings traffic in from outside the cluster.

Module: 06 of 20 Level: Core concepts · Networking Time: 11 min read Updated:
All modules in this track (20)

In this module, you'll learn to:

  • Explain why apps talk to Services, not to pods
  • Follow one request through cluster DNS, a ClusterIP, and kube-proxy to a ready pod
  • Choose a Service type, and bring outside traffic in with an Ingress

Before you start: this builds on Pods, Deployments & Rollouts, especially labels and readiness probes. A basic idea of IP addresses, ports, and DNS helps.

Principle

Never talk to a pod, talk to a Service

In the last module, every rollout created new pods with new IP addresses. If the checkout app called orders-api at 10.0.12.34, that call would break on the next deploy. So Kubernetes adds a stable layer in front: a Service.

A Service gives a group of pods one name and one address that never change. It finds its pods using the same labels the Deployment uses, and keeps an up-to-date list of the ones that are ready. That list is stored as EndpointSlices, and it changes every time a pod starts, stops, or passes or fails its readiness probe.

apiVersion: v1
kind: Service
metadata:
  name: orders-api
  namespace: orders
spec:
  selector:
    app: orders-api        # the same label as the Deployment's pods
  ports:
    - port: 80             # the port callers use
      targetPort: 8080     # the port the container listens on
Scroll sideways to see the full diagram →
Flow

One request from one pod to another, inside the cluster

Here's what happens when code in the checkout pod calls http://orders-api.orders:

Pod-to-pod traffic through a ClusterIP Service
checkout podcalls http://orders-api.orders
DNS lookup inside the cluster
CoreDNSorders-api.orders.svc.cluster.local resolves to 172.20.88.14
the request goes to that Service address (ClusterIP)
Service orders-apia virtual IP. No process listens on it; it's a routing rule
kube-proxy's rules on the node pick one ready pod and rewrite the address
Ready
orders-api pod 110.0.12.34:8080 receives the request
Ready
orders-api pod 210.0.40.18:8080
Not ready
orders-api pod 3failing readiness, so not in the list and gets no traffic

Two things to take from this:

  • The DNS name is <service>.<namespace>. From inside the same namespace, orders-api alone works. From another namespace, use orders-api.orders. The full form is orders-api.orders.svc.cluster.local.
  • The ClusterIP isn't a real machine. It exists only as routing rules that kube-proxy writes on every node. That's why you can't ping it, and why a "connection refused" there usually means there are no ready pods behind it.

Where do the pod IP addresses themselves come from? A network plugin (CNI) on each node hands them out. In a local kind cluster they come from a private range inside Docker; on Amazon EKS they're real addresses from your VPC subnets, which has consequences covered in Kubernetes on Amazon EKS.

Service types

Choosing how a Service is exposed

TypeReachable fromTypical use
ClusterIP (default)Inside the cluster onlyService-to-service traffic. Most Services should be this.
LoadBalancerOutside the clusterA cloud controller creates a real load balancer for it (a Network Load Balancer on EKS), for TCP/UDP or non-HTTP traffic.
NodePortEvery node's IP, on a high portHandy in a local lab; in the cloud, mostly a building block for load balancers.
Headless (clusterIP: None)Inside the clusterDNS returns the pod IPs themselves. Used by StatefulSets and client-side load balancing.

For HTTP and HTTPS from outside, you don't expose each Service as a load balancer. You use an Ingress, which the next section shows.

Ingress

From outside the cluster to a pod

An Ingress is a set of HTTP routing rules ("requests for api.example.com/orders go to the orders-api Service"). On its own it does nothing. An ingress controller makes it real: it watches Ingress objects and configures a proxy or a cloud load balancer to match, using the same reconciliation loop from How a Cluster Works.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: orders-api
  namespace: orders
spec:
  ingressClassName: alb      # which controller handles it
  rules:
    - host: api.example.com
      http:
        paths:
          - path: /orders
            pathType: Prefix
            backend:
              service:
                name: orders-api
                port:
                  number: 80
Scroll sideways to see the full diagram →

The Service still matters here. The controller reads the Service's endpoints to know which pods to send to, so readiness probes control what the load balancer sends traffic to as well. On Amazon EKS the controller is usually the AWS Load Balancer Controller, and the load balancer is an Application Load Balancer. Kubernetes on Amazon EKS follows that exact path, and Ingress Architecture & Cost covers how many load balancers to run and Ingress's successor, the Gateway API.

Security

By default, every pod can reach every pod

Kubernetes networking is flat: out of the box, any pod can connect to any other pod in any namespace. A NetworkPolicy limits that, for example allowing only the checkout namespace to reach orders-api. A policy only takes effect if the cluster's network plugin enforces it; on EKS, the VPC CNI does once you turn the feature on in the add-on's configuration. Start with a "deny all incoming" default per namespace, then allow what each app needs.

# Deny all incoming traffic to every pod in the namespace, then add
# narrower policies that allow what each app actually needs
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: orders
spec:
  podSelector: {}
  policyTypes: [Ingress]
Scroll sideways to see the full diagram →
Try it

See the pieces in a running cluster

# Services and their ClusterIPs
kubectl get services -n orders

# The pod IPs currently behind a Service (ready ones only)
kubectl get endpointslices -n orders -l kubernetes.io/service-name=orders-api

# Start a temporary pod and test DNS and HTTP from inside the cluster
kubectl run tmp-debug -n orders --rm -it --image=busybox:1.36 --restart=Never -- sh
  nslookup orders-api.orders
  wget -qO- http://orders-api.orders/healthz/ready

# Reach a Service from your laptop without any load balancer
kubectl port-forward -n orders service/orders-api 8080:80
Scroll sideways to see the full diagram →
Implementation notes
  • "Service has no endpoints" is the first thing to check when requests fail. It almost always means the selector doesn't match the pod labels, or no pod is passing readiness.
  • port and targetPort are different things. Callers use port; the container must be listening on targetPort. A mismatch gives connection errors even though everything looks healthy.
  • kubectl port-forward is a debugging tool, not a way to serve users. It tunnels through the API server from your machine only, and stops when you close the terminal.
  • Debug layer by layer. DNS, then Service, then endpoints, then pod. The EKS ingress incident case study applies exactly this order to a real outage.
Recap

Key terms

  • Service: a stable name and address in front of a changing set of pods.
  • ClusterIP: a Service's virtual IP; really just routing rules on each node.
  • EndpointSlice: the current list of ready pod IPs behind a Service.
  • CoreDNS: the cluster's DNS server, which resolves names like orders-api.orders.
  • Ingress: HTTP routing rules for traffic from outside, made real by an ingress controller.
  • NetworkPolicy: a rule limiting which pods may connect to which.
Check yourself

Pop quiz

Five questions: three on the ideas in this module, and two scenarios where you apply them. The order changes every time you take it, and 4 out of 5 passes.

5 questions. Decide on your answer to each, then check it.

  1. Scenario

    Code in the checkout namespace calls the orders-api Service in the orders namespace. Which name works?

    1. orders-api
    2. orders-api.orders
    3. orders.orders-api
    4. The IP address of an orders-api pod
    Show answer

    orders-api.orders

    Short names only resolve within the same namespace. Across namespaces, use <service>.<namespace>. Pod IPs change on every rollout, so never use them.

  2. Scenario

    A Service has no endpoints. What are the two most likely causes?

    1. Its selector doesn't match the pod labels, or no pod is passing readiness
    2. The ClusterIP is wrong, or DNS is down
    3. The Ingress is missing, or the load balancer is down
    4. The namespace is full, or the node is out of memory
    Show answer

    Its selector doesn't match the pod labels, or no pod is passing readiness

    Endpoints are the ready pods that match the selector. A label typo or a failing readiness probe leaves the list empty.

  3. Concept

    Why can't you ping a ClusterIP?

    1. A NetworkPolicy blocks it by default
    2. It isn't a real machine, only routing rules that kube-proxy writes on each node
    3. ClusterIPs only accept HTTPS
    4. It's only reachable from outside the cluster
    Show answer

    It isn't a real machine, only routing rules that kube-proxy writes on each node

    Nothing listens on the ClusterIP. kube-proxy's rules send traffic for its ports to a ready pod, so a ping gets no answer.

  4. Concept

    You want several HTTP services reachable from the internet under one hostname, routed by path. What do you use?

    1. A LoadBalancer Service for each one
    2. A NodePort for each one
    3. An Ingress (or Gateway API routes) handled by a controller
    4. A headless Service
    Show answer

    An Ingress (or Gateway API routes) handled by a controller

    An Ingress routes HTTP by host and path to different Services through one entry point. A LoadBalancer Service per app costs one load balancer each.

  5. Concept

    By default, which pods can connect to a pod in the payments namespace?

    1. Only pods in payments
    2. None, until you add a NetworkPolicy
    3. Any pod in any namespace
    4. Only pods with the same labels
    Show answer

    Any pod in any namespace

    Kubernetes networking is flat by default. NetworkPolicies, enforced by the network plugin, are how you restrict it.

Questions about this learning path?

I'm happy to go into more detail on any of this: how the pieces fit together, ingress costs, RBAC design, or how I triage incidents.

anupojuprudhvi@gmail.com