Deploy a real application
Take an application from a container image to something reachable in a browser — Deployment, Service, Ingress, configuration, probes and a rolling update.
We will deploy an application to the kind cluster from chapter two, expose it, configure it, and then perform a rolling update and a rollback.
Install an ingress controller
Ingress objects are inert without a controller. For kind:
kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/main/deploy/static/provider/kind/deploy.yaml
kubectl wait --namespace ingress-nginx \
--for=condition=ready pod \
--selector=app.kubernetes.io/component=controller \
--timeout=120s
A namespace
kubectl create namespace demo
kubens demo # or: kubectl config set-context --current --namespace=demo
Configuration first
config.yaml:
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
GREETING: "Hello from Kubernetes"
---
apiVersion: v1
kind: Secret
metadata:
name: app-secrets
type: Opaque
stringData:
API_TOKEN: "dev-token-not-a-real-secret"
kubectl apply -f config.yaml
The Deployment
deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
labels:
app: web
spec:
replicas: 3
selector:
matchLabels:
app: web
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 1
maxUnavailable: 0
template:
metadata:
labels:
app: web
spec:
containers:
- name: web
image: nginxdemos/hello:0.4
ports:
- containerPort: 80
name: http
envFrom:
- configMapRef:
name: app-config
- secretRef:
name: app-secrets
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
memory: 128Mi
readinessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 2
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: http
initialDelaySeconds: 10
periodSeconds: 20
Four parts deserve attention.
maxUnavailable: 0 — during a rolling update, never drop below the desired replica count.
Combined with maxSurge: 1, Kubernetes adds a new pod before removing an old one. This is what
makes a deploy invisible to users.
Resource requests and limits. The request is what the scheduler reserves when placing the pod. The limit is the hard ceiling. Without a request, the scheduler assumes zero and will happily overcommit a node.
readinessProbe — “can this pod receive traffic?” Failing it removes the pod from the
Service’s endpoints without restarting it.
livenessProbe — “is this pod healthy?” Failing it restarts the container. Point it at
something cheap; a liveness probe that hits your database can turn a slow database into a
restart loop across every pod at once.
kubectl apply -f deployment.yaml
kubectl get pods -w
Watch them go Pending → ContainerCreating → Running.
Expose it
service.yaml:
apiVersion: v1
kind: Service
metadata:
name: web
spec:
selector:
app: web
ports:
- port: 80
targetPort: http
name: http
kubectl apply -f service.yaml
kubectl get endpoints web
You should see three IP:port pairs. If this is empty, the selector does not match your pod labels — that is the check from chapter three.
Test from inside the cluster:
kubectl run tmp --rm -it --image=curlimages/curl --restart=Never -- curl -s http://web
Route from outside
ingress.yaml:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web
spec:
ingressClassName: nginx
rules:
- host: demo.localhost
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web
port:
name: http
kubectl apply -f ingress.yaml
curl -H "Host: demo.localhost" http://localhost
The extraPortMappings from chapter two is what makes localhost work here.
Roll out a change
kubectl set image deployment/web web=nginxdemos/hello:0.3
kubectl rollout status deployment/web
Watch it in another terminal:
kubectl get pods -w
New pods appear and become ready before old ones terminate — maxUnavailable: 0 in action.
Roll it back
kubectl rollout history deployment/web
kubectl rollout undo deployment/web
kubectl rollout status deployment/web
Scale
kubectl scale deployment/web --replicas=5
kubectl get pods
Or let the cluster decide:
kubectl autoscale deployment/web --min=2 --max=10 --cpu-percent=70
kubectl get hpa
The HorizontalPodAutoscaler needs metrics-server installed, and it needs the CPU requests
you set earlier — the target percentage is a percentage of the request.
Clean up
kubectl delete namespace demo
Deleting the namespace removes everything in it.
Next: the commands you will actually use.