Skip to content
CKAD: Certified Kubernetes Application Developer

Application Deployment

Last verified against its sources on 23 September 2026

Application Deployment covers the second 20% of the CKAD exam: rolling a Deployment out and back safely, implementing release strategies like blue/green and canary with nothing but labels and Services, and using Helm and Kustomize to package and adapt manifests for real environments.

The exam rewards knowing which tool fits which job: a plain rolling update for routine changes, a second Deployment and a selector flip for an instant cutover, a small second Deployment for a gradual one, Helm when the app is a versioned package with its own release history, and Kustomize when the same base manifests need small, environment- specific differences without duplicating files.

Deployments and Rolling Updates

  • Configure a Deployment's rolling update strategy with maxSurge and maxUnavailable to control rollout speed and safety.
  • Monitor, pause, resume and roll back a Deployment rollout with kubectl.

A Deployment's default update strategy is RollingUpdate: old Pods are replaced by new ones gradually rather than all at once. Two fields tune how that gradual replacement behaves. maxSurge caps how many Pods above the desired replica count may exist at once — extra capacity created ahead of removing old Pods. maxUnavailable caps how many Pods below the desired count are tolerated at once — old Pods allowed to be removed before their replacements are ready. Both default to 25% of the replica count, rounded per Kubernetes' own rules, so a rollout of 10 replicas may briefly run anywhere from 9 to 12 Pods depending on where it is in the process.

Setting maxUnavailable: 0 guarantees full capacity throughout the rollout at the cost of needing surge capacity to schedule the extra Pods; setting maxSurge: 0 guarantees the replica count is never exceeded at the cost of tolerating some unavailability while old Pods are removed first.

bash

apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
spec:
  replicas: 10
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 2
      maxUnavailable: 0
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
    spec:
      containers:
        - name: web
          image: web:2.3
A Deployment tuned for rollout safety over speed.

kubectl rollout status deployment/web follows a rollout live and reports when it finishes or gets stuck. kubectl rollout history deployment/web lists past revisions, each backed by its own ReplicaSet — Kubernetes keeps old ReplicaSets around (scaled to zero) up to revisionHistoryLimit, 10 by default, so kubectl rollout undo has something to revert to. kubectl rollout pause and kubectl rollout resume let you stop a rollout mid-way to verify the new Pods before letting it continue.

Older tutorials mention --record on kubectl set image or kubectl apply to label why a revision was created; that flag has been removed from kubectl's current command reference. The supported way to label a revision is the kubernetes.io/change- cause annotation on the Deployment — set it directly in the manifest, or with kubectl annotate — which kubectl rollout history reads into its CHANGE-CAUSE column.

A rollout's new Pods can never pull their image because of a typo'd tag. Does kubectl rollout status finish, fail fast, or hang?Answer it yourself first, then open this.

It hangs — the new Pods never become Ready, so the rollout stalls part-way rather than completing or rolling back on its own.

Blue/Green and Canary Deployment Strategies

  • Implement a blue/green release by running two Deployments and switching a Service's selector between them.
  • Implement a canary release by running a small second Deployment behind the same Service selector.

Kubernetes has no API object literally named "blue-green" or "canary" — the CKAD curriculum calls for implementing these with plain primitives: labels, a Service's selector, and ordinary Deployments. A Service routes traffic to whichever Pods currently match its spec.selector; it has no idea which Deployment those Pods came from.

A blue/green release runs two full Deployments at once — "blue" (live) and "green" (new) — each with its own distinct label such as version: blue or version: green. The Service selector initially matches blue. Once green is verified, the cutover is a single edit to the Service's selector so it matches green's Pods instead — new connections land on green immediately, and rolling back is the same edit in reverse, instant because blue's Pods never stopped running.

bash

# Before: Service routes to the "blue" Deployment's Pods
spec:
  selector:
    app: web
    version: blue

# After: same Service, now routing to "green" instead
spec:
  selector:
    app: web
    version: green
The cutover is a one-line change to the Service's selector.

A canary release instead runs a small second Deployment that shares the same labels the Service already selects on — typically just app: web, without a version label in the selector at all — so the Service load-balances across both the stable Deployment's Pods and the canary's. If stable runs 9 replicas and canary runs 1, roughly one in ten connections reaches the canary, because a Service spreads connections across all matching Pods without favoring one Deployment over another.

That "roughly" matters: a plain Kubernetes Service has no concept of a configured traffic percentage — the split just falls out of the replica ratio, connection by connection, with no guarantee any single request lands one way or the other. Precise, percentage-exact traffic splitting needs a service mesh or an Ingress controller with weighted-routing support; it isn't a feature of the core Service object itself.

A canary Deployment shares the stable Service's selector.
A canary Deployment has 1 replica while stable has 9, both matched by the same Service selector. Roughly what share of requests hit the canary?Answer it yourself first, then open this.

Roughly 10% — the split follows the replica ratio, not an exact configured percentage, since a Service has no built-in weighted routing.

Deploying Applications with Helm

  • Install and upgrade an application from a Helm chart, overriding its default values as needed.
  • Roll back a Helm release to a previous revision using its release history.

Helm is the package manager for Kubernetes. A chart is a versioned bundle of templated manifests — Chart.yaml for metadata, values.yaml for the defaults its templates fill in, and a templates/ directory of the manifests themselves. A release is one named, installed instance of a chart in a namespace; the same chart can back several independent releases at once, each with its own values.

Helm 3 — the current major version — removed Tiller, the server-side component Helm 2 required inside the cluster. The helm CLI now talks to the Kubernetes API directly using the same credentials kubectl uses, so there is nothing extra to install or secure beyond the cluster itself. helm install <release> <chart> creates a release; helm upgrade <release> <chart> -f values.yaml updates one that already exists; helm upgrade --install does either, which makes it the natural choice for an idempotent pipeline step that shouldn't care whether the release exists yet.

bash

helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm install my-nginx bitnami/nginx \
  --namespace web --create-namespace \
  --set replicaCount=3
Add a repository, then install with overridden values.

Helm keeps a release's history the way a Deployment keeps ReplicaSet revisions. helm history <release> lists past revisions; helm rollback <release> <revision> restores the release's Kubernetes resources to that revision's state. Passing --atomic to helm install or helm upgrade makes Helm roll the release back automatically if the operation fails or times out, instead of leaving it half-applied. helm uninstall <release> removes every resource the release created; add --keep-history if you want the revision record to survive an uninstall.

A value set with --set on the command line overrides the same key in values.yaml; a -f other-values.yaml file overrides it too, and later flags win over earlier ones when both are given. Nothing about this requires editing the chart's own templates.

A helm upgrade with --atomic fails partway through. What happens?Answer it yourself first, then open this.

Helm automatically rolls the release back to its last successful revision instead of leaving it half-upgraded.

Customizing Manifests with Kustomize

  • Structure a base and environment-specific overlays with Kustomize, applying them with kubectl apply -k.
  • Patch a base manifest for one environment using the unified patches field.

Kustomize takes the opposite approach from Helm: no templating language at all. You write plain, valid YAML manifests once as a base, then layer overlays on top that patch or add to them for each environment. kubectl has supported kustomization.yaml files natively since v1.14 — kubectl apply -k <dir> applies what a directory's kustomization produces, and kubectl kustomize <dir> just prints it without applying anything, useful for reviewing the result first.

A kustomization.yaml (apiVersion: kustomize.config.k8s.io/ v1beta1, kind: Kustomization) lists its resources: — plain manifest files, or another kustomization directory used as a base — and can set cross-cutting fields such as namespace, namePrefix, nameSuffix, labels and commonAnnotations that get applied to everything it references, without editing those referenced files at all.

bash

# base/kustomization.yaml
resources:
  - deployment.yaml
  - service.yaml

# overlays/production/kustomization.yaml
resources:
  - ../../base
namespace: production
patches:
  - path: increase-replicas.yaml
    target:
      kind: Deployment
      name: web
An overlay references a base and adds environment-specific fields.

Patching a specific resource is done through the unified patches: field, which accepts either a strategic-merge patch or a JSON 6902 patch and auto-detects which one it's looking at. The older patchesStrategicMerge and patchesJson6902 fields still work but are deprecated in favor of patches; kustomize edit fix can migrate an old kustomization.yaml to the current field automatically.

configMapGenerator and secretGenerator build a ConfigMap or Secret from files or literal key-value pairs listed right in the kustomization file. By default, the generated object's name gets a content-hash suffix appended — so when the data changes, the generated name changes too, which forces anything referencing it by name (like a Deployment's volume) to roll. generatorOptions.disableNameSuffixHash: true turns that behavior off, at the cost of losing the automatic rollout.

An old kustomization.yaml uses patchesStrategicMerge. Does it still work, and what's the current recommendation?Answer it yourself first, then open this.

It still works but is deprecated; use the unified patches field instead — kustomize edit fix can migrate the file automatically.

Sources