HAProxy、Keepalived 在 Ocatvia 的應用實現與分析
目錄
文章目錄
Amphora
建立一個 loadbalancer 需要佔用一到兩臺 Amphora Instance 作為 “負載均衡器“ 的執行載體,實際提供高可用負載均衡底層支撐是 HAProxy & Keepalived。
- HAProxy:L4-L7 負載均衡器
- Keepalived:Linux 體系的高可用解決方案
不過 Amphora 並非是一開始就執行著 haproxy 和 keepalived 服務程序的,而是在需要執行它們的時候才會被 amphora-agent 啟動。
啟動 keepalived
keepalived 服務程序在 Amphora 被 loadbalancer 納管後啟動,TASK:AmphoraVRRPStart 就是啟動服務的邏輯實現,而且從 UML 圖可見,只有當 loadbalancer_topology = ACTIVE_STANDBY
時才會載入 keepalived,提供高可用服務。
# file: /opt/rocky/octavia/octavia/controller/worker/tasks/amphora_driver_tasks.py
class AmphoraVRRPStart(BaseAmphoraTask) :
"""Task to start keepalived of all amphorae of a LB."""
def execute(self, loadbalancer):
self.amphora_driver.start_vrrp_service(loadbalancer)
LOG.debug("Started VRRP of loadbalancer %s amphorae",
loadbalancer.id)
進過一系列呼叫後最終由 AmphoraAPIClient 發出 PUT vrrp/start
# file: /opt/rocky/octavia/octavia/amphorae/backends/agent/api_server/keepalived.py
def manager_keepalived_service(self, action):
action = action.lower()
if action not in [consts.AMP_ACTION_START,
consts.AMP_ACTION_STOP,
consts.AMP_ACTION_RELOAD]:
return webob.Response(json=dict(
message='Invalid Request',
details="Unknown action: {0}".format(action)), status=400)
if action == consts.AMP_ACTION_START:
keepalived_pid_path = util.keepalived_pid_path()
try:
# Is there a pid file for keepalived?
with open(keepalived_pid_path, 'r') as pid_file:
pid = int(pid_file.readline())
os.kill(pid, 0)
# If we got here, it means the keepalived process is running.
# We should reload it instead of trying to start it again.
action = consts.AMP_ACTION_RELOAD
except (IOError, OSError):
pass
cmd = ("/usr/sbin/service octavia-keepalived {action}".format(
action=action))
try:
subprocess.check_output(cmd.split(), stderr=subprocess.STDOUT)
except subprocess.CalledProcessError as e:
LOG.debug('Failed to %s octavia-keepalived service: %s %s',
action, e, e.output)
return webob.Response(json=dict(
message="Failed to {0} octavia-keepalived service".format(
action), details=e.output), status=500)
return webob.Response(
json=dict(message='OK',
details='keepalived {action}ed'.format(action=action)),
status=202)
上述程式碼可知,Amphora 中的 amphora-agent 是通過執行 CLI /usr/sbin/service octavia-keepalived start
來啟動 keepalived 的。
# file: /usr/lib/systemd/system/octavia-keepalived.service
[Unit]
Description=Keepalive Daemon (LVS and VRRP)
After=network-online.target .service
Wants=network-online.target
Requires=.service
[Service]
# Force context as we start keepalived under "ip netns exec"
SELinuxContext=system_u:system_r:keepalived_t:s0
Type=forking
KillMode=process
ExecStart=/sbin/ip netns exec amphora-haproxy /usr/sbin/keepalived -D -d -f /var/lib/octavia/vrrp/octavia-keepalived.conf -p /var/lib/octavia/vrrp/octavia-keepalived.pid
ExecReload=/bin/kill -HUP $MAINPID
PIDFile=/var/lib/octavia/vrrp/octavia-keepalived.pid
[Install]
WantedBy=multi-user.target
octavia-keepalived.service 定義了 keepalived 的啟動指令碼、配置檔案以 PID 檔案的路徑。配置檔案的內容如下:
# file: /var/lib/octavia/vrrp/octavia-keepalived.conf
vrrp_script check_script {
script /var/lib/octavia/vrrp/check_script.sh
interval 5
fall 2
rise 2
}
vrrp_instance 01197be798d5440da846cd70f52dc503 { # VRRP instance name is loadbalancer UUID
state MASTER # Master router
interface eth1 # VRRP IP device
virtual_router_id 1 # VRID
priority 100
nopreempt
garp_master_refresh 5
garp_master_refresh_repeat 2
advert_int 1
authentication {
auth_type PASS
auth_pass b76d77e
}
unicast_src_ip 172.16.1.3 # VRRP IP
unicast_peer {
172.16.1.7 # Backup router VRRP IP
}
virtual_ipaddress {
172.16.1.10 # VIP address
}
track_script {
check_script
}
}
從配置檔案可知 keepalived 使用 NIC eth1 作為 VRRP IP 和 VIP 的 interface,但是直接在 Amphora 執行 ifconfig 是看不見 eth1 的。因為 Amphora 將 VIP 設定到了 namespace amphora-haproxy 中:
[email protected]:~# ip netns
amphora-haproxy
[email protected]:~# ip netns exec amphora-haproxy bash
[email protected]:~# ifconfig
eth1 Link encap:Ethernet HWaddr fa:16:3e:f4:69:4b
inet addr:172.16.1.3 Bcast:172.16.1.255 Mask:255.255.255.0
inet6 addr: fe80::f816:3eff:fef4:694b/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:0 (0.0 B) TX bytes:648 (648.0 B)
eth1:0 Link encap:Ethernet HWaddr fa:16:3e:f4:69:4b
inet addr:172.16.1.10 Bcast:172.16.1.255 Mask:255.255.255.0
UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1
除了 start 之外,view_func:manage_service_vrrp 還支援 stop 和 reload 操作。而 keepalived 配置檔案的更新則交由 view_func:upload_keepalived_config 來完成。
而在 octavia-worker 端,更新 keepalived 配置檔案的邏輯實現在 Task:AmphoraVRRPUpdate,AmphoraVRRPUpdate 在 AmphoraVRRPStart 之前執行,配置檔案通渲染 Jinja 模板的方式生成。
啟動 haproxy
我們知道 HAProxy 負責監聽 frontend 的請求,然後根據不同的條件和 ACL 規則將請求分發到 backend,這一特性正是 Octavia Listener 物件的定義。所以,當為 loadbalancer 建立 listener 時才會啟動 haproxy 服務程序。
從 UML 可知,執行指令 openstack loadbalancer listener create --protocol HTTP --protocol-port 8080 lb-1
建立 Listener 時會執行到 Task:ListenersUpdate,由 ListenersUpdate 完成了 haproxy 配置檔案的 Upload 和 haproxy 服務程序的 Reload。
配置檔案 /var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557/haproxy.cfg,其中 1385d3c4-615e-4a92-aea1-c4fa51a75557 為 Listener UUID:
# Configuration for loadbalancer 01197be7-98d5-440d-a846-cd70f52dc503
global
daemon
user nobody
log /dev/log local0
log /dev/log local1 notice
stats socket /var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557.sock mode 0666 level user
maxconn 1000000
defaults
log global
retries 3
option redispatch
peers 1385d3c4615e4a92aea1c4fa51a75557_peers
peer l_Ustq0qE-h-_Q1dlXLXBAiWR8U 172.16.1.7:1025
peer O08zAgUhIv9TEXhyYZf2iHdxOkA 172.16.1.3:1025
frontend 1385d3c4-615e-4a92-aea1-c4fa51a75557
option httplog
maxconn 1000000
bind 172.16.1.10:8080
mode http
timeout client 50000
因為此時的 Listener 只指定了監聽的協議和埠,所以 frontend
section 也設定了相應的 bind 172.16.1.10:8080
和 mode http
。
服務程序:systemctl status haproxy-1385d3c4-615e-4a92-aea1-c4fa51a75557.service 其中 1385d3c4-615e-4a92-aea1-c4fa51a75557 為 Listener UUID:
# file: /usr/lib/systemd/system/haproxy-1385d3c4-615e-4a92-aea1-c4fa51a75557.service
[Unit]
Description=HAProxy Load Balancer
After=network.target syslog.service amphora-netns.service
Before=octavia-keepalived.service
Wants=syslog.service
Requires=amphora-netns.service
[Service]
# Force context as we start haproxy under "ip netns exec"
SELinuxContext=system_u:system_r:haproxy_t:s0
Environment="CONFIG=/var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557/haproxy.cfg" "USERCONFIG=/var/lib/octavia/haproxy-default-user-group.conf" "PIDFILE=/var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557/1385d3c4-615e-4a92-aea1-c4fa51a75557.pid"
ExecStartPre=/usr/sbin/haproxy -f $CONFIG -f $USERCONFIG -c -q -L O08zAgUhIv9TEXhyYZf2iHdxOkA
ExecReload=/usr/sbin/haproxy -c -f $CONFIG -f $USERCONFIG -L O08zAgUhIv9TEXhyYZf2iHdxOkA
ExecReload=/bin/kill -USR2 $MAINPID
ExecStart=/sbin/ip netns exec amphora-haproxy /usr/sbin/haproxy-systemd-wrapper -f $CONFIG -f $USERCONFIG -p $PIDFILE -L O08zAgUhIv9TEXhyYZf2iHdxOkA
KillMode=mixed
Restart=always
LimitNOFILE=2097152
[Install]
WantedBy=multi-user.target
從服務程序配置可以看出實際啟動的服務為 /usr/sbin/haproxy-systemd-wrapper
,它是執行在 namespace amphora-haproxy 中的,該指令碼做的事情可以從日誌瞭解到:
Nov 15 10:12:01 amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77 ip[13206]: haproxy-systemd-wrapper: executing /usr/sbin/haproxy -f /var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557/haproxy.cfg -f /var/lib/octavia/haproxy-default-user-group.conf -p /var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557/1385d3c4-615e-4a92-aea1-c4fa51a75557.pid -L O08zAgUhIv9TEXhyYZf2iHdxOkA -Ds
就是呼叫了 /usr/sbin/haproxy
指令而已。
最後
本篇介紹了 Octavia 是如何將 HAProxy、Keepalived 等常用的負載均衡解決方案封裝到 Amphora Instance 的,同時也介紹了 Create Listener 所需要處理的事情。需要注意的是 HAProxy 的配置檔案會隨著 Listener、Pool、Member、L7policy、L7rule、health-monitor 等物件的變更而變更,這些我們以後再作討論。還有一點補充的就是建立 Listener 會執行 Task:UpdateVIP,這是因為 Lisenter 含有的協議及埠資訊都需要被更新到 VIP 的安全組規則中,否則 Listener 要如何監聽得到傳輸層的資料包呢?