Highly available control planes

Stacked versus external etcd, quorum math, a load balancer in front of the API servers, and the kubeadm flags that let additional control plane nodes join.

Architecture · 25%35 min2 tasks

After this lesson

  • 01You can explain stacked and external etcd topologies and how many member failures three or five members survive.
  • 02You can initialize a cluster with a load-balanced controlPlaneEndpoint and join further control plane nodes with --control-plane and --certificate-key.
  • 03You can read an existing cluster's control plane layout from the kubeadm-config ConfigMap and the API server's flags.

This lesson covers Implement and configure a highly-available control plane. The exam will not have you build a load balancer, but it can ask you to join another control plane node, to say how many etcd members may fail, or to find out how an existing cluster is laid out. All of that rests on one question: which pieces hold state, and which do not.

What has to be redundant

The API server is stateless. Run one on every control plane node, put a load balancer in front, and any instance can serve any request. The scheduler and the controller manager also run on every control plane node, but only one copy of each acts at a time: they elect a leader through a Lease object in kube-system, and the others wait to take over. etcd is the hard part, because it holds the state and needs a majority of its members to agree on every write.

etcd membersQuorum (majority)Failures tolerated
110
220
321
431
532
743

Quorum is floor(n/2) + 1. A write commits only once a quorum has it, and a member cut off from the majority stops accepting writes rather than drift apart. Even sizes buy nothing: four members tolerate one failure, the same as three, with one more machine that can break, and two members are worse than one. Run three, or five if you must survive two failures; beyond that every write waits on more peers. When quorum is lost, the API server cannot write anything. Running Pods keep running, but nothing in the cluster can change.

Stacked or external etcd

Stacked etcdExternal etcd
Where etcd runsA static Pod on every control plane node, created by kubeadmIts own hosts, usually as a systemd service, set up before kubeadm init
The API server talks toIts local member: --etcd-servers=https://127.0.0.1:2379Every member: --etcd-servers=https://10.0.0.21:2379,https://10.0.0.22:2379,…
Machines for three of each36
Failure couplingLosing a node removes an API server and an etcd member togetherControl plane and etcd fail independently
In kubeadm-configetcd.localetcd.external with endpoints and client certificate paths
etcd.yaml in the manifests directoryYesNo

Building it with kubeadm

First control plane, then the othersbash
# First control plane. The endpoint is the load balancer's DNS name (or VIP) and port.
sudo kubeadm init \
  --control-plane-endpoint "k8s-api.lab.internal:6443" \
  --upload-certs \
  --pod-network-cidr=192.168.0.0/16

# kubeadm prints two join commands. The control plane one adds two flags:
sudo kubeadm join k8s-api.lab.internal:6443 --token <token> \
  --discovery-token-ca-cert-hash sha256:<hash> \
  --control-plane --certificate-key <key>

# Two hours later the uploaded certificates are gone. On an existing control plane:
sudo kubeadm init phase upload-certs --upload-certs                  # prints a new key
sudo kubeadm token create --print-join-command --certificate-key <new-key>

controlPlaneEndpoint is the address every kubeconfig, kubelet and joining node uses, and kubeadm adds it to the API server certificate. kubeadm cannot convert a cluster initialized without it into an HA one, so set it on day one even with a single node; this kind lab has cka-control-plane:6443. The load balancer passes TCP 6443 through to every API server (TLS ends at the API server) and health-checks each backend; HAProxy with keepalived, kube-vip and cloud load balancers all work. --upload-certs stores the shared CA, ServiceAccount and front-proxy keys (and the etcd CA when etcd is stacked) in the kubeadm-certs Secret, encrypted with the certificate key. Secret and key expire after two hours. Without them you copy those files to each new control plane by hand before joining it.

The same with a config file and external etcd (kubeadm init --config … --upload-certs)yaml
apiVersion: kubeadm.k8s.io/v1beta4
kind: ClusterConfiguration
kubernetesVersion: v1.37.1
controlPlaneEndpoint: "k8s-api.lab.internal:6443"
networking:
  podSubnet: 192.168.0.0/16
etcd:
  external:
    endpoints:
      - https://10.0.0.21:2379
      - https://10.0.0.22:2379
      - https://10.0.0.23:2379
    caFile: /etc/kubernetes/pki/etcd/ca.crt
    certFile: /etc/kubernetes/pki/apiserver-etcd-client.crt
    keyFile: /etc/kubernetes/pki/apiserver-etcd-client.key
Reading the layout of a running clusterbash
# What kubeadm decided at init time
kubectl -n kube-system get cm kubeadm-config -o jsonpath='{.data.ClusterConfiguration}' | grep '^controlPlaneEndpoint:'
kubectl -n kube-system get cm kubeadm-config -o jsonpath='{.data.ClusterConfiguration}' | grep -A3 '^etcd:'

# Which etcd members the API server uses (one argument per line, so commas stay intact)
kubectl -n kube-system get pod -l component=kube-apiserver \
  -o jsonpath='{range .items[0].spec.containers[0].command[*]}{@}{"\n"}{end}' | grep -- --etcd-servers

# Control plane nodes, their Pods, and the current leaders
kubectl get nodes -l node-role.kubernetes.io/control-plane
kubectl -n kube-system get pods -l tier=control-plane -o wide
kubectl -n kube-system get lease kube-scheduler kube-controller-manager \
  -o custom-columns=NAME:.metadata.name,HOLDER:.spec.holderIdentity

In the docs · the tab you get on exam day

Taskar-ha-1
Architecture4%~6 min

context · kubectl config use-context kind-cka

Describe this cluster's control plane layout

Find out how this cluster's control plane is wired, without changing anything, and write each answer as a bare value. (1) The control plane endpoint kubeadm configured, as host:port, to /tmp/cka/ar-cp-endpoint.txt. (2) The etcd topology, as the single word stacked or external, to /tmp/cka/ar-etcd-topology.txt. (3) The full value of kube-apiserver's --etcd-servers flag to /tmp/cka/ar-etcd-servers.txt. (4) If this cluster were grown to five control plane nodes with the same etcd topology, how many etcd members could fail at once without losing quorum? Write the number to /tmp/cka/ar-etcd-tolerance.txt.
  1. 01

    Set up the scenario

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ha-1/setup | bash
  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ha-1/verify | bash
    Counts toward your readiness score.
  4. 04

    Clean up

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ha-1/cleanup | bash
Taskar-ha-2
Architecture7%~12 min

Cluster: a kubeadm-built cluster (VM lab). The kind lab cannot run this one.

Join a third control plane node

Clients reach this cluster's API through a load balancer at k8s-api.lab.internal:6443. Control plane nodes cp1 and cp2 run stacked etcd. Node cp3 is prepared with containerd and v1.37 packages and is already in the load balancer's backend pool. Join cp3 as a third control plane node. The cluster was initialized days ago, so neither its bootstrap token nor its certificate key is valid anymore. You work on cp1; ssh cp3 reaches the new node.

This one needs a cluster you built with kubeadm. Read it and the solution now; run it when you have the VM lab.

  1. 01

    Nothing to set up

    Start on a healthy lab. Check it with lab-check.sh if unsure.

  2. 02

    Solve it in your terminal

    Work the way the exam makes you: kubectl, the docs, nothing else. Put any answer files under /tmp/cka/.

  3. 03

    Check your work

    curl -fsSL https://codewithmuh.com/labs/cka/t/ar-ha-2/verify | bash
    Counts toward your readiness score.

Finished reading?

0 of 2 tasks passed. Readiness counts tasks, so run them before you move on.