Cluster Architecture, Installation & Configuration
Domain 1 of the CKA curriculum is officially named Cluster Architecture, Installation and Configuration. It's worth 25% of your score — the second-largest of the five domains, behind only Troubleshooting — and it publishes more distinct competencies than any other: eight of them, from bootstrapping a cluster with kubeadm to writing your first CustomResourceDefinition. Everything else on the exam quietly assumes this domain is solid ground. You can't troubleshoot a control plane you've never built, and you can't reason about a stuck Pod without already knowing what the CNI plugin someone installed is actually doing underneath it. This page walks the domain in the order you'd actually meet it building a real cluster: preparing infrastructure and running kubeadm init, assembling a highly available control plane, keeping a cluster alive across upgrades, the RBAC basics every cluster needs from day one, Helm and Kustomize for cluster-level components, and the extension interfaces — CNI, CSI, CRI — plus CRDs and operators that let Kubernetes grow past what ships in the box.
Imagine you're opening a brand-new wing of a hospital. Before any patient arrives, someone sets up the central administration office that tracks every bed, doctor, and order — that's your control plane. Because a hospital can't go dark if one office loses power, you build two or three backup offices sharing the same records — a highly available control plane. A badge system decides which staff can open which doors: nurses can't authorize discharges, cleaners can't prescribe medicine — that's RBAC. Instead of building every reception desk from raw lumber, you order pre-fab kits — Helm and Kustomize. And standard wall sockets and pipe fittings mean any vendor's monitor or oxygen line just plugs in — CNI, CSI, and CRI — including entirely new departments nobody designed for on day one, which is what CRDs and operators are for.
kubeadm init and gets his hands into the manifests. Nothing in the other four domains works until these two agree the foundation is solid.The five CKA domains, and where the rest of this blueprint lives
☺ Like you're 10: Five topics make up the whole exam, they're not equal size, and this is the second-biggest one on the list.
These are the five domains and weights exactly as published in the CNCF's Certified Kubernetes Administrator (CKA) Exam Curriculum, version 1.35 — five domains, no sub-weights, summing to exactly 100% (25 + 15 + 20 + 10 + 30). This page is Domain 1; each of the other four has its own page in this blueprint.
| Domain | Weight | Covered |
|---|---|---|
| D1 — Cluster Architecture, Installation and Configuration | 25% | This page |
| D2 — Workloads and Scheduling | 15% | Workloads & Scheduling |
| D3 — Servicing and Networking | 20% | Services & Networking |
| D4 — Storage | 10% | Storage |
| D5 — Troubleshooting | 30% | Troubleshooting |
A naming note worth having once: the v1.35 curriculum PDF prints the 20% domain as "Servicing and Networking"; a lot of study material writes it "Services and Networking." Same domain, same weight — no meaningful difference, just two names for one thing.
Domain 1 alone publishes eight competencies — more than any other domain — out of 27 total across the whole exam:
- Manage role based access control (RBAC)
- Prepare underlying infrastructure for installing a Kubernetes cluster
- Create and manage Kubernetes clusters using kubeadm
- Manage the lifecycle of Kubernetes clusters
- Implement and configure a highly-available control plane
- Use Helm and Kustomize to install cluster components
- Understand extension interfaces (CNI, CSI, CRI, etc.)
- Understand CRDs, install and configure operators
The rest of this page works through those eight in the order you'd actually meet them building a real cluster.
The CKA is performance-based, two hours, graded entirely on the end state of a live cluster — there is no multiple choice anywhere on it. That format, the pass mark, price, and the exact documentation allowlist you're permitted to open during the exam all change over time, and this is an independent, unofficial study resource with no affiliation to the CNCF or Linux Foundation. Confirm every logistics detail — format, duration, pass mark, price, retake policy, permitted docs, curriculum version — on the official Linux Foundation CKA page and the CNCF certification page before you register.
Preparing infrastructure and bootstrapping with kubeadm
☺ Like you're 10: Before you can turn the lights on, every node needs the same wiring — then one command turns the first node into the whole building's control room.
"Prepare underlying infrastructure" is a competency of its own for a reason — kubeadm init fails fast and unhelpfully if the machine underneath it isn't ready. Every node needs a unique hostname, MAC address, and /sys/class/dmi/id/product_uuid (cloned VM images are the classic way to violate this without noticing); swap traditionally disabled, since kubeadm refuses to proceed with it left on unless you've deliberately opted into the newer NodeSwap feature gate; a container runtime that speaks the Container Runtime Interface — containerd is the default almost everyone reaches for now that dockershim was removed in v1.24; and the kernel plumbing that lets the Pod network actually route traffic: the br_netfilter module loaded, and net.bridge.bridge-nf-call-iptables, net.bridge.bridge-nf-call-ip6tables, and net.ipv4.ip_forward all set to 1. One more detail the exam likes to hide a wrong answer in: the cgroup driver your container runtime uses and the one kubelet uses must match — systemd is the modern default for both, and a mismatch here produces confusing node failures rather than one clean error.
# --- run on every node before kubeadm touches anything --- cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf br_netfilter EOF cat <<EOF | sudo tee /etc/sysctl.d/k8s.conf net.bridge.bridge-nf-call-iptables = 1 net.bridge.bridge-nf-call-ip6tables = 1 net.ipv4.ip_forward = 1 EOF sudo sysctl --system sudo swapoff -a # and remove the swap entry from /etc/fstab, or it comes back on reboot # --- on the first control-plane node --- sudo kubeadm init \ --pod-network-cidr=10.244.0.0/16 \ --control-plane-endpoint=k8s-lb.internal:6443 \ --upload-certs # kubeadm prints two block of commands at the end — copy both, you need them once, ever mkdir -p $HOME/.kube sudo cp /etc/kubernetes/admin.conf $HOME/.kube/config sudo chown $(id -u):$(id -g) $HOME/.kube/config kubectl apply -f https://raw.githubusercontent.com/flannel-io/flannel/master/Documentation/kube-flannel.yml # (any CNI plugin works here — the cluster has no Pod networking until one is applied)
The --pod-network-cidr flag matters because it must match whatever CNI manifest you apply next — get them out of sync and Pods sit in ContainerCreating forever with no single obviously helpful event. Nothing routes traffic between Pods until a CNI plugin is applied; kubeadm deliberately doesn't pick one for you.
Highly available control planes: stacked vs. external etcd
☺ Like you're 10: One office is a single point of failure. Two patterns of backup offices exist, and they differ in where the filing cabinet lives.
A highly available control plane means more than one kube-apiserver — because the API server is stateless, you can run as many as you like behind a load balancer and none of them care which one a client happened to hit. The state that actually matters lives in etcd, and etcd's Raft consensus needs an odd-numbered quorum — 3 or 5 members, never an even number — to keep tolerating a node loss without losing the majority vote a write needs to commit. Two topologies satisfy that, and the CKA expects you to reason about both. Stacked etcd — kubeadm's default — runs an etcd member on the same node as each control-plane instance; fewer machines, but losing a control-plane node costs you an etcd member too. External etcd separates the two: a standalone etcd cluster that every control-plane node's API server talks to over the network; more machines to manage, but a control-plane node failure doesn't touch etcd's quorum at all.
Building either one starts the same way as a single-node cluster, plus one flag: --control-plane-endpoint pointed at the load balancer's stable address, set on the very first kubeadm init even if you're only planning a single control-plane node today — changing it later means rebuilding. --upload-certs encrypts and stashes the shared PKI material in the cluster itself so additional control-plane nodes don't need certificates copied over by hand; the upload expires after two hours by default, and kubeadm init phase upload-certs --upload-certs regenerates it if you miss the window.
# additional control-plane node joins with --control-plane and the cert key # from the original kubeadm init's --upload-certs output sudo kubeadm join k8s-lb.internal:6443 \ --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:1234...cafe \ --control-plane \ --certificate-key 9a1b2c... # from --upload-certs, or a re-run of the upload-certs phase # a worker node just omits --control-plane and --certificate-key entirely sudo kubeadm join k8s-lb.internal:6443 \ --token abcdef.0123456789abcdef \ --discovery-token-ca-cert-hash sha256:1234...cafe
Cluster lifecycle: version skew, upgrades, and etcd backups
☺ Like you're 10: Not every piece has to be the exact same version at the exact same time — but there are hard rules for how far apart they're allowed to drift, and a strict order for closing the gap.
Kubernetes' version skew policy is exam-tested and specific: kube-apiserver instances in an HA cluster may differ from each other by at most one minor version during a rolling upgrade; kubelet may run up to three minor versions older than kube-apiserver; kube-scheduler, kube-controller-manager, and cloud-controller-manager must never be newer than the API server and may be up to one minor version older; and kubectl may sit one minor version either side of the API server. The one rule everything else follows from: nothing is ever upgraded ahead of kube-apiserver.
That gives the upgrade order its shape. Upgrade the kubeadm package first, then run kubeadm upgrade plan and kubeadm upgrade apply on the first control-plane node only; every other control-plane node and every worker instead runs kubeadm upgrade node. Only after a node's control-plane components (or kubelet, on a worker) are upgraded do you drain it, upgrade kubelet and kubectl themselves, restart the kubelet, and uncordon — skip the drain and workloads disappear mid-upgrade with no warning; skip the uncordon and the node just quietly never receives another Pod again.
# first control-plane node apt-mark unhold kubeadm && apt-get update && apt-get install -y kubeadm=1.35.1-1.1 && apt-mark hold kubeadm kubeadm upgrade plan kubeadm upgrade apply v1.35.1 kubectl drain cp-1 --ignore-daemonsets --delete-emptydir-data apt-mark unhold kubelet kubectl apt-get update && apt-get install -y kubelet=1.35.1-1.1 kubectl=1.35.1-1.1 apt-mark hold kubelet kubectl systemctl daemon-reload && systemctl restart kubelet kubectl uncordon cp-1 # every other node (control-plane or worker): kubeadm upgrade node, not upgrade apply apt-mark unhold kubeadm && apt-get install -y kubeadm=1.35.1-1.1 && apt-mark hold kubeadm kubeadm upgrade node # ...then the same drain / kubelet+kubectl upgrade / uncordon sequence as above
The lifecycle competency also covers backing up the one thing an upgrade can't recreate for you: etcd's data. etcdctl snapshot save takes a point-in-time snapshot against a running cluster; restoring never talks to a live API server at all — etcdutl (etcd 3.6 moved snapshot restore out of etcdctl into this separate binary) writes a fresh data directory offline, and you point the etcd static Pod's hostPath at that new directory and let kubelet restart it.
ETCDCTL_API=3 etcdctl snapshot save /opt/backups/etcd-$(date +%s).db \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key etcdutl snapshot restore /opt/backups/etcd-1755000000.db \ --data-dir=/var/lib/etcd-restored # then edit /etc/kubernetes/manifests/etcd.yaml's hostPath to point at /var/lib/etcd-restored
Practicing the failure modes this domain doesn't fully cover — a control plane that won't come back after a bad upgrade, a restore that silently used the wrong snapshot — is exactly what Domain 5, Troubleshooting tests at 30% weight. This page teaches you to build and maintain the cluster; that one teaches you to fix it under a clock.
RBAC — the operator's baseline
☺ Like you're 10: A badge system: what a badge can open is written down explicitly, and no badge opens a door nobody wrote a rule for.
Kubernetes RBAC is default-deny: an identity — a user, a group, or a ServiceAccount — can do exactly what an explicit rule grants and nothing else; there's no such thing as a deny rule, only the absence of an allow. Four objects do all the work. A Role lists allowed verbs (get, list, watch, create, update, patch, delete) against resources, scoped to one namespace; a ClusterRole is the same idea but cluster-wide, or reusable across many namespaces. A RoleBinding attaches a Role — or a ClusterRole, for a reusable permission set applied to just one namespace — to a subject within that namespace; a ClusterRoleBinding attaches a ClusterRole cluster-wide, with no namespace boundary at all.
apiVersion: v1
kind: ServiceAccount
metadata: { name: builder, namespace: dev }
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata: { name: pod-reader, namespace: dev }
rules:
- apiGroups: [""]
resources: ["pods", "pods/log"]
verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata: { name: builder-reads-pods, namespace: dev }
subjects:
- kind: ServiceAccount
name: builder
namespace: dev
roleRef: { kind: Role, name: pod-reader, apiGroup: rbac.authorization.k8s.io }The command that saves you from guessing whether a policy actually works: kubectl auth can-i, which can impersonate any subject with --as and --as-group before you ever hand out a real credential.
kubectl auth can-i delete pods -n dev \ --as=system:serviceaccount:dev:builder # no kubectl auth can-i get pods -n dev \ --as=system:serviceaccount:dev:builder # yes
This domain expects the mechanics — writing correct Roles and Bindings, then verifying them. Hardening a real cluster's RBAC posture — least-privilege audits, aggregated ClusterRoles, tying RBAC to Pod Security Admission and OPA/Kyverno policy — is deliberately out of scope here and belongs to CKS, this course's own upcoming RBAC & Admission Control deep dive, and DevSecOps' Kubernetes Security Deep Dive, which covers the same objects from an attacker's-eye view.
Helm and Kustomize for cluster components
☺ Like you're 10: Instead of hand-building every reception desk from raw lumber, you order a pre-fab kit and just tell it your room's dimensions.
This competency is specifically about cluster components — the add-ons a cluster needs before any application workload shows up: an ingress controller, cert-manager, metrics-server, the CNI plugin itself, a CSI driver. It isn't about how application teams package their own workloads, which is a Domain 2 concern. Helm packages a component as a versioned chart with templated manifests and a values.yaml you override per environment; helm upgrade --install is the idiom worth memorizing, since it installs on a fresh cluster and upgrades an existing release with one identical command — exactly what a repeatable bootstrap script needs.
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx helm repo update helm upgrade --install ingress-nginx ingress-nginx/ingress-nginx \ --namespace ingress-nginx --create-namespace \ --set controller.service.type=LoadBalancer \ --values ingress-values.yaml helm history ingress-nginx -n ingress-nginx # every prior revision, for a rollback helm rollback ingress-nginx 2 -n ingress-nginx
Kustomize takes the opposite approach — no templating language at all, just plain YAML manifests plus a kustomization.yaml that patches them declaratively, and it ships built into kubectl itself via -k. The standard shape is a base/ with the generic manifests and one overlays/<env>/ directory per environment that patches only what differs:
# overlays/prod/kustomization.yaml
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- ../../base
namePrefix: prod-
commonLabels:
environment: prod
patches:
- target: { kind: Deployment, name: metrics-server }
patch: |-
- op: replace
path: /spec/replicas
value: 2kubectl apply -k overlays/prod/ # or: kustomize build overlays/prod | kubectl apply -f -
Both tools get a full tool guide of their own here — Helm and Kustomize — and the Platform Engineering course's Helm and Kustomize pages go considerably deeper into chart authoring and advanced patch strategies than the CKA needs.
Extension interfaces: CNI, CSI, and CRI
☺ Like you're 10: Three standard plug shapes — network, storage, and the engine itself — so any vendor's part clicks into the same socket.
Kubernetes doesn't implement networking, storage provisioning, or containers itself; it defines three interfaces and lets vendors plug in behind them. The Container Runtime Interface (CRI) is what kubelet speaks to actually start and stop containers — containerd is the default almost every distribution ships since Docker support (dockershim) was removed in v1.24, with CRI-O as the other common choice. crictl talks CRI directly, bypassing kubectl and the API server entirely, which makes it the tool you reach for exactly when the API server itself is the thing that's broken:
crictl ps -a # containers this node's runtime knows about, API server or not crictl images crictl logs <container-id> crictl inspect <container-id> | grep -A3 '"pid"'
The Container Network Interface (CNI) is what kubelet calls each time it creates a Pod's network namespace — plugin binaries live in /opt/cni/bin, and configuration in /etc/cni/net.d/, normally exactly one active .conflist per node. Calico, Cilium, and Flannel are the common exam-relevant choices, and this course covers each as its own tool guide; the mechanics of how a plugin actually wires a Pod's veth pair into the node's bridge or overlay live in this course's Networking & the CNI deep dive.
The Container Storage Interface (CSI) is the same idea for volumes: a driver ships as a controller plugin (usually a Deployment, talking to whatever cloud or storage backend actually provisions the volume) and a node plugin (a DaemonSet, handling the mount on each node), registered against the cluster through CSIDriver and CSINode objects. A StorageClass names which CSI driver handles a given class of volume:
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata: { name: fast-ssd }
provisioner: ebs.csi.aws.com # the CSI driver's registered name, not a Kubernetes built-in
parameters: { type: gp3 }
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: DeleteFull volume mechanics — access modes, reclaim policy, PV/PVC binding — belong to Domain 4, Storage and this course's own Storage & the CSI deep dive; the Platform Engineering course's Kubernetes as the Substrate page covers all three extension interfaces from the platform-builder's altitude, if you want the same material from a different angle.
CRDs and operators
☺ Like you're 10: A CRD teaches the API a brand-new word; an operator is the robot that then makes that word actually mean something.
A CustomResourceDefinition extends the Kubernetes API with a type you invent — the moment it's applied, kubectl get, RBAC, kubectl apply, and every other API mechanic just work against it, identically to how they work against a built-in Pod or Deployment.
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata: { name: postgresclusters.db.example.com }
spec:
group: db.example.com
scope: Namespaced
names: { kind: PostgresCluster, plural: postgresclusters, singular: postgrescluster }
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
replicas: { type: integer }
version: { type: string }
---
apiVersion: db.example.com/v1
kind: PostgresCluster
metadata: { name: orders-db, namespace: prod }
spec: { replicas: 3, version: "16" }A CRD alone is just a schema — nothing reconciles it into anything real. An operator is a controller written against that custom type, running the exact same loop every built-in Kubernetes controller runs: watch the API for the object's current spec (desired) and status (observed), compare the two, and take whatever action closes the gap — provisioning a real 3-node Postgres cluster, in the example above, and writing the outcome back to status. That reconciliation loop is the same one covered generally in The Object Model; a CRD plus an operator is just that pattern aimed at a domain you defined yourself instead of one the Kubernetes project shipped. This course's own Operators & Custom Resource Definitions deep dive goes deep on writing one; the Platform Engineering course's Platform APIs & CRDs covers the same ground from a platform team's perspective, designing CRDs as a self-service API surface rather than a single application's config.
Where Domain 1 trips people up
☺ Like you're 10: Almost every wrong answer here is a step done out of order, or a version pinned when it should've been unpinned.
- Forgetting
apt-mark hold/unhold. kubeadm holdskubeadm,kubelet, andkubectlat their installed version specifically so a routineapt-get upgradecan't silently jump your control plane forward. Skip the unhold before an intentional upgrade and the install command is a silent no-op; forget the hold afterward and the next unrelatedapt-get upgradedoes the jump for you. kubeadm upgrade applyvs.kubeadm upgrade node. Only the first control-plane node ever runsapply. Every other node — control-plane or worker — runsupgrade node. Runningapplytwice is a common scripted-upgrade bug.etcdctlvs.etcdutl. Since etcd 3.6,snapshot restorelives inetcdutl, notetcdctl— check which binary your cluster's etcd version actually ships before assuming an old command still works.- The
--upload-certswindow is two hours. Miss it and additional control-plane nodes can't join with the certificate key you were given — re-runkubeadm init phase upload-certs --upload-certsrather than assuming something's broken. - RBAC has no deny rules. If a scenario implies "explicitly block this ServiceAccount," the real fix is removing or narrowing an allow rule, or scoping a Role tighter — there's no RBAC object that expresses "deny."
- A missing CNI plugin looks like a hung cluster, not an error. Nodes report
NotReadyand Pods sit inPendingorContainerCreatingwith no single obvious error line — kubeadm deliberately ships no default CNI, and it's the first thing to check on a cluster that "isn't working" right afterkubeadm init.
This course covers CKAD and CKS in the same depth as CKA — see the certifications hub for all three. The nine other CNCF certifications plus the LFCS that complete the Golden Kubestronaut ladder alongside Kubestronaut live in the sibling Golden Astronaut course.
Benny: Cluster's up — three control-plane nodes, stacked etcd, CNI applied, kubectl config copied. Forty minutes, start to finish.
Owl: And the version skew? I don't want a kubelet three-and-a-half minors behind the API server discovering that "point-five" isn't a real version kubeadm accepts.
Benny: Same minor everywhere, on purpose. First real drift happens at the next upgrade, and I'm holding every package until then.
Gizmo: Or... skip the RBAC step entirely? Bind cluster-admin to everything, ship it Friday, nobody's ever locked out of anything. 🤑
Timmy: That's not "nobody locked out" — that's "everybody holds a master key." One compromised ServiceAccount and it's not just their namespace anymore, it's the whole cluster's.
Nutty: I already tagged what's stacked where — labels, kubeconfig context names, all of it. Ask me before you SSH into the wrong node's shell again.
1. Which CKA domain carries the most weight, and how does Domain 1's 25% compare to it? 2. Name the eight published competencies under Cluster Architecture, Installation and Configuration. 3. What's the practical difference between stacked etcd and external etcd, and what rule must an etcd cluster's member count always satisfy? 4. Walk the correct order of operations for upgrading a non-first control-plane node. 5. Why can't an RBAC Role or ClusterRole express a "deny" rule? 6. Name the three extension interfaces this domain covers and what each one abstracts.
Check your answers
- Troubleshooting, at 30% — the single largest domain. Domain 1 is second, at 25%, and covers more distinct competencies (eight) than any other domain.
- Manage RBAC; prepare underlying infrastructure; create and manage clusters using kubeadm; manage cluster lifecycle; implement and configure a highly-available control plane; use Helm and Kustomize to install cluster components; understand extension interfaces (CNI, CSI, CRI); understand CRDs and install/configure operators.
- Stacked etcd runs an etcd member on each control-plane node (fewer machines, but a control-plane node loss costs an etcd member too); external etcd runs etcd as a separate cluster the API servers talk to over the network (more machines, but control-plane and etcd failures are decoupled). Either way, etcd's Raft quorum must be an odd number — 3 or 5 — to tolerate a node loss without losing majority.
- Upgrade the kubeadm package, run
kubeadm upgrade node(notapply— that's first-control-plane-node only), then drain the node, unhold and upgrade kubelet and kubectl, restart the kubelet, and uncordon. - Kubernetes RBAC only expresses allow rules; an identity can do exactly what an explicit rule grants and nothing else. There's no object type for "deny" — removing access means removing or narrowing an allow rule, not adding a block.
- CRI (Container Runtime Interface) — how kubelet starts and stops containers, implemented by containerd or CRI-O. CNI (Container Network Interface) — how a Pod's network namespace gets wired up. CSI (Container Storage Interface) — how a StorageClass's provisioner actually creates and attaches a volume.