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.
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 onOne request from one pod to another, inside the cluster
Here's what happens when code in the checkout pod calls http://orders-api.orders:
Two things to take from this:
- The DNS name is
<service>.<namespace>. From inside the same namespace,orders-apialone works. From another namespace, useorders-api.orders. The full form isorders-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.
Choosing how a Service is exposed
| Type | Reachable from | Typical use |
|---|---|---|
ClusterIP (default) | Inside the cluster only | Service-to-service traffic. Most Services should be this. |
LoadBalancer | Outside the cluster | A cloud controller creates a real load balancer for it (a Network Load Balancer on EKS), for TCP/UDP or non-HTTP traffic. |
NodePort | Every node's IP, on a high port | Handy in a local lab; in the cloud, mostly a building block for load balancers. |
Headless (clusterIP: None) | Inside the cluster | DNS 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.
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: 80The 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.
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]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:80Implementation 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.
portandtargetPortare different things. Callers useport; the container must be listening ontargetPort. A mismatch gives connection errors even though everything looks healthy.kubectl port-forwardis 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.
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.
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.
-
Scenario
Code in the
checkoutnamespace calls theorders-apiService in theordersnamespace. Which name works?Show answer
orders-api.ordersShort names only resolve within the same namespace. Across namespaces, use
<service>.<namespace>. Pod IPs change on every rollout, so never use them. -
Scenario
A Service has no endpoints. What are the two most likely causes?
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.
-
Concept
Why can't you ping a ClusterIP?
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.
-
Concept
You want several HTTP services reachable from the internet under one hostname, routed by path. What do you use?
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.
-
Concept
By default, which pods can connect to a pod in the
paymentsnamespace?Show answer
Any pod in any namespace
Kubernetes networking is flat by default. NetworkPolicies, enforced by the network plugin, are how you restrict it.