Services: Stable Networking for Ephemeral Pods
Give Pods a stable address, expose them inside and outside the cluster, and restrict who can talk to whom.
Every Pod gets its own IP address, but Pods come and go, and their addresses go with them. A Service provides a stable address in front of a changing set of Pods. This guide builds on the web Deployment from the previous guide and covers Services, exposing them outside the cluster, Ingress, and network policies.
The problem: Pod IPs change
kubectl get pods -l app=web -o wide
kubectl delete pod POD_NAME
kubectl get pods -l app=web -o wide
The replacement Pod has a different IP, and it may be on a different node. Anything that had the old address hard-coded is now broken, and with replicas that come and go, tracking addresses by hand isn’t possible.
Create a ClusterIP Service
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80
The selector picks the Pods that back the Service, using the same labels you saw with Deployments. port is the port the Service listens on, and targetPort is the port on the Pods it forwards to, which should match the container’s port. Because no type is set, this is a ClusterIP Service: reachable only from inside the cluster, on a stable virtual IP.
kubectl apply -f service.yaml
kubectl get service web
kubectl get endpointslices -l kubernetes.io/service-name=web
The EndpointSlice lists the Pod IPs currently behind the Service. Scale the Deployment up or down and it updates automatically, while the Service’s own address never changes. (Older clusters and tutorials use kubectl get endpoints web for the same information. Recent versions deprecate that API in favor of EndpointSlices.)
Inside the cluster, the Service also gets a DNS name: web from the same namespace, or web.NAMESPACE.svc.cluster.local from anywhere. Test it from a throwaway Pod:
kubectl run tmp --rm -it --image=busybox:1.36 --restart=Never -- wget -qO- http://web
You should see the nginx welcome page. On each node, kube-proxy keeps the rules that spread connections across the Service’s ready Pods. In the common iptables mode this is effectively random rather than strict round-robin, and Pods that fail their readiness check are taken out of rotation.
Expose a Service outside the cluster
There are two more Service types, and each builds on the one before:
- NodePort also opens a port (30000–32767) on every node. Anything that can reach a node’s IP can reach the Service on that port.
- LoadBalancer additionally asks the environment for an external load balancer pointing at those node ports. On a cloud provider that happens automatically. On bare metal you need something like MetalLB.
apiVersion: v1
kind: Service
metadata:
name: web-public
spec:
type: NodePort
selector:
app: web
ports:
- port: 80
targetPort: 80
nodePort: 30080
Leave out nodePort and Kubernetes picks a free one. On minikube with the Docker driver, the node’s IP often isn’t reachable directly from your laptop, so let minikube open it for you:
minikube service web-public --url
For a LoadBalancer Service on minikube, run minikube tunnel in another terminal.
Route HTTP with Ingress
A LoadBalancer per Service gets expensive and only handles raw traffic. An Ingress lets you route HTTP(S) traffic to many Services by host and path, through a single entry point, and terminate TLS there. An Ingress only describes the rules. It needs an ingress controller running in the cluster to act on them (on minikube, minikube addons enable ingress).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: web.example.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
number: 80
Requests for web.example.local now go to the web Service. Certificates for the host can be automated with a tool like cert-manager.
Restrict traffic with NetworkPolicy
By default every Pod can talk to every other Pod. A NetworkPolicy selects Pods and lists what traffic is allowed. Once a Pod is selected for ingress by any policy, everything not explicitly allowed is denied. Start by denying all incoming traffic in a namespace:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
The empty podSelector selects every Pod in the namespace. Then allow only what you need, for instance traffic to the web Pods from Pods labeled role: client:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-client-to-web
spec:
podSelector:
matchLabels:
app: web
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
role: client
ports:
- protocol: TCP
port: 80
Network policies are namespaced, and they are only enforced if the cluster’s network plugin supports them, for example Calico or Cilium. Some minimal local setups accept the objects but silently ignore them, so test that a denied connection really is blocked.
Popular Guides
-
What Kubernetes Is and the Problems It Solves Why containers alone stop scaling, and how Kubernetes turns a group of machines into one platform. -
How a Kubernetes Cluster Works: Control Plane and Nodes The components that make a cluster tick, and what happens between kubectl apply and a running container. -
Deployments: Scaling, Self-Healing, and Rolling Updates Run replicas of a Pod, update them without downtime, and roll back when a release goes wrong. -
Debug a Failing Pod: describe, logs, exec, and port-forward A repeatable checklist for finding out why a Pod is Pending, crashing, or not answering.