This repository contains an Ansible playbook that provisions a highly-available load balancer for a Kubernetes control plane (or any TCP service) using HAProxy and Keepalived. The setup provides a virtual IP (VIP) managed via VRRP and forwards traffic (default: TCP 6443) to one or more backend API servers.
Tested layouts in the inventory show a typical Kubernetes API setup where HAProxy fronts multiple control-plane nodes on port 6443, and Keepalived advertises a shared VIP on the selected interface.
- Installs and configures HAProxy with a TCP frontend and multiple backends.
- Installs and configures Keepalived for VRRP to provide a floating Virtual IP (VIP) between two or more load balancer nodes.
- Optionally hardens the system by disabling unneeded services and priming a package cache.
- Uses group/host vars in the
inventory*.yamlto control ports, VIP, interface, and HAProxy backends.
playbook.yml # Entry playbook: disable_services, package_cache, haproxy, keepalived
inventory.yaml # Example inventory (RHEL/Debian style IPs & vars)
roles/
haproxy/
defaults/main.yml # Defaults (mode, options, template vars)
tasks/main.yml # Installs and enables haproxy, renders config
templates/haproxy.cfg.j2 # HAProxy configuration template
keepalived/
defaults/main.yml # Keepalived defaults (state, priority, interface, VIP, VRID, auth)
tasks/main.yml # Installs and enables keepalived, renders config
templates/keepalived.conf.j2
disable_services/
defaults/main.yml # List of unwanted services (per OS family)
tasks/main.yml # Stops & disables them
package_cache/ # (If present) Preps package cache / mirrors
run.sh, # Convenience wrapper for Ansible execution
lint.sh # Ansible lint helper
Note: Exact role contents may vary slightly; see the files in
roles/for the authoritative configuration knobs.
- Control machine: Python 3 and Ansible >= 2.14 (recommended).
- Managed hosts: Two or more Linux servers (e.g., Ubuntu/Debian/RHEL) with:
- SSH access for an Ansible user with
become: true(sudo) privileges. - Network interface name for the VIP (e.g.,
eth0,enp1s0).
- SSH access for an Ansible user with
- Open firewall for:
- VRRP traffic (protocol 112) between load balancer nodes.
- VIP frontend port (default
6443) from clients to the load balancer VIP. - Backend node ports (e.g., control plane nodes on
6443).
-
Edit inventory:
inventory.yaml
Key variables per host (examples taken from
inventory.yaml):all: children: loadbalancers: hosts: 192.168.195.10: keepalived_state: MASTER # MASTER or BACKUP keepalived_priority: 101 # Higher on MASTER keepalived_interface: eth0 # Interface to bind VIP keepalived_virtual_ip: 192.168.222.2/17 keepalived_virtual_router_id: 51 # Same across the VRRP group haproxy_frontend_bind: "*:6443" haproxy_frontend_options: - "option tcplog" haproxy_backend_name: talos-prod1-apiserver haproxy_backend_mode: "tcp" haproxy_backend_balance: "leastconn" haproxy_backend_options: - "option tcp-check" haproxy_backend_servers: - name: talos-prod1-apiserver-0 ip: 192.168.178.245 port: 6443 params: "check check-ssl verify none" # add more servers as needed
Adjust
keepalived_*variables to match your network. Ensure all nodes in the VRRP group use the samekeepalived_virtual_router_id,keepalived_virtual_ip, andkeepalived_auth_pass(if used). -
Run the playbook from the repo root:
ansible-playbook -i inventory.yaml playbook.yml
-
(Optional) Use helper scripts:
./run.sh # wrapper around ansible-playbook
Defined in roles/keepalived/defaults/main.yml and/or per-host in inventory:
keepalived_state:MASTER|BACKUPkeepalived_priority: integer; higher = preferred master (e.g., MASTER=101, BACKUP=100)keepalived_interface: interface name (e.g.,eth0)keepalived_virtual_router_id:1..255; must match on all nodeskeepalived_virtual_ip: CIDR VIP (e.g.,192.168.1.100/24)keepalived_auth_pass: shared password (if using auth)
Common vars (from inventory and role defaults):
haproxy_frontend_bind: e.g.,"*:6443"haproxy_frontend_mode:"tcp"(default for k8s API)haproxy_frontend_options: list of strings (e.g.,"option tcplog")haproxy_backend_name: name for the backend (e.g.,talos-prod1-apiserver)haproxy_backend_mode:"tcp"haproxy_backend_balance: e.g.,"roundrobin","leastconn"haproxy_backend_options: list (e.g.,"option tcp-check")haproxy_backend_servers: list of backends- name: apiserver-0 ip: 10.0.0.10 port: 6443 params: "check check-ssl verify none"
- HAProxy
- Config:
/etc/haproxy/haproxy.cfg(rendered fromroles/haproxy/templates/haproxy.cfg.j2) - Service:
systemctl status haproxy
- Config:
- Keepalived
- Config:
/etc/keepalived/keepalived.conf(rendered fromroles/keepalived/templates/keepalived.conf.j2) - Service:
systemctl status keepalived
- Config:
- VIP
- Check VIP on the interface:
ip addr show dev <iface>
- Check VIP on the interface:
After the playbook completes on all load balancer nodes:
# On any LB node:
sudo systemctl status haproxy keepalived
# VIP should be present on MASTER:
ip addr show dev <iface> | grep <VIP>
# From a client machine:
nc -vz <VIP> 6443
# or
openssl s_client -connect <VIP>:6443 -servername kubernetes </dev/null
# Kubernetes API (if used):
curl -k https://<VIP>:6443/healthzIf you configured multiple backends, stopping one backend should not disrupt access via the VIP.
The role disable_services stops and disables a predefined list of non-essential services (depends on OS). Review and modify the list in roles/disable_services/defaults/main.yml before running in production environments.
- Ensure L2 connectivity and VRRP (protocol 112) is allowed between the load balancer nodes.
- All nodes in one VRRP group must share:
keepalived_virtual_router_id,keepalived_virtual_ip, and authentication settings. - Firewalls must allow the VIP port to reach HAProxy and allow HAProxy to reach each backend server port.
- If the interface name differs (e.g.,
enp1s0vseth0), setkeepalived_interfaceaccordingly in the inventory.
If you plan to open-source this, add a LICENSE file. No license file was detected in this archive.