Post
Kubernetes: rolling updates, config, probes and autoscaling
Update hello-app without downtime and roll back, pass settings with ConfigMaps and Secrets, add readiness and liveness probes, set requests and limits, and scale with an HPA.
This part builds on the hello Deployment and Service in k8s/ from Deployments, Services and getting traffic in. Each change below is an edit to k8s/deployment.yaml followed by kubectl apply.
Rolling updates and rollbacks
Change the code, build hello-app:1.1, load it into kind, and change the image in deployment.yaml:
docker build -t hello-app:1.1 .
kind load docker-image hello-app:1.1
# edit k8s/deployment.yaml: image: hello-app:1.1
kubectl apply -f k8s/deployment.yaml
kubectl rollout status deployment/hello
Kubernetes doesn’t stop everything and restart. It does a rolling update: start a new Pod, wait until it’s ready, remove an old one, repeat. By default up to 25% extra Pods may be created (maxSurge) and 25% may be unavailable (maxUnavailable); with 3 replicas that means one new Pod at a time and none missing.
A rollout starts only when the Pod template (.spec.template) changes, such as the image or an environment variable. Scaling doesn’t start one.
If the new version is bad, go back:
kubectl rollout history deployment/hello
kubectl rollout undo deployment/hello
kubectl rollout undo deployment/hello --to-revision=1
By default a Deployment keeps 10 old ReplicaSets around for this (revisionHistoryLimit). After an undo, fix the file too, or your next apply brings the bad version back.
Configuration: ConfigMaps and Secrets
Keep settings out of the image, so the same image runs in every environment. A ConfigMap holds plain settings, and a Secret holds sensitive ones:
kubectl create configmap hello-config --from-literal=LOG_LEVEL=info
kubectl create secret generic hello-secret --from-literal=DB_PASSWORD=dev-only
Pass them to the container as environment variables, in the Pod template:
containers:
- name: hello
image: hello-app:1.1
envFrom:
- configMapRef:
name: hello-config
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: hello-secret
key: DB_PASSWORD
envFrom loads every key of the ConfigMap. secretKeyRef picks one key from the Secret. (Our tiny server reads only PORT; these show the wiring.) ConfigMaps and Secrets can also be mounted as files.
Don’t let the name fool you. The Secrets docs warn that Secrets are stored unencrypted in etcd by default, and values in YAML are only base64-encoded, which anyone can decode. Turn on encryption at rest, limit who can read Secrets with RBAC, and consider an external secret store. Never commit a Secret manifest with real values to Git.
Health checks: probes
Kubernetes knows whether your container’s process is running, not whether your app works. Probes tell it. The probes docs define three:
- Readiness: is the container ready for traffic? If it fails, the Pod is removed from the Service’s endpoints, but not restarted.
- Liveness: is the container still healthy? If it fails enough times, the kubelet restarts the container.
- Startup: has a slow app finished starting? Liveness and readiness checks wait until it succeeds.
Add them to the container:
readinessProbe:
httpGet:
path: /
port: 3000
periodSeconds: 5
livenessProbe:
httpGet:
path: /
port: 3000
periodSeconds: 10
failureThreshold: 3
Readiness makes rolling updates safe: a new Pod gets traffic only after it answers. In a real app, point probes at a light endpoint such as /healthz, and keep liveness simple: if it depends on the database and the database blips, Kubernetes restarts every Pod at once.
Resources: requests and limits
Tell Kubernetes how much CPU and memory each container needs:
resources:
requests:
cpu: 100m
memory: 64Mi
limits:
memory: 128Mi
- Requests are what the scheduler reserves. A Pod is placed only on a node with that much free.
100mis 100 millicores, a tenth of a CPU. - Limits are the ceiling. A container that goes over its memory limit is killed (
OOMKilled) and restarted. Going over a CPU limit just slows it down.
Without requests the scheduler is guessing and one hungry Pod can starve its neighbors, so set them everywhere.
Scaling automatically
A HorizontalPodAutoscaler (HPA) changes replicas for you based on load:
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: hello
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: hello
minReplicas: 2
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
The target is 70% of the requested CPU on average, so CPU requests must be set. The HPA also needs the Metrics Server, which kind doesn’t include and most managed clouds do (HPA walkthrough). When an HPA manages a Deployment, remove replicas from the Deployment’s file, or every apply resets it.