Deploying an enterprise-grade, highly available (HA) Kubernetes cluster requires robust load balancing across control plane nodes, automated infrastructure provisioning, and an eBPF-powered Container Network Interface (CNI). In this multi-part series, we walk through building a production-ready Kubernetes cluster from scratch using Kubespray, Ubuntu 24.04 LTS, HAProxy + Keepalived for API server load balancing, and Cilium CNI.
This article is Part 1 of our deployment series, focusing on architecture topology, load balancer setup, Kubespray inventory generation using bash arrays, Cilium CNI configuration, and cluster initialization.
Cluster Architecture & Node Topology
Our cluster layout consists of 8 dedicated virtual or physical nodes running Ubuntu 24.04 LTS:
- Load Balancers / VIP (2 Nodes):
lb-01(192.168.10.11),lb-02(192.168.10.12) sharing Virtual IP (VIP):192.168.10.10 - Kubernetes Control Plane / Masters (3 Nodes):
k8s-master-1(192.168.10.21),k8s-master-2(192.168.10.22),k8s-master-3(192.168.10.23) - Kubernetes Worker Nodes (3 Nodes):
k8s-worker-1(192.168.10.31),k8s-worker-2(192.168.10.32),k8s-worker-3(192.168.10.33) - Ansible Control Node:
ansible-deploy(192.168.10.5)
Step 1: System Preparation on All Nodes (Ubuntu 24.04 LTS)
Execute prerequisite configuration across all 8 nodes:
# Update OS packages and disable swap
sudo apt update && sudo apt upgrade -y
sudo swapoff -a
sudo sed -i '/ swap / s/^/#/' /etc/fstab
# Load required kernel modules for Kubernetes & Containerd
cat <<EOF | sudo tee /etc/modules-load.d/k8s.conf
overlay
br_netfilter
EOF
sudo modprobe overlay
sudo modprobe br_netfilter
# Configure sysctl networking parameters
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
Step 2: Deploy HAProxy + Keepalived Load Balancers
To prevent single points of failure for the Kubernetes API Server, configure Keepalived (VRRP VIP) and HAProxy on lb-01 and lb-02.
1. Install HAProxy and Keepalived:
sudo apt install -y haproxy keepalived
2. Configure Keepalived on lb-01 (Master LB):
cat <<EOF | sudo tee /etc/keepalived/keepalived.conf
vrrp_script check_haproxy {
script "killall -0 haproxy"
interval 2
weight 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 101
advert_int 1
authentication {
auth_type PASS
auth_pass K8sSecretPass
}
virtual_ipaddress {
192.168.10.10/24
}
track_script {
check_haproxy
}
}
EOF
3. Configure Keepalived on lb-02 (Backup LB):
Set state BACKUP and priority 100 on lb-02.
4. Configure HAProxy on Both LB Nodes:
cat <<EOF | sudo tee /etc/haproxy/haproxy.cfg
frontend k8s-api
bind 192.168.10.10:6443
mode tcp
option tcplog
default_backend k8s-masters
backend k8s-masters
mode tcp
option tcp-check
balance roundrobin
server k8s-master-1 192.168.10.21:6443 check fall 3 rise 2
server k8s-master-2 192.168.10.22:6443 check fall 3 rise 2
server k8s-master-3 192.168.10.23:6443 check fall 3 rise 2
EOF
sudo systemctl restart keepalived haproxy
sudo systemctl enable keepalived haproxy
Step 3: Kubespray Setup & Inventory Generation via Bash Array
On your Ansible control node (ansible-deploy), set up Kubespray and generate your inventory file using the bash array method:
# Clone Kubespray repository
git clone https://github.com/kubernetes-sigs/kubespray.git
cd kubespray
# Create Python Virtual Environment
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
# Copy sample inventory directory
cp -rfp inventory/sample inventory/mycluster
# Declare node IPs array and generate hosts.yaml inventory
declare -a IPS=(192.168.10.21 192.168.10.22 192.168.10.23 192.168.10.31 192.168.10.32 192.168.10.33)
CONFIG_FILE=inventory/mycluster/hosts.yaml python3 contrib/inventory_builder/inventory.py ${IPS[@]}
Update inventory/mycluster/hosts.yaml to assign 3 control plane nodes and 3 worker nodes:
all:
hosts:
node1:
ansible_host: 192.168.10.21
ip: 192.168.10.21
access_ip: 192.168.10.21
node2:
ansible_host: 192.168.10.22
ip: 192.168.10.22
access_ip: 192.168.10.22
node3:
ansible_host: 192.168.10.23
ip: 192.168.10.23
access_ip: 192.168.10.23
node4:
ansible_host: 192.168.10.31
ip: 192.168.10.31
access_ip: 192.168.10.31
node5:
ansible_host: 192.168.10.32
ip: 192.168.10.32
access_ip: 192.168.10.32
node6:
ansible_host: 192.168.10.33
ip: 192.168.10.33
access_ip: 192.168.10.33
children:
kube_control_plane:
hosts:
node1:
node2:
node3:
kube_node:
hosts:
node4:
node5:
node6:
etcd:
hosts:
node1:
node2:
node3:
k8s_cluster:
children:
kube_control_plane:
kube_node:
Step 4: Configure External Load Balancer & Cilium CNI
Edit Kubespray cluster variables to route API traffic through our HAProxy VIP and enable Cilium CNI.
1. External Load Balancer Settings (inventory/mycluster/group_vars/all/all.yml):
apiserver_loadbalancer_domain_name: "192.168.10.10"
loadbalancer_apiserver:
address: 192.168.10.10
port: 6443
2. Cilium CNI Settings (inventory/mycluster/group_vars/k8s_cluster/k8s-cluster.yml):
kube_network_plugin: cilium
cilium_enable_netpol: true
cilium_tunnel_mode: "vxlan"
cilium_enable_prometheus: true
Step 5: Run Kubespray Playbook & Verify Cluster
Execute the Kubespray Ansible playbook to build the cluster:
ansible-playbook -i inventory/mycluster/hosts.yaml --become --become-user=root cluster.yml
After deployment completes, verify cluster health from node1 (master):
sudo cp /etc/kubernetes/admin.conf ~/.kube/config
kubectl get nodes -o wide
kubectl get pods -n kube-system
cilium status
Conclusion & What’s Next in Part 2
You now have a production-ready, highly available Kubernetes cluster running on Ubuntu 24.04 LTS with 3 control plane nodes, 3 worker nodes, HAProxy/Keepalived load balancing, and eBPF-powered Cilium CNI.
In Part 2 of this series, we will focus on advanced operational services:
- Cilium LB IPAM: Setting up dedicated subnet IP pools for Kubernetes LoadBalancer services
- Cilium Gateway API & HTTPRoute: Configuring eBPF-native ingress routing and HTTPRoute resources
- Longhorn Storage: Deploying persistent, cloud-native distributed block storage
- Cert-Manager & Let’s Encrypt ClusterIssuer: Automating SSL/TLS certificate issuance and lifecycle management
