Post
Kubernetes: Deployments, Services and getting traffic in
Run hello-app as a Deployment that keeps three copies alive, give it a stable address with a Service, compare Service types, and route outside traffic with Gateway API or Ingress.
This part needs the kind cluster and the manifest basics from Kubernetes from zero to hero, and the hello-app image from Docker from zero to hero: a tiny Node server on port 3000.
Deployments: keep N copies running
Build the image with a real version tag and load it into the kind cluster (kind nodes can’t see your local images otherwise):
docker build -t hello-app:1.0 .
kind load docker-image hello-app:1.0
Why 1.0 and not latest? Kubernetes always tries to pull :latest from a registry, and our image exists only locally. A version tag also tells you exactly what’s running.
Create a folder k8s/ and a file k8s/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: hello
labels:
app: hello
spec:
replicas: 3
selector:
matchLabels:
app: hello
template:
metadata:
labels:
app: hello
spec:
containers:
- name: hello
image: hello-app:1.0
ports:
- containerPort: 3000
Read it as a sentence: keep 3 replicas of Pods labeled app: hello, and here’s the template for making one. The selector must match the labels in the template, otherwise the API rejects it.
kubectl apply -f k8s/deployment.yaml
kubectl get deployments
kubectl get pods -l app=hello
NAME READY UP-TO-DATE AVAILABLE AGE
hello 3/3 3 3 20s
Under the hood the Deployment created a ReplicaSet, and the ReplicaSet created the Pods. You manage the Deployment and leave the rest to it.
Now watch the reconcile loop work. Delete one Pod (use a name from your get pods output):
kubectl delete pod <one-of-the-hello-pods>
kubectl get pods -l app=hello
A new Pod with a new name is already starting.
Scaling is one field: change replicas: 3 to 5 in the file and apply again. For a quick test:
kubectl scale deployment/hello --replicas=5
Prefer the file: if you scale by command and later apply it, you’re back to 3.
Services: a stable address for Pods
Pods are disposable. Each new one gets a new IP, so nothing should talk to a Pod’s IP directly. A Service gives a group of Pods one stable name and IP, and spreads traffic across whichever Pods match its selector right now.
Create k8s/service.yaml:
apiVersion: v1
kind: Service
metadata:
name: hello
spec:
selector:
app: hello
ports:
- port: 80
targetPort: 3000
port is what clients use. targetPort is the container port the traffic goes to. Apply the whole folder, and test from inside the cluster with a throwaway Pod:
kubectl apply -f k8s/
kubectl get services
kubectl run tmp --rm -it --image=busybox:1.37 --restart=Never -- wget -qO- http://hello
Hello from a container
The name hello works because the cluster runs its own DNS. In the same namespace the Service name is enough; from another, use hello.<namespace>.
To reach it from your own browser during development, forward a local port:
kubectl port-forward service/hello 8080:80
# in another terminal:
curl localhost:8080
Service types
Without a type, a Service is ClusterIP. The Service docs list the others:
| Type | What it does |
|---|---|
ClusterIP |
The default. A stable IP and DNS name, inside the cluster only. |
NodePort |
Also opens one port on every node, from the range 30000-32767 by default. |
LoadBalancer |
Also asks the cloud provider for an external load balancer. |
ExternalName |
A DNS alias (CNAME) to a name outside the cluster. No proxying. |
On a cloud cluster, type: LoadBalancer gives one Service a public IP. With many services you don’t want a load balancer each.
Getting traffic in: Gateway and Ingress
To route many hostnames and paths to many Services through one entry point, Kubernetes has two APIs:
- Ingress, the older one. The Ingress page now recommends Gateway instead and says the Ingress API is frozen: it still works, but gets no new features.
- Gateway API, the recommended replacement. A cluster operator sets up a
Gateway(the entry point), and each team attachesHTTPRouteobjects to it.
An HTTPRoute that sends hello.example.com to our Service:
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: hello
spec:
parentRefs:
- name: my-gateway
hostnames:
- hello.example.com
rules:
- backendRefs:
- name: hello
port: 80
Neither API does anything alone: you need a controller that implements it, installed in the cluster or provided by your cloud (implementations).
The popular ingress-nginx controller was retired in March 2026. Existing installs keep running but get no security fixes, so if a tutorial says to install it, pick a maintained controller.