Skip to content
CKAD: Certified Kubernetes Application Developer

Application Environment, Configuration and Security

Last verified against its sources on 23 September 2026

Application Environment, Configuration and Security is the largest single domain on the CKAD exam at 25%: externalizing configuration and secrets correctly, constraining what a workload can consume, understanding how a request to the API server is actually decided, and extending Kubernetes itself with custom resources.

Several of this module's facts are the kind that look obvious until an exam question is built around the exception — a Secret that isn't encrypted, an environment variable that doesn't update, a CustomResourceDefinition that does nothing without a controller.

ConfigMaps and Secrets

  • Externalize configuration into a ConfigMap and sensitive data into a Secret, then consume each correctly in a Pod.
  • Predict whether a running Pod picks up a ConfigMap or Secret change automatically.

A ConfigMap holds non-sensitive configuration; a Secret holds sensitive data such as passwords or tokens, structured the same way. Both are consumed the same two ways: as an environment variable (env[].valueFrom.configMapKeyRef or secretKeyRef) or as a mounted volume, where each key becomes a file. Neither storage mechanism is exclusive to sensitivity level — a Secret is simply the object Kubernetes treats as sensitive by convention and tooling, not one that's cryptographically protected on its own.

That last point matters more than it sounds: Secret values are base64-encoded, not encrypted. Base64 is a reversible encoding, not a cipher — anyone with API read access to a Secret can decode it in one command. Genuine protection comes from RBAC controlling who can read the Secret at all, plus, if needed, separately configuring encryption at rest for etcd.

bash

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: db-credentials
        key: password
volumeMounts:
  - name: db-creds
    mountPath: /etc/db-credentials
    readOnly: true
volumes:
  - name: db-creds
    secret:
      secretName: db-credentials
The same key, consumed two different ways.

Update behavior differs sharply by consumption method. A ConfigMap or Secret consumed as an environment variable is fixed at container start — changing the object later has no effect until the Pod is restarted. One consumed as a volume mount updates eventually: the kubelet checks freshness on its periodic sync, so the total delay is roughly the sync period plus a cache propagation delay. A container using a ConfigMap or Secret as a subPath volume mount never receives updates at all, regardless of how long you wait.

Setting immutable: true on a ConfigMap or Secret — stable since Kubernetes v1.21 — locks its data permanently: the flag itself cannot be reverted, and the data cannot be edited afterward. The only way to change it is to delete and recreate the object, and since existing Pods keep their old mount point, those Pods should be recreated too.

A Secret consumed via an environment variable is updated. Does the running Pod see the new value without a restart?Answer it yourself first, then open this.

No — environment variables are fixed at container start; only volume-mounted (non-subPath) Secrets update in place, and even then only after a delay.

Resource Requests, Limits and Quotas

  • Set container resource requests and limits that produce the intended Quality of Service class.
  • Apply a ResourceQuota and a LimitRange to constrain a namespace's overall resource usage.

A request is what the scheduler uses to place a Pod — it only schedules a Pod onto a node with at least that much spare capacity. A limit is a hard ceiling: CPU usage above the limit gets throttled, while memory usage above the limit can get the container killed by the out-of-memory handler. These two numbers, present or absent per container, decide a Pod's Quality of Service (QoS) class. Guaranteed requires every container to set both a CPU and a memory request equal to its limit. Burstable covers anything that doesn't meet Guaranteed but sets at least one request or limit somewhere. BestEffort is a Pod where no container sets any request or limit at all.

QoS class decides eviction order when a node runs low on resources: BestEffort Pods are evicted first, then Burstable, and Guaranteed Pods last.

bash

resources:
  requests:
    cpu: "250m"
    memory: "256Mi"
  limits:
    cpu: "250m"
    memory: "256Mi"
Equal request and limit on both resources — this container is Guaranteed.

A ResourceQuota is a namespace-scoped object that caps total resource consumption — or total object counts, such as the number of Pods or Services — across everything in that namespace. A namespace-wide quirk worth knowing: once a namespace has a ResourceQuota covering compute resources (CPU or memory), every Pod submitted to that namespace must specify its own requests and limits, or it is rejected outright at admission — the quota can't account for a Pod whose consumption it can't measure.

A LimitRange is a different, complementary namespace-scoped object: it supplies default requests and limits for any container that omits them, and can also enforce per-container minimum and maximum bounds. Where a ResourceQuota caps totals, a LimitRange fills gaps and bounds individual containers.

A container sets a memory request and limit but no CPU request or limit at all. Guaranteed, Burstable, or BestEffort?Answer it yourself first, then open this.

Burstable — it doesn't meet Guaranteed's all-resources-equal rule, but it does have at least one request or limit set.

ServiceAccounts, Authentication, Authorization and SecurityContexts

  • Attach a ServiceAccount to a Pod and control whether its token is automatically mounted.
  • Apply a SecurityContext that follows least-privilege for a Pod or container.

Every request to the Kubernetes API server passes through three stages in order: authentication (who is making this request), authorization (is that identity allowed to do this — RBAC is the usual mechanism), and admission control (mutating and validating webhooks and built-in controllers that can still modify or reject an already-authorized request). Any stage can stop the request before it reaches etcd.

Every Pod runs as a ServiceAccount — the default one for its namespace if none is specified. How that identity's token reaches the Pod has changed: a long-lived, non-expiring token stored in an auto-created Secret used to be generated for every ServiceAccount automatically; since Kubernetes v1.24, that automatic Secret is no longer created by default. Instead, Pods get a short-lived, audience-bound token delivered through a projected volume — stable behavior since v1.22 — refreshed automatically before it expires.

Every API request passes through all three stages, in order.

A securityContext can be set at the Pod level (applies to every container) or the container level (overrides the Pod level for that one container, field by field). Useful fields: runAsUser sets the numeric UID a process runs as; runAsNonRoot: true doesn't pick a UID for you — it only refuses to start the container if the UID it would run as turns out to be root. allowPrivilegeEscalation: false blocks a process from gaining more privileges than its parent had. readOnlyRootFilesystem: true mounts the container's root filesystem read-only. capabilities.drop: ["ALL"], optionally followed by adding back only the specific Linux capabilities actually needed, is the least-privilege pattern for capabilities.

If a Pod never talks to the Kubernetes API at all, automountServiceAccountToken: false — set on the Pod or its ServiceAccount — stops a token from being mounted into it in the first place, removing a credential the Pod has no use for.

A Pod sets runAsUser 1000 at the Pod level; one container overrides it with runAsUser 2000. Which UID does that container run as?Answer it yourself first, then open this.

2000 — a container-level securityContext field overrides the same field set at the Pod level.

Extending Kubernetes with CRDs and Operators

  • Register a CustomResourceDefinition and create instances of the custom resource it defines.
  • Explain what an Operator adds on top of a plain CRD.

A CustomResourceDefinition (CRD) registers a brand-new kind of object with the Kubernetes API, under apiextensions.k8s.io/v1 — the older v1beta1 version stopped being served as of Kubernetes v1.22, with v1 available since v1.16. Once a CRD is registered, instances of that kind — custom resources — can be created, read, updated and deleted with kubectl exactly like any built-in object, validated against the schema the CRD declares, and stored in etcd the same way.

On its own, that's all a CRD does: define a schema and a place to store structured data. Nothing watches it, and nothing acts on it — creating a custom resource with no controller behind its CRD just stores data, with no operational effect whatsoever.

bash

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: backupschedules.example.com
spec:
  group: example.com
  scope: Namespaced
  names:
    plural: backupschedules
    singular: backupschedule
    kind: BackupSchedule
  versions:
    - name: v1
      served: true
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                schedule:
                  type: string
A minimal CRD under the current apiVersion.

An Operator is a custom controller that watches instances of a CRD and reconciles real state to match what they declare — a database operator, for example, watching BackupSchedule objects and actually creating the Jobs, volumes or external API calls needed to make backups happen on that schedule. The CRD defines what — the shape of the data. The operator's controller defines what to do about it. A CRD without a controller is inert data; the operator is what turns a custom resource into something that actually does something in the cluster.

A CRD has no controller watching it. You create an instance of its custom resource. Does anything happen operationally?Answer it yourself first, then open this.

No — it's just stored. A CRD with no controller behind it has no operational effect; creating an instance only writes data.

Sources