Configure HAProxy for a Highly Available Kubernetes API
Three healthy control-plane nodes already share an etcd cluster. Their names and internal addresses are listed in `/root/control-planes.txt`. Complete the missing client-facing load-balancer configuration on this lab node. HAProxy and socat are installed; `/etc/haproxy/haproxy.cfg` contains the frontend and backend skeleton.
Live lab access
This task description is public. Its live lab requires full access; six other CKA labs are available in Free Practice with AI Lab Coach.
Create an account to continue →
Explore the six free labs with AI Lab Coach →
Read the brief on any device. Use a desktop or laptop for the terminal lab. Nothing starts until you choose Start.
The task
Three healthy control-plane nodes already share an etcd cluster. Their names and internal addresses are listed in /root/control-planes.txt. Complete the missing client-facing load-balancer configuration on this lab node. HAProxy and socat are installed; /etc/haproxy/haproxy.cfg contains the frontend and backend skeleton.
Requirements
1. Add all three listed control-plane addresses on port 6443 to backend controlplanes, using server names cp1, cp2, and cp3. Enable TCP health checks on each server.
2. Validate the configuration and start HAProxy using it, writing its PID to /run/haproxy.pid. Preserve the frontend on 127.0.0.1:7443 and the configured statistics socket.
3. Copy the existing administrator kubeconfig to /root/ha.conf and change that copy's server to https://127.0.0.1:7443. Preserve CA and client authentication; do not disable TLS verification.
4. Verify that the shared endpoint serves Kubernetes API requests and all three backends are healthy.
The HAProxy skeleton is provided so this exercise focuses on connecting the HA control plane rather than writing a load balancer from scratch.
Original Prepium guide · Written for Kubernetes v1.35 and checked against the official documentation on 6 October 2026. Use a disposable practice cluster. The HAProxy snippets follow the HAProxy 2.8 manual; run them on a throwaway load-balancer VM, not on a host that already fronts production traffic.
One stable API endpoint for several control planes: HA topologies and the load balancer
A highly available control plane is several kube-apiserver instances that all serve the same cluster. Clients cannot be pointed at three addresses, so a TCP load balancer gives them one, and kubeadm records it as controlPlaneEndpoint. Exam tasks focus on the plumbing: backends, health checks, and a kubeconfig that uses the shared endpoint without weakening TLS.
kubectl / kubelets ──→ LB 10.40.0.5:6443 (mode tcp, health checks)
├──→ api-a 10.40.0.11:6443 apiserver + etcd member
├──→ api-b 10.40.0.12:6443 apiserver + etcd member
└──→ api-c 10.40.0.13:6443 apiserver + etcd member (stacked etcd)
1. Stacked or external etcd?
In the stacked topology, each control-plane node also runs a local etcd member that talks only to that node's API server. It is kubeadm's default and needs the fewest machines, but one lost node takes an API server and an etcd member with it, so run at least three. In the external topology, etcd runs on its own hosts and every API server talks to the etcd cluster. A single failure hurts less, but you need twice the hosts (three control-plane plus three etcd). Both designs put the API servers behind a load balancer.
2. Pass TLS through, check every backend
Forward raw TCP so TLS still ends at the API servers, which hold the serving certificates. Our own HAProxy fragment:
frontend k8s_api_in
bind 10.40.0.5:6443
mode tcp
option tcplog
default_backend k8s_api_pool
backend k8s_api_pool
mode tcp
balance roundrobin
default-server inter 3000 fall 3 rise 2
server api-a 10.40.0.11:6443 check
server api-b 10.40.0.12:6443 check
server api-c 10.40.0.13:6443 check
Without check, HAProxy considers a server always available and keeps sending clients to a dead API server. With check and no application-level check configured, a server counts as healthy when a TCP connection succeeds. inter sets the probe interval in milliseconds, fall how many failures mark it down, rise how many successes bring it back.
3. Validate before you (re)start
haproxy -c -f /etc/haproxy/haproxy.cfg # exit status 0 means valid
systemctl restart haproxy
nc -zv -w 2 10.40.0.5 6443
-c only checks the files and exits before binding. The kubeadm HA guide reads the nc result like this: connection refused means the path works but no API server answers yet; a timeout means the balancer cannot reach the backends at all.
4. Point clients at the endpoint and prove it
Edit a copy of a kubeconfig and change only the cluster's server field, for example with kubectl config set-cluster <cluster> --server=https://10.40.0.5:6443 --kubeconfig ./lb.conf. Keep the CA data and client credentials; never add insecure-skip-tls-verify. TLS verification succeeds only if the address you use appears in the API server certificate's Subject Alternative Names. Check with openssl x509 -in /etc/kubernetes/pki/apiserver.crt -noout -text; extra names go in apiServer.certSANs.
kubectl --kubeconfig ./lb.conf get --raw='/readyz?verbose'
echo "show stat" | socat /run/lb-admin.sock stdio | cut -d, -f1,2,18
The second command reads HAProxy's runtime socket (a stats socket line in global); fields 1, 2 and 18 are proxy, server and status, which should be UP.
Common mistakes
- A frontend or backend left in
mode http, often inherited from adefaultssection: the balancer stops being a plain TCP pass-through, so TLS no longer flows untouched to the API servers. Setmode tcpon both. - A kubeconfig server address missing from the certificate SANs: kubectl fails certificate verification; the fix is the SAN list, not skipping verification.
- Editing the file but never restarting:
-cpasses, yet the running process keeps the old backends. - Only two stacked control planes: per the etcd FAQ a two-member cluster needs both members for quorum, so it tolerates zero failures.
| Example check | Evidence |
|---|---|
| Config is valid | haproxy -c -f exits 0 |
| Every backend is probed | Each server line carries check; status UP in show stat |
| Endpoint serves the API | get --raw='/readyz?verbose' through the balancer lists its checks as ok |
| TLS kept intact | The kubeconfig copy still has its CA data and no skip flag |
Cleanup: delete the practice VMs. References: Options for Highly Available Topology, Creating Highly Available Clusters with kubeadm, kubeadm HA considerations, HAProxy 2.8 configuration manual. Next, try kubeadm init from a config file.
Practice scope
This additional curriculum lab is available in single-task practice with the existing 12-minute limit. Its alternate scenario also appears in one of the two mixed curriculum mocks. The first three 17-task mocks keep their original task sets. Hints, an explained solution and requirement-by-requirement feedback are available in the lab.
Browse CKA practice tasks →