Kubernetes Deployments, Rollouts, ConfigMaps, and Secrets for CKA
Introduction
Kubernetes Deployments, ConfigMaps and Secrets are central to the Workloads & Scheduling domain (15% of the CKA). This lesson teaches rolling updates, rollback, application configuration and self-healing workloads. Practise creating a three-replica Deployment, replacing its image, recovering a bad release and configuring health probes. These are original practice scenarios aligned with the published competencies; the exam uses hands-on tasks rather than this course's multiple-choice quiz.
The official exam page currently lists Kubernetes v1.37. Confirm the version for your sitting and use documentation matching your lab. Examples below illustrate controller behaviour; private application images and files must be supplied by the exercise. The nginx tags are fixed lab examples, not production version recommendations.
After this lesson you will be able to create and update Deployments, control rollout strategy, undo a bad release, inject configuration with ConfigMaps and Secrets through environment variables and mounted files, add probes and restart policies, and choose between Deployments, DaemonSets, StatefulSets and Jobs.
Learning objectives
- Describe how a Deployment, its ReplicaSets and Pods relate, and read rollout status and history.
- Tune a rolling update with
maxSurgeandmaxUnavailable, pause, resume and roll back. - Create ConfigMaps and Secrets from literals, files and directories, and consume them as environment variables,
envFrom, volume mounts andsubPathfiles. - Configure liveness, readiness and startup probes and understand
restartPolicy. - Pick the right controller: Deployment, StatefulSet, DaemonSet, Job or CronJob.
Kubernetes Deployments and ReplicaSets
A Deployment declares the desired state of a stateless application. It manages ReplicaSets, and each ReplicaSet manages identical Pods. When you change the Pod template (image, env, resources, and so on), the Deployment creates a new ReplicaSet and gradually scales it up while scaling the old one down. Changing only replicas does not create a new ReplicaSet.
kubectl create deployment web --image=nginx:1.27 --replicas=3 -n demo
kubectl get deploy,rs,pods -n demo -l app=web
kubectl scale deployment web --replicas=5 -n demo
kubectl set image deployment/web nginx=nginx:1.27.2 -n demo # container name is nginx
kubectl expose deployment web --port=80 --target-port=80 -n demoGenerate a manifest to edit rather than writing one from scratch:
kubectl create deployment web -n demo --image=nginx:1.27 --replicas=3 --dry-run=client -o yaml > web.yamlThe skeleton you should be able to write from memory:
apiVersion: apps/v1
kind: Deployment
metadata:
name: web
namespace: demo
spec:
replicas: 3
selector:
matchLabels:
app: web # must match the template labels; immutable after creation
strategy:
type: RollingUpdate
rollingUpdate:
maxSurge: 25%
maxUnavailable: 25%
template:
metadata:
labels:
app: web
spec:
containers:
- name: nginx
image: nginx:1.27
ports:
- containerPort: 80Facts that are tested:
spec.selectormust matchspec.template.metadata.labels, and it cannot be changed after creation.- The Deployment's Pods are named
<deployment>-<replicaset-hash>-<random>. - Deleting a Pod owned by a ReplicaSet makes the ReplicaSet create another; deleting the Deployment deletes its ReplicaSets and Pods (use
--cascade=orphanto keep them). kubectl scaleon a Deployment is declarative state in the cluster; if you manage the YAML in Git, update the file as well.
Rolling updates and rollbacks
The default strategy is RollingUpdate. Two knobs control the pace, either as absolute numbers or percentages:
| Field | Meaning | Default |
|---|---|---|
maxSurge |
Extra non-terminating replicas allowed during the update | 25% (rounded up) |
maxUnavailable |
How many Pods below replicas may be unavailable |
25% (rounded down) |
With 4 replicas and defaults, the controller allows one surge replica and budgets for at least 3 available replicas. Pods still terminating can temporarily push the total above 5. Setting maxUnavailable: 0 and maxSurge: 1 avoids intentionally reducing available replicas during the rollout, provided replacement Pods can start. Useful readiness checks, spare capacity and graceful connection draining are also needed for uninterrupted service; this strategy does not protect against independent failures. Recreate terminates old replicas before creating replacements during an upgrade. It introduces downtime and should not be treated as a general concurrency or data-locking guarantee.
A Pod counts as available only after it has been Ready for minReadySeconds. This is why readiness probes matter: without a meaningful readiness check, a running but broken application may count as ready and allow the rollout to continue.
Managing a rollout
kubectl rollout status deployment/web -n demo # blocks until finished or failed
kubectl rollout history deployment/web -n demo
kubectl rollout history deployment/web --revision=2 -n demo # see the template of a revision
kubectl rollout undo deployment/web -n demo # back to the previous revision
kubectl rollout undo deployment/web --to-revision=1 -n demo
kubectl rollout pause deployment/web -n demo # batch several changes
kubectl set resources deployment/web -c nginx --limits=cpu=500m,memory=256Mi -n demo
kubectl rollout resume deployment/web -n demo
kubectl rollout restart deployment/web -n demo # restart annotation changes the Pod templateNotes:
- The history is the set of old ReplicaSets, limited by
revisionHistoryLimit(default 10). Setting it to 0 removes the ability to roll back. - The
CHANGE-CAUSEcolumn is populated from the annotationkubernetes.io/change-cause. Use an explicit annotation rather than the obsolete--recordflag:kubectl annotate deployment/web -n demo kubernetes.io/change-cause="upgrade to 1.27.2" --overwrite. - A rollout that cannot progress (for instance
ImagePullBackOff) reportsProgressDeadlineExceededafterprogressDeadlineSeconds(default 600). This status neither pauses the controller nor automatically rolls back: reconciliation continues, and fixing the cause can allow progress. Inspect the remaining healthy replicas and decide whether to fix or undo the release. rollout undorestores a retained Pod template; it does not restore a replica count, changed ConfigMap/Secret contents, a database schema or application data. Use versioned configuration when a release must be reproducible.
Worked example: the update that stalls
Scenario. Fabrikam's shop Deployment has 4 replicas (maxSurge 25%, maxUnavailable 25%). You run kubectl set image deployment/shop app=fabrikam/shop:v2 and the status never completes. A snapshot of kubectl get pods shows 3 old Pods Running and new Pods in ImagePullBackOff (the tag has a typo, v2 should be 2.0).
Reasoning.
kubectl rollout status deployment/shopreports that updated replicas are still unavailable.kubectl describe pod <new>showsFailed to pull image ... not found.- Up to one old replica can be removed under the unavailable budget; the remaining three healthy replicas may still serve traffic. Capacity is reduced, so check latency and errors instead of assuming customers are unaffected. The number of failed new Pods can change as the controller reconciles.
- Fix choices:
kubectl set image deployment/shop app=fabrikam/shop:2.0to move forward, orkubectl rollout undo deployment/shopto return to the previous template. Both start a new rollout. - Verify with
kubectl rollout statusandkubectl get rs(the broken ReplicaSet scales to 0).
Why not the alternatives? Deleting the failing Pod only makes the ReplicaSet recreate it with the same bad image. Scaling to 0 and back causes an outage. Editing the ReplicaSet directly is overwritten by the Deployment controller.
ConfigMaps
A ConfigMap stores non-confidential configuration as key-value pairs (up to 1 MiB). Values may be short strings or whole files.
kubectl create configmap app-config -n demo \
--from-literal=LOG_LEVEL=info --from-literal=FEATURE_X=true
kubectl create configmap nginx-conf -n demo --from-file=nginx.conf # key = file name
kubectl create configmap settings -n demo --from-file=app.properties=./dev.properties # custom key
kubectl create configmap envfile -n demo --from-env-file=.envThere are four ways to consume it:
spec:
containers:
- name: app
image: demo/app:1.0
env:
- name: LOG_LEVEL # 1. one key as one variable
valueFrom:
configMapKeyRef:
name: app-config
key: LOG_LEVEL
envFrom: # 2. all keys as variables
- configMapRef:
name: app-config
volumeMounts:
- name: conf # 3. keys as files in a directory
mountPath: /etc/app
- name: nginx-single # 4. one file placed without hiding the directory
mountPath: /etc/nginx/nginx.conf
subPath: nginx.conf
volumes:
- name: conf
configMap:
name: app-config
- name: nginx-single
configMap:
name: nginx-confBehaviour you must remember:
| Consumption | Updates after the ConfigMap changes? |
|---|---|
Environment variables (env, envFrom) |
No; Pods must be restarted (for example kubectl rollout restart) |
| Volume mount | Eventually: kubelet sync plus cache/watch propagation delay; the app must re-read the files |
Volume mount with subPath |
No |
Immutable ConfigMap (immutable: true) |
Never; you create a new one and change the reference |
A missing ConfigMap referenced by env or a volume leaves the Pod in CreateContainerConfigError (or ContainerCreating for volumes) unless the reference is marked optional: true. Mounting a volume at a directory hides whatever the image had there, which is why subPath is used for a single file.
Secrets
A Secret has the same shape as a ConfigMap but is intended for sensitive data: passwords, tokens, TLS keys. Values are base64-encoded in the API, which is encoding, not encryption. Real protection comes from Kubernetes RBAC and Secret permissions, configured encryption at rest for etcd, and avoiding exposure in logs and environment dumps. A principal allowed to create workloads can often mount namespace Secrets even without direct Secret read permission.
kubectl create secret generic db-creds -n demo \
--from-literal=username=app --from-literal=password='S3cr3t!'
kubectl create secret tls web-tls --cert=tls.crt --key=tls.key -n demo
kubectl create secret docker-registry regcred --docker-server=registry.example.com \
--docker-username=bot --docker-password=pw -n demo
kubectl get secret db-creds -n demo -o jsonpath='{.data.password}' | base64 -dThe literal passwords above and below are dummy lab values. Real values on command lines can leak through shell history or process inspection; use protected files or an approved secret-management workflow.
Secret types you should recognise: Opaque (generic), kubernetes.io/tls, kubernetes.io/dockerconfigjson (image pull), kubernetes.io/basic-auth, kubernetes.io/ssh-auth, kubernetes.io/service-account-token.
Consumption mirrors ConfigMaps (secretKeyRef, secretRef in envFrom, secret: volumes). Use defaultMode to set file permissions, and imagePullSecrets for private registries:
spec:
imagePullSecrets:
- name: regcred
containers:
- name: app
image: registry.example.com/app:1.0
env:
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: db-creds
key: password
volumeMounts:
- name: creds
mountPath: /etc/creds
readOnly: true
volumes:
- name: creds
secret:
secretName: db-creds
defaultMode: 0400Choose the consumption method your application supports. Files permit eventual updates when mounted without subPath, but the application must reload them. Environment values can appear in process dumps or debugging output and do not refresh in running containers. kubectl describe pod normally displays a Secret reference rather than decoding its value; it is incorrect to assume that it directly prints Secret-backed passwords. In YAML, data expects base64 values while stringData accepts plain text and is converted on write.
Worked example: refresh a Secret-backed environment variable
Scenario. Northwind's orders Deployment reads DB_PASSWORD from Secret db-creds via an environment variable. The DBA changes the password to N3wPass. You update the Secret, but the application keeps failing to connect.
Reasoning.
- Replace the Secret value:
kubectl create secret generic db-creds -n demo --from-literal=password=N3wPass --dry-run=client -o yaml | kubectl apply -f -. - Environment variables are fixed at container start, so running Pods still hold the old password.
- Trigger new Pods with
kubectl rollout restart deployment/orders -n demo; the rolling strategy replaces them gradually. - Verify completion with
kubectl rollout status deployment/orders -n demo, then inspectkubectl logs deploy/orders -n demowithout logging credentials and check the application's database health.
Why not the alternatives? Deleting all Pods simultaneously risks an outage. Waiting for propagation does not refresh environment variables; mounted-file updates also require application reload support. The Pod environment cannot be edited in place. This scenario starts after the old database password has been invalidated, so availability may already be lost. For planned rotation, coordinate overlapping valid credentials, rollout and old-credential revocation with the database team.
Self-healing: probes and restart policy
Kubernetes restarts failed containers and replaces failed Pods, but it needs you to say what "healthy" means.
| Probe | Question | On failure |
|---|---|---|
| Liveness | Is the process alive? | kubelet terminates it after the failure threshold; restart follows restart policy |
| Readiness | Can it accept traffic now? | Endpoint becomes unready; normal Service routing avoids it (no restart) |
| Startup | Has the slow application finished starting? | Holds off liveness/readiness; repeated failure terminates it, subject to restart policy |
Each probe is one of httpGet, tcpSocket, exec or grpc, with tuning fields initialDelaySeconds, periodSeconds, timeoutSeconds, successThreshold and failureThreshold.
containers:
- name: app
image: demo/app:1.0
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5
failureThreshold: 3
livenessProbe:
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 10
periodSeconds: 10
startupProbe:
httpGet: { path: /healthz, port: 8080 }
failureThreshold: 30 # up to 30 x periodSeconds to start
periodSeconds: 5Guidelines: do not make liveness depend on downstream services (a database outage would restart every Pod in a loop), and use readiness for dependency checks.
restartPolicy is set at Pod level: Always (default; required for Deployments), OnFailure and Never (used by Jobs). After repeated crashes, the kubelet applies exponential back-off (10 s, 20 s, 40 s ... capped at 5 minutes), which you see as CrashLoopBackOff. These are default timings, not an invariant: feature gates and kubelet configuration can change restart delays. Container-level restart rules can override the Pod policy when supported; the examples here use ordinary application containers without overrides.
Comparing the workload controllers
| Controller | Use for | Identity and storage | Scheduling |
|---|---|---|---|
| Deployment | Stateless apps | Interchangeable Pods | Any suitable node |
| StatefulSet | Databases, clustered apps | Stable names (db-0, db-1), stable DNS through a headless Service, one PVC per Pod via volumeClaimTemplates, ordered start and stop |
Any suitable node |
| DaemonSet | One Pod per node (log agents, CNI, node exporters) | Per node | Every matching node, including new ones; tolerations needed for tainted nodes |
| Job | Run-to-completion work (completions, parallelism, backoffLimit) |
Pods end when done | Any |
| CronJob | Scheduled Jobs (schedule, concurrencyPolicy) |
Creates Jobs | Any |
Useful commands:
kubectl create job pi --image=perl:5.40 -- perl -Mbignum=bpi -wle 'print bpi(2000)'
kubectl create cronjob nightly --image=busybox --schedule="0 2 * * *" -- /bin/sh -c "date"
kubectl get statefulset,ds -A
kubectl rollout status statefulset/dbDaemonSets have no kubectl create daemonset; generate a Deployment manifest with --dry-run=client -o yaml, change kind to DaemonSet, and delete the replicas and strategy fields. StatefulSets update Pods in reverse ordinal order by default and honour partition for canary updates.
Worked example: a node agent that must reach every node
Scenario. Contoso needs a log collector on every node, including control plane nodes that carry the taint node-role.kubernetes.io/control-plane:NoSchedule.
Reasoning.
- A DaemonSet places one Pod per node automatically and covers new nodes as they join.
- Control plane nodes are tainted, so the Pod template needs a toleration; without it those nodes are skipped.
- The agent only needs read access to host logs, so mount
/var/logread-only through ahostPathvolume.
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: log-agent
namespace: kube-system
spec:
selector:
matchLabels: { app: log-agent }
template:
metadata:
labels: { app: log-agent }
spec:
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
containers:
- name: agent
image: registry.example.com/log-agent:1.0
volumeMounts:
- { name: varlog, mountPath: /var/log, readOnly: true }
volumes:
- name: varlog
hostPath: { path: /var/log }This manifest demonstrates placement and read-only mounting; supply a compatible collector image and its tailing/output configuration before expecting logs to be collected. Admission policy must permit the hostPath volume.
Verify with kubectl get ds log-agent -n kube-system (DESIRED matches eligible nodes, and READY confirms successful starts) and kubectl get pods -n kube-system -o wide -l app=log-agent.
Why not the alternatives? A Deployment with replicas equal to the node count does not guarantee one per node. A Deployment with pod anti-affinity is fragile and does not follow new nodes. A StatefulSet adds identity guarantees that are not needed.
Common pitfalls and exam tips
- Wrong container name in
set image. The syntax isdeployment/<name> <container-name>=<image>; check withkubectl get deploy web -o jsonpath='{.spec.template.spec.containers[*].name}'. - Labels and selectors. A selector that does not match the template labels is rejected; changing a selector on an existing Deployment is not allowed.
- Env vars do not auto-refresh. After changing a ConfigMap or Secret used via env, run
kubectl rollout restart. Plan capacity and Pod scheduling and HPA together so new replicas can start. subPathmounts never refresh and mounting over a directory hides its content.- Base64 is not security. Use
base64 -dto decode, and never paste decoded secrets into logs or answers. - Liveness probes pointing at dependencies cause cascading restarts; use readiness.
- A missing readiness probe may let a running but broken application count as ready. Services and EndpointSlice readiness explain the effect on traffic.
- Namespace and context. Always
kubectl config use-contextas instructed and add-n. - Exam tip: speed matters. Use
kubectl create ... --dry-run=client -o yaml,kubectl explain deployment.spec.strategy --recursive, andkubectl rollout statusto avoid guessing. Setalias k=kubectlandexport do="--dry-run=client -o yaml"only if you are already comfortable with them; the environment allows them but the time to type is the point.
Mini case study: the faulty release at Adventure Works
Adventure Works runs the catalog Deployment (4 replicas, image catalog:1.8, namespace store). Configuration comes from ConfigMap catalog-config (env) and Secret catalog-db. Tasks:
- Plan an update to
catalog:1.9that retains four available replicas during normal rollout. Assume sufficient capacity and no independent failures. - After the release, Pods crash-loop because
CATALOG_CACHE_SIZEis missing in the ConfigMap. Restore service quickly, then fix properly. - Add a readiness probe on
/readyport 8080 so a bad build cannot take over silently.
Solution.
- Before releasing 1.9, install the readiness probe shown below and verify the current version's rollout. Set the update strategy with
kubectl patch deployment catalog -n store -p '{"spec":{"strategy":{"rollingUpdate":{"maxSurge":1,"maxUnavailable":0}}}}', then runkubectl set image deployment/catalog -n store catalog=catalog:1.9and watchkubectl rollout status deployment/catalog -n store. WithmaxUnavailable: 0, the controller waits for a replacement to become available before intentionally removing an old replica. This is a rollout budget, not a guarantee against unrelated crashes or application errors. - Fast recovery:
kubectl rollout undo deployment/catalog -n store. Proper fix:kubectl patch configmap catalog-config -n store -p '{"data":{"CATALOG_CACHE_SIZE":"512"}}', then roll out 1.9 again. Because the values are consumed as environment variables, a restart is required for the Pods to see the new key. Check Pod status withkubectl get pods -n store; a missing key referenced throughconfigMapKeyRefshowsCreateContainerConfigError, while the crash loop here comes from the application failing at start. - The readiness probe to configure before the release is below. Add it with
kubectl edit deployment catalog -n storeundercontainers[0]:
readinessProbe:
httpGet: { path: /ready, port: 8080 }
periodSeconds: 5The edit changes the Pod template and starts another rollout; the new Pods only receive traffic once they report ready.
Commentary. A rollback can restore the previous Pod template, but recovery time depends on image pulls, startup and shared configuration. If the previous release also needs the missing key, restore that configuration too. Readiness and maxUnavailable: 0 reduce rollout risk; verify application health and traffic before declaring recovery.
Key terms
- Deployment / ReplicaSet: controller for stateless apps / controller keeping a set of identical Pods.
- Rolling update: gradual replacement of old Pods with new ones.
- maxSurge / maxUnavailable: extra and missing Pods allowed during an update.
- Rollout / revision: a version change and its numbered history entry.
- ConfigMap / Secret: configuration data / sensitive data objects.
- Probe: periodic health check (liveness, readiness, startup).
- DaemonSet / StatefulSet: one Pod per node / Pods with stable identity and storage.
- CrashLoopBackOff: container repeatedly failing, restarts delayed with back-off.
Summary checklist
-
kubectl create deployment,scale,set image,rollout status|history|undo|restart. - Selector matches template labels and is immutable; template changes create a new ReplicaSet.
-
maxSurgeandmaxUnavailablecontrol the pace; readiness probes make rollouts safe. - ConfigMaps and Secrets: create from literal, file and env-file; consume via
env,envFrom, volumes,subPath. - Env vars need a restart; volume mounts update (not
subPath); base64 is not encryption. - Liveness restarts, readiness gates traffic, startup protects slow starts.
- DaemonSet for per-node Pods (tolerations for taints), StatefulSet for stable identity and storage, Job/CronJob for batch.
Official sources
- Deployments: https://kubernetes.io/docs/concepts/workloads/controllers/deployment/ (opens in a new tab)
- Performing a rolling update: https://kubernetes.io/docs/tutorials/kubernetes-basics/update/update-intro/ (opens in a new tab)
- ConfigMaps: https://kubernetes.io/docs/concepts/configuration/configmap/ (opens in a new tab)
- Secrets: https://kubernetes.io/docs/concepts/configuration/secret/ (opens in a new tab)
- Liveness, readiness and startup probes: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/ (opens in a new tab)
- DaemonSet: https://kubernetes.io/docs/concepts/workloads/controllers/daemonset/ (opens in a new tab)
- StatefulSet: https://kubernetes.io/docs/concepts/workloads/controllers/statefulset/ (opens in a new tab)
- Pod lifecycle and restart behaviour: https://kubernetes.io/docs/concepts/workloads/pods/pod-lifecycle/ (opens in a new tab)
- CKA domains and current exam version: https://training.linuxfoundation.org/certification/certified-kubernetes-administrator-cka/ (opens in a new tab)
- CKA curriculum: https://github.com/cncf/curriculum (opens in a new tab)
