🎯 LESSON OBJECTIVE__HTMLTAG_68___
After completing this lesson, you will:
- ✅ Understand why Load Balancer is needed for K8s API Server in HA setup
- ✅ Install and configure keepalived for Virtual IP (VIP) failover
- ✅ Install and configure HAProxy for API server load balancing
- ✅ Configure health checks and automatic failover
- ✅ Test HA by simulating failures
- ✅ Compare keepalived+HAProxy vs kube-vip
PART 1: WHY DO YOU NEED LOAD BALANCER FOR API SERVER?
1.1. Issue with single API endpoint
graph LR
subgraph PROBLEM["❌ Hardcode master1"]
kubectl1["kubectl"] -->|":6443"| master1a["master1
kube-apiserver"]
master1a -.->|"DOWN!"| X["❌ Toàn bộ cluster
mất quản lý"]
end
subgraph SOLUTION["✅ Virtual IP"]
kubectl2["kubectl"] -->|":6443"| VIP["🔷 VIP
10.10.20.100"]
VIP --> m1["master1"] & m2["master2"] & m3["master3"]
m1 -.->|"DOWN"| VIP
end
style PROBLEM fill:#450a0a,stroke:#dc2626,color:#fca5a5
style SOLUTION fill:#052e16,stroke:#22c55e,color:#bbf7d0
style X fill:#dc2626,stroke:#fca5a5,color:#fff
style VIP fill:#1d4ed8,stroke:#60a5fa,color:#fff
1.2. Load Balancer Architecture
graph TD
VIP["🔷 VIP: 10.10.20.100:6443
keepalived floating IP"]
HAProxy["⚡ HAProxy
L4 TCP proxy · Listen :6443
Health check: /healthz"]
VIP --> HAProxy
HAProxy --> m1["master1:6443
kube-apiserver"] & m2["master2:6443
kube-apiserver"] & m3["master3:6443
kube-apiserver"]
subgraph FAILOVER["🔄 Failover"]
lb1["lb1
keepalived MASTER
holds VIP"]
lb2["lb2
keepalived BACKUP"]
lb1 -.->|"lb1 down → lb2 takes VIP
trong < 3 giây"| lb2
end
style VIP fill:#1d4ed8,stroke:#60a5fa,color:#fff
style HAProxy fill:#7c3aed,stroke:#a78bfa,color:#fff
style lb1 fill:#15803d,stroke:#22c55e,color:#fff
style lb2 fill:#78716c,stroke:#a8a29e,color:#fff
1.3. Select Architecture
| Option | Advantages | Disadvantages | Recommendation |
|---|---|---|---|
| Dedicated LB nodes | Isolation, simple, clear separation__HTMLTAG_115___ | Need 2 more servers__HTMLTAG_117___ | ✅ Production |
| HAProxy on masters | No additional servers needed | Resource contention, complexity | Lab/Small |
| kube-vip (DaemonSet) | No need external LB | Runs inside K8s → chicken-egg | Simple setups |
We will use dedicated LB nodes (lb1 + lb2) for production-grade setup.
PART 2: INSTALLATION AND CONFIGURATION OF HAPROXY
2.1. Install HAProxy
# Trên CẢ HAI lb1 và lb2:Cài đặt HAProxy (version 2.8+ LTS)
sudo apt install -y haproxy
Verify version
haproxy -v
Output: HAProxy version 2.8.x ...
2.2. HAProxy configuration
# Backup config gốc sudo cp /etc/haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg.bakTạo cấu hình mới
cat > /etc/haproxy/haproxy.cfg << 'EOF' #---------------------------------------------------------------------
Global settings
#--------------------------------------------------------------------- global log /dev/log local0 log /dev/log local1 notice chroot /var/lib/haproxy stats socket /run/haproxy/admin.sock mode 660 level admin stats timeout 30s user haproxy group haproxy daemon
# SSL/TLS settings ssl-default-bind-ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256 ssl-default-bind-ciphersuites TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384 ssl-default-bind-options ssl-min-ver TLSv1.2 # Tuning maxconn 50000 tune.ssl.default-dh-param 2048#---------------------------------------------------------------------
Defaults
#--------------------------------------------------------------------- defaults log global mode tcp # Layer 4 (TCP) mode cho K8s API option tcplog option dontlognull timeout connect 5000ms timeout client 50000ms timeout server 50000ms retries 3 default-server inter 10s downinter 5s rise 2 fall 3 slowstart 60s
#---------------------------------------------------------------------
Stats page (cho monitoring)
#--------------------------------------------------------------------- listen stats bind *:9000 mode http stats enable stats uri /stats stats realm HAProxy\ Statistics stats auth admin:SecureP@ssw0rd # Đổi password trong production! stats refresh 10s
#---------------------------------------------------------------------
Kubernetes API Server Frontend
#--------------------------------------------------------------------- frontend k8s-api bind *:6443 mode tcp option tcplog default_backend k8s-api-backend
#---------------------------------------------------------------------
Kubernetes API Server Backend
#--------------------------------------------------------------------- backend k8s-api-backend mode tcp option tcp-check balance roundrobin
# Health check: TCP connection check to port 6443 # (K8s API server responds to TCP SYN on this port) server master1 10.10.20.11:6443 check server master2 10.10.20.12:6443 check server master3 10.10.20.13:6443 check#---------------------------------------------------------------------
etcd (optional - cho external etcd client access)
#---------------------------------------------------------------------
frontend etcd
bind *:2379
mode tcp
default_backend etcd-backend
backend etcd-backend
mode tcp
balance roundrobin
server etcd1 10.10.20.11:2379 check
server etcd2 10.10.20.12:2379 check
server etcd3 10.10.20.13:2379 check
EOF
2.3. Advanced Health Check (HTTP)
TCP check only checks for open ports. To check the API server is really healthy, use HTTP health check:
# Thay thế backend section bằng advanced version:
backend k8s-api-backend
mode tcp
option tcp-check
balance roundrobin
# HTTP health check qua /healthz endpoint
option httpchk GET /healthz
http-check expect status 200
# default-server: check interval 3s, fall after 3 failures, rise after 2 success
default-server inter 3s fall 3 rise 2
server master1 10.10.20.11:6443 check check-ssl verify none
server master2 10.10.20.12:6443 check check-ssl verify none
server master3 10.10.20.13:6443 check check-ssl verify none
⚠️ Note: HTTP health check requires check-ssl verify none because the K8s API uses self-signed certificates. In production, you can configure CA cert for verification.
2.4. Start HAProxy
# Validate config sudo haproxy -c -f /etc/haproxy/haproxy.cfg # Output: Configuration file is validEnable và start
sudo systemctl enable haproxy sudo systemctl start haproxy
Verify
sudo systemctl status haproxy ss -tlnp | grep 6443
Output:
LISTEN 0 50000 *:6443 users:(("haproxy",pid=1234,...))
Kiểm tra stats page
curl http://localhost:9000/stats
Mở browser: http://lb1:9000/stats → login admin/SecureP@ssw0rd
PART 3: INSTALLATION AND CONFIGURATION KEEPALIVED
3.1. What is keepalived?
keepalived uses VRRP (Virtual Router Redundancy Protocol) to manage a Virtual IP between 2+ servers:
sequenceDiagram
participant lb1 as lb1 (MASTER, priority 101)
participant VIP as VIP 10.10.20.100
participant lb2 as lb2 (BACKUP, priority 100)
Note over lb1,lb2: 1. Ban đầu — lb1 giữ VIP
lb1->>VIP: Giữ VIP
loop Mỗi 1 giây
lb1->>lb2: VRRP Advertisement
lb2-->>lb1: Xác nhận alive
end
Note over lb1: 2. lb1 crash!
lb1--xlb2: ❌ Không gửi VRRP
Note over lb2: 3+ giây không nhận VRRP
lb2->>lb2: Promote → MASTER
lb2->>VIP: Gửi Gratuitous ARP
lb2->>VIP: Nhận VIP
Note over lb1,lb2: Failover hoàn tất ~3 giây
Note over lb1: 4. lb1 recover
lb1->>lb2: Lên lại, thấy lb2 MASTER
lb1->>lb1: Trở thành BACKUP (nopreempt)
3.2. Install keepalived
# Trên CẢ HAI lb1 và lb2: sudo apt install -y keepalivedEnable non-local IP binding (cho VIP)
echo "net.ipv4.ip_nonlocal_bind = 1" >> /etc/sysctl.d/99-keepalived.conf sudo sysctl -p /etc/sysctl.d/99-keepalived.conf
3.3. Keepalived configuration on lb1 (MASTER)
cat > /etc/keepalived/keepalived.conf << 'EOF' ! Configuration for keepalivedglobal_defs { router_id LB1 # Unique identifier for this node enable_script_security # Required cho script execution script_user root }
Health check script cho HAProxy
vrrp_script check_haproxy { script "/usr/bin/killall -0 haproxy" # Check if haproxy process exists interval 2 # Check every 2 seconds weight -30 # Reduce priority by 30 if check fails fall 3 # Mark DOWN after 3 consecutive failures rise 2 # Mark UP after 2 consecutive successes }
vrrp_instance K8S_API { state MASTER # Initial state: MASTER on lb1 interface eth1 # Network interface for VRRP (cluster network) virtual_router_id 51 # Same ID on both LBs (0-255) priority 101 # Higher priority = preferred MASTER advert_int 1 # VRRP advertisement interval (seconds) nopreempt # Không tự preempt khi recovered (recommended)
authentication { auth_type PASS auth_pass K8sHA2026 # Shared password (max 8 chars) } virtual_ipaddress { 10.10.20.100/24 dev eth1 label eth1:vip } track_script { check_haproxy # Track HAProxy health } # Notification scripts (optional) notify_master "/etc/keepalived/notify.sh MASTER" notify_backup "/etc/keepalived/notify.sh BACKUP" notify_fault "/etc/keepalived/notify.sh FAULT"
} EOF
3.4. Keepalived configuration on lb2 (BACKUP)
cat > /etc/keepalived/keepalived.conf << 'EOF' global_defs { router_id LB2 enable_script_security script_user root }vrrp_script check_haproxy { script "/usr/bin/killall -0 haproxy" interval 2 weight -30 fall 3 rise 2 }
vrrp_instance K8S_API { state BACKUP # Initial state: BACKUP on lb2 interface eth1 virtual_router_id 51 # PHẢI GIỐNG lb1 priority 100 # Thấp hơn lb1 (101) advert_int 1 nopreempt
authentication { auth_type PASS auth_pass K8sHA2026 # PHẢI GIỐNG lb1 } virtual_ipaddress { 10.10.20.100/24 dev eth1 label eth1:vip } track_script { check_haproxy } notify_master "/etc/keepalived/notify.sh MASTER" notify_backup "/etc/keepalived/notify.sh BACKUP" notify_fault "/etc/keepalived/notify.sh FAULT"
} EOF
3.5. Notification Script (Optional)
# Trên cả lb1 và lb2: cat > /etc/keepalived/notify.sh << 'SCRIPT' #!/bin/bash STATE=$1 DATETIME=$(date '+%Y-%m-%d %H:%M:%S') HOSTNAME=$(hostname)echo "${DATETIME} - ${HOSTNAME} transitioned to ${STATE}" >> /var/log/keepalived-state.log
Optional: Send notification (Slack, email, etc.)
curl -X POST -H 'Content-type: application/json' \
--data "{"text":"keepalived: ${HOSTNAME} → ${STATE}"}" \
https://hooks.slack.com/services/YOUR/WEBHOOK/URL
SCRIPT
chmod +x /etc/keepalived/notify.sh
3.6. Start keepalived
# Trên lb1 (start MASTER trước): sudo systemctl enable keepalived sudo systemctl start keepalivedTrên lb2 (start BACKUP sau):
sudo systemctl enable keepalived sudo systemctl start keepalived
Verify trên lb1 (MASTER):
ip addr show eth1
Output:
inet 10.10.20.9/24 brd 10.10.20.255 scope global eth1
inet 10.10.20.100/24 scope global secondary eth1:vip ← VIP!
Verify trên lb2 (BACKUP):
ip addr show eth1
Output:
inet 10.10.20.10/24 brd 10.10.20.255 scope global eth1
(Không có VIP)
Check keepalived state
sudo journalctl -u keepalived -f
PART 4: TESTING HA FAILOVER
4.1. Test 1: VIP Failover when lb1 down
# Terminal 1: Continuous ping tới VIP từ workstation ping 10.10.20.100Terminal 2: Tắt keepalived trên lb1 (MASTER)
ssh lb1 "sudo systemctl stop keepalived"
Kết quả expected trên Terminal 1:
64 bytes from 10.10.20.100: icmp_seq=45 ttl=64 time=0.4ms
64 bytes from 10.10.20.100: icmp_seq=46 ttl=64 time=0.4ms
Request timeout for icmp_seq 47 ← 1-3 packets lost
Request timeout for icmp_seq 48
64 bytes from 10.10.20.100: icmp_seq=49 ttl=64 time=0.5ms ← VIP on lb2 now
64 bytes from 10.10.20.100: icmp_seq=50 ttl=64 time=0.5ms
Verify VIP chuyển sang lb2:
ssh lb2 "ip addr show eth1 | grep 'inet '"
Output: inet 10.10.20.100/24 scope global secondary eth1:vip ✅
Restore lb1:
ssh lb1 "sudo systemctl start keepalived"
VIP stays on lb2 (nopreempt mode)
4.2. Test 2: HAProxy failure → keepalived demo
# Stop HAProxy trên lb1 (hiện đang MASTER): ssh lb1 "sudo systemctl stop haproxy"keepalived health check detects HAProxy down
→ Priority giảm từ 101 → 71 (101 - 30)
→ lb2 priority 100 > 71 → lb2 becomes MASTER
Check log:
ssh lb1 "sudo journalctl -u keepalived --since '1 min ago'"
Output:
VRRP_Script(check_haproxy) failed (exited with status 1)
VRRP_Instance(K8S_API) Changing effective priority from 101 to 71
VRRP_Instance(K8S_API) Received advert with higher priority 100
VRRP_Instance(K8S_API) Entering BACKUP STATE
Restore HAProxy:
ssh lb1 "sudo systemctl start haproxy"
4.3. Test 3: API Server Backend Failover
# Sau khi K8s cluster đã chạy (Bài 6-7):Continuous API call qua VIP
while true; do curl -sk https://10.10.20.100:6443/healthz && echo " OK $(date)" sleep 1 done
Tắt kube-apiserver trên master1:
ssh master1 "sudo crictl stop $(sudo crictl ps --name kube-apiserver -q)"
Output expected:
ok OK Wed Apr 02 10:00:01 2026
ok OK Wed Apr 02 10:00:02 2026 ← HAProxy routes to master2/3
ok OK Wed Apr 02 10:00:03 2026 ← No interruption!
Kiểm tra HAProxy stats:
curl -s "http://lb1:9000/stats;csv" | grep k8s-api-backend
master1 → DOWN, master2/3 → UP
PART 5: ALTERNATIVE — kube-vip
5.1. What is kube-vip?
kube-vip runs as static pod on control plane nodes, combining VIP + load balancing in 1 component:
keepalived + HAProxy (external):
├── 2 dedicated LB servers
├── keepalived manages VIP
└── HAProxy load balances → API servers
kube-vip (internal):
├── Chạy như DaemonSet/static pod trên masters
├── Leader election qua Raft hoặc ARP
├── Leader giữ VIP + local LB
└── Không cần external servers
5.2. Detailed comparison
| Criteria | keepalived + HAProxy | kube-vip |
|---|---|---|
| Extra servers | Need 2 LB servers | No need |
| Complexity | 2 components need to be managed | 1 component |
| Independence | ✅ Independent of K8s cluster | ❌ Runs in K8s (chicken-egg) |
| Chicken-egg problem | ✅ None | ⚠️ need to init before K8s |
| Health checks | ✅ Advanced (HTTP, TCP, script) | Basic |
| Monitoring | ✅ HAProxy stats, Prometheus | Limited |
| Production proven | ✅ 20+ years | Newer, less battle-tested |
| Lab/Development | Overkill | ✅ Perfect |
💡 Recommendation:
- Production: keepalived + HAProxy (mature, independent, observable)
- Lab/Small: kube-vip (simpler, no extra servers)
5.3. kube-vip Quick Setup (Reference)
# Tạo kube-vip manifest trước khi kubeadm init export VIP=10.10.20.100 export INTERFACE=eth1 export KVVERSION=$(curl -sL https://api.github.com/repos/kube-vip/kube-vip/releases | jq -r ".[0].name")Generate static pod manifest
ctr image pull ghcr.io/kube-vip/kube-vip:$KVVERSION ctr run --rm --net-host ghcr.io/kube-vip/kube-vip:$KVVERSION vip /kube-vip
manifest pod
--interface $INTERFACE
--address $VIP
--controlplane
--arp
--leaderElection | tee /etc/kubernetes/manifests/kube-vip.yaml
PART 6: PRODUCTION CONSIDERATIONS
6.1. HAProxy Production Tuning
# Thêm vào haproxy.cfg global section: global # Tăng max connections maxconn 100000# Sử dụng nhiều CPU cores nbthread 4 # Số threads = số CPU cores # Enable stats socket cho runtime API stats socket /run/haproxy/admin.sock mode 660 level admin expose-fd listeners # Logging chi tiết log-send-hostname
6.2. Monitoring HAProxy with Prometheus
# HAProxy built-in Prometheus exporter (HAProxy 2.4+): # Thêm vào haproxy.cfg: frontend prometheus bind *:8405 mode http http-request use-service prometheus-exporter if { path /metrics } no logVerify:
curl http://lb1:8405/metrics | head -20
Output:
# HELP haproxy_backend_status Current status of the service
haproxy_backend_status{backend="k8s-api-backend",server="master1"} 1
6.3. Monitoring keepalived
# keepalived exports SNMP metrics # Hoặc check via logs: sudo journalctl -u keepalived -fCustom health check cho keepalived process:
systemctl is-active keepalived && echo "RUNNING" || echo "DOWN"
Check VRRP state:
cat /var/log/keepalived-state.log
💡 KEY TAKEAWAYS
- VIP is critical for K8s HA — all components connect via VIP, no hardcode master IP
- keepalived manage VIP failover via VRRP protocol, failover < 3 giây
- HAProxy load balance TCP traffic to healthy API servers with health checks
- nopreempt mode avoids unnecessary VIP flapping when MASTER recover
- Health check script in keepalived to ensure VIP is only on the node with HAProxy healthy
- Test failover BEFORE deploying K8s: turn off LB, turn off HAProxy, verify VIP migration
🎯 EXERCISE
Exercise 1: Deploy HAProxy + keepalived
- Install HAProxy + keepalived on lb1 and lb2 following instructions
- Verify VIP active on lb1
- Access HAProxy stats page
Exercise 2: Failover Testing
- Test 1: Stop keepalived on lb1, verify VIP moves to lb2
- Test 2: Stop HAProxy on lb1, verify automatic demotion
- Test 3: Restart both, verify correct state
- Measuring failover time with continuous ping
Exercise 3: Advanced
- Add Prometheus exporter for HAProxy
- Write a notification script that sends an alert to Slack when failover occurs__HTMLTAG_385___
- Configure HAProxy logging details in a separate file
📚 NEXT POST
In Lesson 5: Installing containerd and kubeadm on all nodes, we will install the container runtime (containerd) and kubeadm tools, getting ready for K8s HA cluster initialization.