Pods, Deployments and Services
Pods, Deployments and Services: the three objects that put a .NET API on a cluster, and the loop that keeps them matching.
kubectl apply -f deployment.yaml # Deployment -> ReplicaSet -> PodsKubernetes runs containers across a group of machines. You do not start a container yourself. You write down what you want, and Kubernetes keeps reality matching what you wrote. That compare-and-correct loop is called the control loop, and it never stops.
Pods
A pod is the smallest thing Kubernetes runs. It is one or more containers that share one IP address. A .NET API is normally one container in one pod. Pods are disposable. Every replacement pod gets a new name and a new IP, so nothing may depend on either.
Deployments
A Deployment is the object you actually write. It names the image, says how many copies (replicas) to keep, and describes the pod to create. Kubernetes turns it into a ReplicaSet, and the ReplicaSet creates the pods. Delete a pod and a new one appears, because the count no longer matches. kubectl create deployment orders --image=registry.example.com/orders:v1 is shorthand for a simple Deployment; kubectl get deployment orders -o yaml prints the long form it wrote.
Services
Because pod IPs change, callers need a fixed address. A Service is that address. It is a name in cluster DNS, orders inside the same namespace or orders.default.svc.cluster.local from anywhere, and it spreads connections over every pod whose labels match its selector.
What trips people up
A Service finds pods by labels, never by name. containerPort only documents a port; it opens nothing. The .NET container images listen on 8080 from .NET 8 onwards, where it was 80 before, so targetPort is 8080.
Example
// The whole API that the Deployment and Service below run.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// One resource, so there is something to curl through the Service.
app.MapGet("/orders/1", () => new { id = 1, total = 42 });
// A probe will call this in the next lesson. For now it proves
// the process is listening.
app.MapGet("/healthz", () => Results.Ok("Healthy"));
// The .NET container images set the listening port to 8080, so the
// Service forwards to 8080 without any extra configuration here.
app.Run();When to use it
- Put an existing containerised .NET API on a cluster for the first time.
- Give another team a stable in-cluster address for your service instead of a pod IP.
- Work out why requests to a Service time out when the pods are clearly running.
More examples
The Deployment
The three replicas are identical and interchangeable. Reach for this file whenever you add a .NET service to a cluster: it is the smallest Deployment that is still correct. Note that the selector is fixed once the Deployment exists; changing it later is rejected, so pick the label now.
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders
spec:
replicas: 3
selector:
matchLabels:
app: orders # must match the pod labels below
template:
metadata:
labels:
app: orders # the label the Service will look for
spec:
containers:
- name: api
image: registry.example.com/orders:v1
ports:
- containerPort: 8080 # documentation only
env:
- name: ASPNETCORE_ENVIRONMENT
value: ProductionThe Service in front of it
Another service in the cluster now calls http://orders/orders/1 and does not care which pod answers. ClusterIP is right for an API that sits behind an ingress; a Service that must be reached from outside the cluster is covered in the Ingress lesson.
apiVersion: v1
kind: Service
metadata:
name: orders
spec:
type: ClusterIP # the default: reachable inside the cluster only
selector:
app: orders # every pod with this label gets traffic
ports:
- name: http
port: 80 # the port the Service answers on
targetPort: 8080 # the port in the containerWatching the control loop work
The endpointslices check is the first thing to run when requests to a Service fail: an empty list means the selector matches no pods. Deleting a pod by hand is the cheapest way to see that the Deployment, not you, owns how many pods exist.
kubectl apply -f deployment.yaml -f service.yaml
kubectl get pods -l app=orders
# NAME READY STATUS RESTARTS AGE
# orders-6f8c9d7b54-2j4kq 1/1 Running 0 21s
# Does the Service actually point at anything?
kubectl get endpointslices -l kubernetes.io/service-name=orders
# Delete a pod and the ReplicaSet replaces it.
kubectl delete pod orders-6f8c9d7b54-2j4kq
kubectl get pods -l app=orders
Discussion