Kubernetes and Cloud Native Associate (KCNA) #3: Kubernetes Fundamentals 2 — API, Containers, Scheduling
In #2 we sorted out the components of the control plane and worker nodes, along with core resources like Pod, ReplicaSet, Deployment, Service, and Namespace. That gave us the skeleton of the cluster and the kinds of objects that run on top of it. This post covers the second half of Domain 1 (Kubernetes Fundamentals, 46%): how you actually work with those objects.
Concretely, there are four areas. First, the structure of the Kubernetes API that every resource passes through, and the declarative vs. imperative ways of interacting with it. Second, the containers that actually run inside a Pod and their relationship with images, registries, and runtimes. Third, the scheduling process by which the kube-scheduler decides which node a Pod lands on. Finally, how to inject configuration with ConfigMap and Secret. These four topics fill out the remaining half of Domain 1.
The Kubernetes API: everything is an object #
If you had to sum up Kubernetes in a single sentence, it is a giant API server. Launching a Pod, exposing a Service, creating a Namespace — every one of these is the act of registering or modifying an object on the apiserver. KCNA expects you to develop the instinct of reading “do X in Kubernetes” as “send an API object called X to the apiserver.”
Every resource has the same five-slot structure #
Whether it is a Pod, a Deployment, or a ConfigMap, a Kubernetes object is always made up of the same four or five fields.
| Field | Role |
|---|---|
apiVersion | The API group and version this object belongs to (e.g., v1, apps/v1) |
kind | The kind of object (e.g., Pod, Deployment, Service) |
metadata | Identifying information such as name, namespace, labels, annotations |
spec | The state the user wants (desired state). “This is how it should be.” |
status | The current state (actual state) filled in by the system. The user does not write it. |
The point that comes up most often on the exam is the distinction between spec and status. spec is the goal the user declares, and status is the field a controller fills in to record the current state. Kubernetes controllers continuously work to bring status in line with spec, and this endless correction process is called the reconciliation loop. The controller-manager we saw in #2 is exactly the component that runs this loop.
Here is the minimal form of a Pod manifest.
apiVersion: v1
kind: Pod
metadata:
name: nginx
namespace: default
spec:
containers:
- name: nginx
image: nginx:1.27You can see apiVersion: v1, kind: Pod, metadata.name, and the container definition under spec. There is no status field here. The user writes only the spec and submits it; the apiserver receives it and stores it in etcd, and after the scheduler and kubelet do their work, they fill in the status.
API groups and versions #
It is worth noting that the apiVersion value takes two forms, so it does not trip you up on the exam.
- Core group. You write just
v1. The most basic resources — Pod, Service, ConfigMap, Secret, Namespace — belong here. Since the group name is empty, you write only the version. - Named group. The form is
<group>/<version>. Deployment, ReplicaSet, StatefulSet, and DaemonSet belong toapps/v1; Job and CronJob tobatch/v1; Ingress tonetworking.k8s.io/v1.
Version notation has maturity stages: v1 (stable), v1beta1 (beta), and v1alpha1 (alpha). The more stable the version, the stronger the backward-compatibility guarantee. KCNA does not ask you to memorize every group, but remembering that Pod is core (v1) and Deployment is apps/v1 helps you tell answer choices apart.
Declarative vs. imperative #
There are broadly two ways to interact with Kubernetes. The distinction between them is a recurring KCNA exam point.
Imperative: directly command an action #
The imperative style directly orders “run this action now.” Commands like kubectl run, kubectl create, kubectl delete, and kubectl scale belong here.
kubectl run nginx --image=nginx:1.27
kubectl create deployment web --image=nginx:1.27
kubectl scale deployment web --replicas=3This is fast and intuitive, but what you did is not recorded anywhere. To recreate the same state you have to remember and retype the commands, and change history is difficult to track.
Declarative: declare the desired state #
The declarative style writes “this is the state it should end up in” as a YAML manifest and applies it with kubectl apply.
kubectl apply -f deployment.yamlThe manifest itself is the desired state, and Kubernetes brings the current state in line with that goal. Reapplying the same file is safe (idempotent), and keeping the file in Git preserves the change history as is. This property is the foundation of GitOps, which we will cover in #7. The standard way to operate Kubernetes is declarative, and on the exam, when asked for the “recommended approach,” declarative (kubectl apply) is the answer.
| Aspect | Imperative | Declarative |
|---|---|---|
| Representative commands | kubectl run, create, scale | kubectl apply -f |
| What you specify | The action to run | The desired end state |
| History tracking | Hard | Recorded in file/Git |
| Re-running | May conflict | Idempotent, safe |
| Recommended | Quick practice/debugging | Operational standard |
What kubectl really is #
It is worth being clear about kubectl’s role. kubectl is not part of the cluster — it is simply a client that talks to the apiserver over HTTP. When you run kubectl apply, kubectl reads the YAML and sends an API request to the apiserver, and all the subsequent reconciliation happens inside the control plane. KCNA treats kubectl as “a convenience tool for calling the Kubernetes API.” Calling the apiserver directly with curl instead of kubectl produces the same result. Working through kubectl basics and Pod operations hands-on in Practical track #3 will make this concept stick more firmly.
Containers: what actually runs inside a Pod #
In #2 we said the Pod is the smallest unit of scheduling. What actually runs inside that Pod is a container.
Images and registries #
A container is created from an image. An image is a read-only package that bundles the application binary, libraries, and runtime, and the standard image format is defined by the OCI (Open Container Initiative). Images are stored and distributed in a registry — Docker Hub, GitHub Container Registry, and Amazon ECR are all examples of registries.
In image: nginx:1.27, nginx is the image name and 1.27 is the tag. If you omit the tag, latest is used by default — but latest does not mean “the newest version”; it is just a tag name. In production, an explicit version tag is always recommended. This point also connects to the deployment-stability discussion in #7.
Container runtime #
The actor that takes an image and runs it as a real process is the container runtime. Kubernetes is not tied to any specific runtime; it communicates with the runtime through a standard interface called the CRI (Container Runtime Interface), and containerd or CRI-O plugs into that slot. The kubelet on a worker node instructs the runtime via CRI to “launch a container from this image.” The detailed layering of runtimes and CRI is a Domain 2 topic, covered in depth in #4. For now, the key relationship to grasp is: a container inside a Pod is run by the runtime, and the kubelet drives that runtime through CRI.
When a Pod has multiple containers #
A Pod can hold one or more containers. Most have just one, but two kinds of helper-container patterns show up on the exam.
- Init container. A container that runs sequentially before the main container and then exits. It handles preparation work such as downloading configuration files or waiting for a dependent service. The main container starts only after every init container completes successfully.
- Sidecar. A container that runs alongside the main container, adding helper functions like log collection or a proxy. Proxy injection in a Service Mesh is a representative sidecar case, which we cover in #4.
The key point is that containers in the same Pod share the same network namespace (same IP, localhost communication) and the same volumes.
Scheduling: which node a Pod lands on #
When a new Pod is created, it initially has no node assigned. Deciding which worker node will run the Pod is scheduling, and the control plane component that makes that decision is the kube-scheduler we saw in #2.
Two stages: filtering and scoring #
The kube-scheduler picks a node in two stages.
- Filtering. It filters out nodes that cannot run this Pod. Nodes short on resources (CPU, memory) or that do not meet the requirements get excluded here. The nodes that remain are called feasible nodes.
- Scoring. It scores the remaining nodes and picks the most suitable one. Scores are computed so that resources are spread evenly, or to match a specific policy.
If no node survives filtering, the Pod cannot be placed and stays in the Pending state. On the exam, a Pod stuck in Pending almost always points to a scheduling failure — insufficient resources or a constraint mismatch.
Resource requests drive scheduling #
The first criterion for scheduling is resources. If you write the required CPU and memory under a container’s resources.requests, the scheduler keeps only the nodes that have room to satisfy that request as candidates.
spec:
containers:
- name: app
image: myapp:1.0
resources:
requests:
cpu: "250m"
memory: "256Mi"
limits:
cpu: "500m"
memory: "512Mi"- requests. The value used for scheduling. It means “at minimum this much must be available,” and the scheduler evaluates node suitability based on this value.
- limits. The upper bound on usage while running. Limits are enforced at runtime, not at scheduling time, and a container that exceeds its memory limit may be terminated by OOM.
The distinction that requests are for scheduling, limits are runtime caps comes up often.
nodeSelector: the simplest node targeting #
When you want to place a Pod only on certain nodes, the simplest tool is nodeSelector. It restricts placement to nodes whose labels match exactly.
spec:
nodeSelector:
disktype: ssdThis places the Pod only on nodes carrying the disktype=ssd label. Since the condition is a single exact match, it is limited in expressiveness — but that also makes it easy to understand.
Node affinity / anti-affinity: more flexible rules #
To express richer conditions than nodeSelector, you use affinity.
- Node affinity. It expresses “prefer or require nodes with these labels.” You can vary the strength with
requiredDuringScheduling...(mandatory) andpreferredDuringScheduling...(preferred), enabling rules like “place it here if possible, but allow other nodes if not.” - Pod affinity / anti-affinity. It is based not on a node’s labels but on the location of other Pods. Affinity means “gather this on the same node (or zone) as a specific Pod,” and anti-affinity means “keep it away from a specific Pod.” You use anti-affinity to scatter replicas of the same app across different nodes to raise availability.
At the KCNA level, it is enough to understand that nodeSelector is a simple exact match, while affinity supports preferred or mandatory rules with more complex conditions.
Taints and tolerations: the node pushes Pods away #
Up to now the direction has been Pods choosing nodes. Taints and tolerations are the opposite: a mechanism by which the node pushes Pods away.
- A taint is a mark attached to a node. If a node has a taint, no Pod is scheduled onto it by default. In effect, the node repels Pods.
- A toleration is an exemption attached to a Pod. If a Pod has a toleration that matches a specific taint, it can be placed on the node carrying that taint.
In other words, a taint (the node’s refusal) and a toleration (the Pod’s permission) must form a pair for a Pod to land on that node. The reason ordinary workloads do not run on a control plane node is precisely that the control plane node carries a taint. It is also used to dedicate a GPU node to specific Pods only.
It is important not to mix up the directions here. The affinity family has the Pod attract a node, while taint/toleration has the node repel Pods and accept only those carrying a matching toleration. This directional distinction is a recurring exam trap.
Configuration injection: ConfigMap and Secret #
If you bake an application’s configuration values into the image, you have to rebuild the image for every environment change. Kubernetes provides two resources that inject configuration from outside the container.
ConfigMap: non-secret configuration #
A ConfigMap is a resource that holds non-sensitive configuration values — environment variables, config files, flags — as key-value pairs.
apiVersion: v1
kind: ConfigMap
metadata:
name: app-config
data:
LOG_LEVEL: "info"
APP_MODE: "production"Secret: sensitive information, but not encrypted by default #
A Secret is a resource for sensitive information such as passwords, tokens, and certificates, and its structure is similar to a ConfigMap. There is one exam point KCNA always emphasizes here.
A Secret’s values are merely base64-encoded; they are not encrypted by default.
base64 is an encoding that anyone can decode instantly — it is not encryption. Do not be misled into thinking that a Secret itself keeps its values safe. To store them securely you need to separately enable encryption at rest in etcd, restrict access with RBAC, or integrate an external secret-management tool. The statement “Secrets are not encrypted by default” is a trap that almost always appears on the exam, so memorize it exactly as written.
Two ways to inject into a container #
Whether using a ConfigMap or a Secret, there are two ways to deliver it to a container.
| Method | Description | Characteristics |
|---|---|---|
| Environment variables (env) | Inject key-value pairs as the container’s environment variables | Simple. But changes made while running are not reflected. |
| Volume mount (volume) | Mount the ConfigMap/Secret as files | When file form is needed. Changes can be reflected. |
Whether you inject each setting as an environment variable or mount the whole config file as a volume depends on how the application reads its configuration. Working through real ConfigMap and Secret examples hands-on in Practical track #6 will make the concept concrete.
Exam points from this post #
These are the spots where the second half of Domain 1 most often probes you with multiple-choice options.
- Declarative vs. imperative.
kubectl applyis declarative (declares the desired state, operational standard);kubectl run/createis imperative (directly commands an action). When asked for the “recommended approach,” it is declarative. - spec vs. status. spec is the desired state the user writes; status is the actual state the system fills in.
- apiVersion. Pod, Service, ConfigMap, Secret are core (
v1); the Deployment family isapps/v1. - requests vs. limits. requests are the scheduling criterion; limits are the runtime usage cap.
- affinity vs. taint/toleration. affinity has the Pod attract a node; taint/toleration has the node repel Pods, allowing only those with a toleration.
- Secrets are base64-encoded by default, not encrypted. This single sentence is a recurring trap.
Wrap-up #
What we nailed down in this post:
- Kubernetes API. Every resource has the same structure of apiVersion, kind, metadata, spec, status, and controllers adjust status to match spec.
- Declarative/imperative. The distinction between
kubectl apply(declarative, operational standard) andkubectl run/create(imperative). kubectl is a client that talks to the apiserver. - Containers. Images are stored in a registry, and the runtime runs them through CRI. A Pod holds one or more containers, and init container and sidecar patterns exist.
- Scheduling. The kube-scheduler picks a node through filtering then scoring. requests, nodeSelector, affinity, and taint/toleration influence the decision.
- Configuration injection. Inject ConfigMap (non-secret) and Secret (sensitive, but base64-encoded by default rather than encrypted) via env or volume.
With this, we have wrapped up the 46%-weight Domain 1 (Kubernetes Fundamentals) across #2 and this post. Having covered the largest domain, you have secured the score band that gets you closest to the passing line.
Next: Container Orchestration #
We have covered the core of Kubernetes itself. Now we go one level down to the layer that underlies containers.
In #4 Container Orchestration (22%): Runtime, Security, Networking, Storage, Service Mesh, we will sort out the second-largest domain: container runtimes (containerd, CRI-O) and CRI, security (RBAC, NetworkPolicy), networking (CNI, Service, DNS), storage (CSI, PV, PVC), and Service Mesh.