學習 Neutron 系列文章:
(2)Neutron OpenvSwitch + VLAN 虛擬網絡
(3)Neutron OpenvSwitch + GRE/VxLAN 虛擬網絡
(4)Neutron OVS OpenFlow 流表 和 L2 Population
(9)Neutron FWaas 和 Nova Security Group
(10)Neutron VPNaas
(11)Neutron DVR
(12)Neutron VRRP
1. 基礎知識
1.1 負載均衡的概念
負載均衡(Load Balancing)是將來訪的網絡流量在運行相同應用的多個服務器之間進行分發的一種核心網絡服務。它的功能由負載均衡器(load balancer)提供。負載均衡器可以是一個硬件設備,也可以由軟件實現。它充當反向代理,在多個服務器之間分發網絡或者應用流量。它常用來增加應用的訪問容量(並發用戶數)和可靠性,它也會通過降低服務器的負載來提高應用的總體性能。
(1)負載均衡器的分類
負載均衡器一般可以分為兩類:第4層負載均衡器和第7層負載均衡器。
第 4 層負載平衡器:基於網絡和傳輸層協議(IP,TCP,FTP,UDP等)來均衡負載。
第7層的負載均衡器:基於應用層協議比如 HTTP, SMTP, SNMP, FTP, Telnet 等均衡負載。比如對 HTTP 來說,第七層的負載均衡器能根據應用的特定數據比如 HTTP 頭,cookies 或者應用消息中的數據來做進一步的請求分發。
(2)負載分發算法
兩種類型的負載均衡器都能接收請求,然后根據特定的算法將請求分發到特定的服務器。一些行業標准的算法是:
- 輪詢 (Round robin):輪流分發到各個(活動)服務器
- 加權輪循 (Weighted round robin):每個服務器有一定的加權(weight),輪詢時考慮加權。
- 最少連接 (Least connections):轉發到有最少連接數的服務器
- 最少響應時間 (Least response time):轉發到響應時間最短的服務器
(3)可靠性和可用性
負載均衡器通過監控應用的健康狀態來確保可靠性和可用性,並且只轉發請求到能及時做出響應的服務和應用。
(4)Session persistence (會話保持)
會話保持表示在一個會話期間,轉發一個用戶的請求到同一個后端服務器。這對購物車或者付款類的請求非常重要。 常用的方法包括:
- Source IP:相同來源的請求轉發到同一個服務器
- HTTP Cookie:該模式下,loadbalancer 為客戶端的第一次連接生成 cookie,后續攜帶該 cookie 的請求會被某個 member 處理
- APP Cookie:該模式下,依靠后端應用服務器生成的 cookie 決定被某個 member 處理
(5)常見的開源軟件負載均衡器
- HAProxy
-
Linux Virtual Servers (LVS) - 包括在許多Linux發行版中的簡單快速的4層負載均衡軟件
-
Nginx - 一個快速可靠的web服務器也能當作代理和負載均衡器使用。它常常和 HAProxy一起用於緩存和壓縮。
1.2 三種部署模式
1.2.1 tow-arms (雙臂)模式
也稱為 in-line 模式,或者 bridge mode 模式,或者 transparent mode。此時,所以前端訪問后端的網絡都需要經過 LB,它本身也成為了一種 router。可見,此時的 LB 需要兩個網卡,分別連接前端和后端。
來看一個具體的 IP 地址配置示例:
1.2.2 One-arm (單臂)模式
該模式中,LB 不處於前端和后端的通道上,而是在旁邊。此時,LB 只使用一塊網卡,而且該網卡和后端服務器處於同一個二層網絡中。該 LB 的網卡將會被分配一個 virtual load-balanced IP (VIP)。需要注意的是,此時的 LB 需要對進來的網絡包做 Source 和 Dest NAT 然后再交給某個后端服務器,使得后端服務器返回的網絡包將會到達 LB 而不是直接到達前端,再由 LB 轉交給前端。因為使用了 SNAT,因此后端看不到網絡包的源IP,這在某些需要審計功能的情況下可能無法滿足要求。
來看一個具體的 IP 地址配置示例:
1.2.3 Direct Server Response 模式
這種模式下,LB 只接收進來的網絡包,轉給后端服務器后,后端服務器直接將返回包發回給客戶端。這種模式的好處是,可以提高網絡的吞吐量,壞處是配置較為復雜。
注:以上信息來自 Basic Load-Balancer Scenarios Explained。
1.3 High Availability Proxy(HAProxy)
HAProxy 是個著名的開源的軟件 TCP(四層)/HTTP(七層) 負載均衡器和代理(proxy)軟件,可以運行在 Linux,Solaris 和 FreeBSD 等系統上。它最常用的用途是通過在多個服務器(比如 web服務器,應用,數據庫等)之間分發負載來改善一個服務器系統的性能和可靠性。目前,它已經被許多大公司采用,包括GitHub, Imgur, Instagram, and Twitter 等。它類似 Nginx 的,采用了單進程和事件驅動模型;它使用的內存量低而且穩定,能夠處理大量並發請求。這篇文章 講述了怎樣使用 HAProxy。
主要概念:
- Frontend:定義請求如何被轉發到 backend。
- Backend:一組接收轉發的請求的服務器。其定義包括分發算法和一組服務器的地址和端口。
支持的分發算法:
- Round robin:平均的將網絡流量分發到多個 member。
- Source IP:從某一個特定 IP 發來的網絡請求,總是發到特定的 member。
- Least connections:將收到的網絡請求,發給當前負載均衡池中連接數最少的 member。如果所有的 member 連接數一樣,則遵循 Round robin 的方式。
一個 HAProxy + Keepalived 配置實例:
Health Check(健康檢查):
HAProxy 使用 Health Check 來確定一個 backend server 能不能接收轉發的請求。這避免了在服務器不可用時需要手工刪除它。默認的 Health Check 是試着去和一個server 建立一個 TCP 連接,比如,檢查該 backend server 是否在配置的 IP 和 端口上監聽。你可以指定 health check 方法,包括 tcp,http,ping 等。
當一個 backend server 檢查失敗時,它就無法接受請求了,它就會自動被禁用,請求也不會被轉發到該服務器上,直到它重新變為可用。如果一個 backend 內的所有服務器都 health check 失敗,其對應的服務就會變得不可用。
配置文件:
HAProxy 的核心在於其配置文件(etc/haproxy/haproxy.cfg)。Neutron 的 LBaas 其實也就是生成了該配置文件,然后由 HAProxy 去執行。
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 # Default SSL material locations ca-base /etc/ssl/certs crt-base /etc/ssl/private # Default ciphers to use on SSL-enabled listening sockets. # For more information, see ciphers(1SSL). ssl-default-bind-ciphers kEECDH+aRSA+AES:kRSA+AES:+AES256:RC4-SHA:!kEDH:!LOW:!EXP:!MD5:!aNULL:!eNULL defaults log global mode http option httplog option dontlognull timeout connect 5000 timeout client 50000 timeout server 50000 errorfile 400 /etc/haproxy/errors/400.http errorfile 403 /etc/haproxy/errors/403.http errorfile 408 /etc/haproxy/errors/408.http errorfile 500 /etc/haproxy/errors/500.http errorfile 502 /etc/haproxy/errors/502.http errorfile 503 /etc/haproxy/errors/503.http errorfile 504 /etc/haproxy/errors/504.http frontend localnodes #where HAProxy listens to connections bind *:80 #HAProxy 在所有網卡的 80 端口上監聽 HTTP 請求 mode http #監聽 HTTP 請求,這時它作為一個七層負載均衡器 default_backend nodes #所使用的后端服務器 backend nodes #Where HAPoxy sends incoming connections mode http #轉發 HTTP 包給后端服務器 balance roundrobin #分發算法 option forwardfor #在 HTTP 頭中添加 X-Forwarded-For 頭,使得后端服務器可以獲取原始請求的來源地址 http-request set-header X-Forwarded-Port %[dst_port] #在 HTTP 頭中添加 X-Forwarded-Port 頭從而使得后端服務器可以知道原始的 HTTP Port http-request add-header X-Forwarded-Proto https if { ssl_fc } #在使用 SSL 時添加 X-Forwarded-Proto頭 option httpchk HEAD / HTTP/1.1\r\nHost:localhost #使用 health check 來檢查后端服務器的可達性 server web01 127.0.0.1:9000 check #后端服務器。”check“表示做 health check。 server web02 127.0.0.1:9001 check server web03 127.0.0.1:9002 check listen stats *:1936 #用於監控 HAProxy stats enable stats uri / stats hide-version stats auth someuser:password
2. Neutron 中的虛擬負載均衡器
Neutron LBaas (load-balancer-as-a-service)擴展(extension)提供向在多個 Nova 虛機中運行的應用提供負載均衡的方法。它還提供 API 來快速方便地部署負載均衡器。 它早在 OpenStack 的 Grizzly 版本就集成到 Neutron 中。自集成到 Neutron 以來,LBaaS 經歷過幾次大的變化。目前,在最新發布的 Kilo 版本中,LBaas 代碼從 Neutron 中抽離,直接由獨立的項目管理,以及實現了 LBaaS V2。本文是基於 Juno 版本的 LBaas 進行分析。 OpenStack Neutron 默認以 HAProxy 為負載均衡的 driver,同時也支持 A10 network(參考鏈接)、netscaler、radware (參考文檔)等作為 driver。
2.1 LBaas 中的概念
LBaas 可以看做 OpenStack Neutron 對各種物理負載均衡器的虛擬化。它的概念可以和 HAProxy 中的概念進行類比:
HAProxy 的概念 | LBaas 的概念 | 說明 |
Driver | LBaas 也是采取 driver 模型來支持多種物理的負載均衡器。LBaas 默認實現了 HAProxy driver,同時,它也支持多個其他 Vendor driver。 |
|
Frontend | VIP(Virturl IP address) | LBaas 對外提供服務的地址。VIP 有自己的 IP 地址,而且一般都能通過公網進行訪問。VIP 負責將網絡流量分發到各個 member。 |
Backend | Pool |
代表負載后端的虛擬機池。
在以 HAProxy 為 Driver 的情況下,一個 Pool 對應着在一個獨立的 network namespace 中運行的 HAProxy 進程所管理的 backend。目前一個 pool 只能有一個 VIP。
|
Backend server | Member | Member 對應的是 pool 里面處理網絡請求的一個 OpenStack Nova 虛機。 |
Health check | Health monitor | 它用來監測 pool 里面 member 的狀態,支持 HTTP, TCP, 和 ping 等多種檢測方法。在 Nuetron 中這是可選的,如果沒有 Health monitor,pool 會一直認為所有的 member 都是 ACTIVE 狀態,這樣所有的 member 會一直出現在 VIP 的分發列表中,哪怕 member 對應的實例不能響應網絡請求。這在實際應用中會造成負載均衡的響應異常。 |
LBaas driver 模型:
基本概念:
基本概念之間的聯系:
2.2 LBaas HAProxy 部署實例
2.2.1 安裝
不同的 LBaas drive 支持不同的部署模式。社區實現 LBaas HAPorxy driver 只支持 one-arm 模式。你可以部署LBaas Agent 在network 節點上,也可以部署在別的節點上。本實例中,將 neutron-lbaas-agent 安裝在 network 節點上。你可以部署多個 LBaas 節點(agent),每個 agent 上運行不同的物理負載均衡器。
網絡節點上:
apt-get install neutron-lbaas-agent
apt-get install haproxy
修改 /etc/neutron/lbaas_agent.ini:
interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver
device_driver = neutron.services.loadbalancer.drivers.haproxy.namespace_driver.HaproxyNSDriver
修改/etc/neutron/neutron.conf
service_plugins = router,lbaas
啟動 service:
service neutron-lbaas-agent restart
lbaas-agent 進程:
neutron 18890 1 5 06:53 ? 00:00:01 /usr/bin/python /usr/bin/neutron-lbaas-agent --config-file=/etc/neutron/lbaas_agent.ini --config-file=/etc/neutron/neutron.conf --log-file=/var/log/neutron/lbaas-agent.log
Controller 節點上:
修改/etc/neutron/neutron.conf
service_plugins = router,lbaas
service neutron-server restart
修改/etc/openstack-dashboard/local_settings.py:
OPENSTACK_NEUTRON_NETWORK = {
'enable_router': True,
'enable_quotas': True,
'enable_ipv6': True,
'enable_distributed_router': False,
'enable_ha_router': False,
'enable_lb': True
2.2.2 配置
(1)Create pool
s1@controller:~$ neutron lb-pool-list
+--------------------------------------+-------+----------+-------------+----------+----------------+--------+
| id | name | provider | lb_method | protocol | admin_state_up | status |
+--------------------------------------+-------+----------+-------------+----------+----------------+--------+
| 3b9d8ebd-9eea-4925-b14e-593a6111ff33 | pool1 | haproxy | ROUND_ROBIN | HTTP | True | ACTIVE |
+--------------------------------------+-------+----------+-------------+----------+----------------+--------+
不太理解創建 pool 時候選的 subnet 是什么用途。
(2)Create members
s1@controller:~$ neutron lb-member-list
+--------------------------------------+-------------+---------------+--------+----------------+--------+
| id | address | protocol_port | weight | admin_state_up | status |
+--------------------------------------+-------------+---------------+--------+----------------+--------+
| 1f74a288-937d-4804-9ded-472a5d1110dc | 81.1.180.13 | 80 | 1 | True | ACTIVE |
| 944ff4a0-4070-40e4-8189-20f385755113 | 91.1.180.14 | 80 | 1 | True | ACTIVE |
| c5bd3138-9635-4588-889f-d08fcd364ed4 | 81.1.180.12 | 80 | 1 | True | ACTIVE |
+--------------------------------------+-------------+---------------+--------+----------------+--------+
似乎對 member 自身沒什么限制,只要是 pool 所在的 tenant 內的虛機都可以加入。
(3)Create VIP for pool
s1@controller:~$ neutron lb-vip-list
+--------------------------------------+------+-------------+----------+----------------+--------+
| id | name | address | protocol | admin_state_up | status |
+--------------------------------------+------+-------------+----------+----------------+--------+
| 0c32d37d-f84a-4309-9e01-72d9f0bac69e | vip2 | 81.1.180.81 | HTTP | True | ACTIVE |
+--------------------------------------+------+-------------+----------+----------------+--------+
VIP 的 subnet 也可以和 pool 的subnet 不一致,主要是在指定的 subnet 內創建一個 port。
注意:這里的 VIP 從字面上看具有一定的迷惑性。VIP (virtual ip) 必須創建在 pool 所在的 subnet 中,所以它本身只是一個和 pool member 的 fixed ip 一樣的 fixed ip address。如果要從外網訪問該 lb pool 的話,則還需要創建一個 floating ip 並且把它關聯到 lb pool 的 vip 上。這也是 one-arm 單臂的由來,也就是說 haproxy 所在的namespace 其實只有一個IP地址,分別接收外部連接以及和成員之間的連接。
從 Horizon 中看創建 VIP 的情景:
(4)Create health monitor
s1@controller:~$ neutron lb-healthmonitor-list
+--------------------------------------+------+----------------+
| id | type | admin_state_up |
+--------------------------------------+------+----------------+
| df1fcdd1-c0b5-4e14-a2fe-2d0789fc26a5 | PING | True |
+--------------------------------------+------+----------------+
(5) associate health monitor with pool
s1@controller:~$ neutron lb-healthmonitor-associate df1fcdd1-c0b5-4e14-a2fe-2d0789fc26a5 3b9d8ebd-9eea-4925-b14e-593a6111ff33
Associated health monitor df1fcdd1-c0b5-4e14-a2fe-2d0789fc26a5
以上步驟后的 pool 的詳細配置:
s1@controller:~$ neutron lb-pool-show 3b9d8ebd-9eea-4925-b14e-593a6111ff33
+------------------------+--------------------------------------------------------------------------------------------------------+
| Field | Value |
+------------------------+--------------------------------------------------------------------------------------------------------+
| admin_state_up | True |
| description | |
| health_monitors | df1fcdd1-c0b5-4e14-a2fe-2d0789fc26a5 |
| health_monitors_status | {"monitor_id": "df1fcdd1-c0b5-4e14-a2fe-2d0789fc26a5", "status": "ACTIVE", "status_description": null} |
| id | 3b9d8ebd-9eea-4925-b14e-593a6111ff33 |
| lb_method | ROUND_ROBIN |
| members | 1f74a288-937d-4804-9ded-472a5d1110dc |
| | 944ff4a0-4070-40e4-8189-20f385755113 |
| | c5bd3138-9635-4588-889f-d08fcd364ed4 |
| name | pool1 |
| protocol | HTTP |
| provider | haproxy |
| status | ACTIVE |
| status_description | |
| subnet_id | 4ec65731-35a5-4637-a59b-a9f2932099f1 |
| tenant_id | 74c8ada23a3449f888d9e19b76d13aab |
| vip_id | 19e16b0b-f48b-4f90-803c-d3afaf26c697 |
+------------------------+--------------------------------------------------------------------------------------------------------+
關於 pool,vip,member 的 subnet,在我的測試環境中,三者之間沒什么限制,各自都可以處於不同的 subnet 中,只要subnet 之間配置好了 router。這個 ticket 倒是提出來要限制 member 和 vip 都在 pool 的subnet 中,但是有人認為目前沒什么必要。但是,lb-pool-create 的 help 信息中,subnet 指 ”The subnet on which the members of the pool will be located.“這個說明就和代碼實現就有矛盾了。
2.2.3 驗證
在 vm1 81.1.180.12 上,運行 while true; do echo -e 'HTTP/1.0 200 OK\r\n\r\nserver_151' | sudo nc -l -p 80 ; done
在 vm2 81.1.180.13 上,運行 while true; do echo -e 'HTTP/1.0 200 OK\r\n\r\nserver_153' | sudo nc -l -p 80 ; done
在vm3 上使用 wget http://81.1.180.12 和 wget http://81.1.180.13 ,能夠返回結果,顯示上面的命令成功運行。
再在 vm3 兩次使用 wget http://81.1.180.81,結果分別顯示 server_153 和 server_151,顯示輪詢分發成功。
2.2.4 LBaas 的實現
網絡節點上,neutron-lbaas-agent:
(1)為每個帶有 active VIP 的 pool 創建了一個 network namespace,它以 qlbaas-<pool UUID> 命名:ip netns add qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33
root@network:/home/s1# ip netns list
qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33
root@network:/home/s1# ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip addr
50: tap2d1b74fe-68: <BROADCAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UNKNOWN group default link/ether fa:16:3e:7c:e5:ce brd ff:ff:ff:ff:ff:ff inet 81.1.180.81/24 brd 81.1.180.255 scope global tap2d1b74fe-68 #該 interface 的IP 地址為 VIP 的 IP 地址 valid_lft forever preferred_lft forever inet6 fe80::f816:3eff:fe7c:e5ce/64 scope link valid_lft forever preferred_lft forever
該 interface 掛載 OVS br-int 上並被打上了它所在的 network(pool -> subnet -> network) 對應的本地 VLAN ID:
Bridge br-int
Port "tap2d1b74fe-68" tag: 4 Interface "tap2d1b74fe-68" type: internal
完整的 network namespace 操作過程為:
ovs-vsctl --if-exists del-port tapd9de9e84-23 --add-port br-int tapd9de9e84-23 --set Interface tapd9de9e84-23 type=internal --set Interface tapd9de9e84-23 external-ids:iface-id=d9de9e84-23ad-4f57-b85a-aea99abf409d --set Interface tapd9de9e84-23 external-ids:iface-status=active --set Interface tapd9de9e84-23 external-ids:attached-mac=fa:16:3e:f4:8f:ae
ip link set tapd9de9e84-23 address fa:16:3e:f4:8f:ae
ip netns add qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33
ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 sysctl -w net.ipv4.conf.all.promote_secondaries=1
ip link set tapd9de9e84-23 netns qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip link set lo up ip link set tapd9de9e84-23 netns qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip link set tapd9de9e84-23 up ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip addr show tapd9de9e84-23 permanent scope global ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip -4 addr add 91.1.180.180/24 brd 91.1.180.255 scope global dev tapd9de9e84-23 ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 ip route list dev tapd9de9e84-23 scope link ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 route add default gw 91.1.180.1 ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 arping -U -I tapd9de9e84-23 -c 3 91.1.180.180
(2)生成 haproxy 配置文件
global #global 中除了 group 都是 hard coded 的 daemon user nobody group nogroup #可以由配置項 user_group 指定,默認為 nogroup log /dev/log local0 log /dev/log local1 notice stats socket /var/lib/neutron/lbaas/3b9d8ebd-9eea-4925-b14e-593a6111ff33/sock mode 0666 level user
defaults #都是 hard coded 的 log global retries 3 option redispatch timeout connect 5000 timeout client 50000 timeout server 50000
frontend 0c32d37d-f84a-4309-9e01-72d9f0bac69e option tcplog bind 81.1.180.81:80 #VIP.IP:port mode http #pool1.Protocol default_backend 3b9d8ebd-9eea-4925-b14e-593a6111ff33 maxconn 100 # VIP.ConnectionLimit option forwardfor # 當 mode 為 ”http“時,設置 forwardfor,使得通過 X-Forward-For 頭來保存原始的源 IP 地址
backend 3b9d8ebd-9eea-4925-b14e-593a6111ff33 mode http #pool.protocol balance roundrobin #pool1.Load_Balancing_Method option forwardfor timeout check 5s # Monitor.timeout(5)當 inter 和 timeout 都設置時,超時時間取兩者中的短者 server 1f74a288-937d-4804-9ded-472a5d1110dc 81.1.180.13:80 weight 1 check inter 5s fall 3 #member1 的配置,包括 ip,port(member 提供服務的端口,因此此時沒有指定check port,因此也是健康檢查的TCP端口),weight;check 指定做健康檢查;inter 指定兩次連續檢查之間的間隔,默認2s (5s);fall 指定Max Retries 或者連續幾次檢查失敗即認為member 是 DOWN 的次數 (3)
server 944ff4a0-4070-40e4-8189-20f385755113 91.1.180.14:80 weight 1 check inter 5s fall 3 #member 2 的配置 server c5bd3138-9635-4588-889f-d08fcd364ed4 81.1.180.12:80 weight 1 check inter 5s fall 3 #member 3 的配置
HAProxy 做健康檢查一些別的細節:
- HAProxy 不支持 PING 做健康檢查。詳細見官網 Health checking。
- TCP port:可以使用 check port 來指定健康檢查 TCP 連接所使用的端口,比如指定 80端口: server srv1 10.0.0.1:443 check port 80;如果不指定的話,則使用負載均衡端口,也就是使用 443:server srv1 10.0.0.1:443。
option httpchk server srv1 10.0.0.1:80 check server srv1 10.0.0.1:80 check
- 還可以使用其它檢查方式,包括
-
Checking a SSL port Checking a LDAP service Checking a MySql service Checking a PgSQL service Checking a redis service Checking a SMTP service Checking any service
Neutron LBaaS HAProxy agent health monitor 的一些細節:
- 只支持 TCP, HTTP, HTTPS,選擇 PING 等同於選擇 TCP。參見這個 bug。
- TCP 不能顯式指定用於健康檢查的端口,而是使用負載均衡端口。
- 由 ACTIVE 變為 INACTIVE 狀態的過程需要經過 min(timeout, inter) * fall 時長,也就是需要多次檢查失敗才行。
- 由 INACTIVE 變為 ACTIVE 狀態的過程,只需要 min(timeout, inter) 時長,也就是一次檢查成功就可以了
- 一個 Pool 可以關聯多個 health monitor 執行不同類型的檢查。只有當全部的 monitor 認為一個member 是 active 時,該 member 的狀態才是 ACTIVE,否則都會是 INACTIVE。
- 使用 HTTP 的一個例子
-
timeout check 3s option httpchk GET / http-check expect rstatus 200 server 7015f0d6-12bf-4b68-8455-7c601408dfec 20.0.0.122:80 weight 1 check inter 3s fall 2
- 使用 HTTPS 的一個例子。與 HTTP 相比,只是增加了 "option ssl-hello-chk"。
-
timeout check 3s option httpchk GET / http-check expect rstatus 200 option ssl-hello-chk server 7015f0d6-12bf-4b68-8455-7c601408dfec 20.0.0.122:80 weight 1 check inter 3s fall 2
- 代碼(源代碼):
-
server_addon = ' check inter %(delay)ds fall %(max_retries)d' % monitor opts = [ 'timeout check %ds' % monitor['timeout'] ] if monitor['type'] in (constants.HEALTH_MONITOR_HTTP, constants.HEALTH_MONITOR_HTTPS): opts.append('option httpchk %(http_method)s %(url_path)s' % monitor) opts.append( 'http-check expect rstatus %s' % '|'.join(_expand_expected_codes(monitor['expected_codes'])) ) if monitor['type'] == constants.HEALTH_MONITOR_HTTPS: opts.append('option ssl-hello-chk')
一個使用 TCP 健康監視器時一個 member 狀態變化的簡單例子:
- (0)環境:負載均衡服務為80端口上的 HTTP,使用 TCP health monitor
- (1)將一個member 加入 pool,在 member 上不啟動 HTTP 服務,則在 neutron lb-member-list 中其狀態為 INACTIVE
- (2)登錄該member,啟動 HTTP 服務,經過很短的時間,member 的狀態變為 ACTIVE
- (3)再次登錄該 member,停止 HTTP 服務,經過相對較長的時間,member 的狀態變為 INACTIVE
(3)在 network namespace 中 啟動了一個 haproxy 進程,使用生成的配置文件:
ip netns exec qlbaas-3b9d8ebd-9eea-4925-b14e-593a6111ff33 haproxy -f /var/lib/neutron/lbaas/3b9d8ebd-9eea-4925-b14e-593a6111ff33/conf -p /var/lib/neutron/lbaas/3b9d8ebd-9eea-4925-b14e-593a6111ff33/pid root@network:/home/s1# ps -ef | grep haproxy nobody 22625 1 0 07:29 ? 00:00:02 haproxy -f /var/lib/neutron/lbaas/3b9d8ebd-9eea-4925-b14e-593a6111ff33/conf -p /var/lib/neutron/lbaas/3b9d8ebd-9eea-4925-b14e-593a6111ff33/pid -sf 22365
(4)在 lbaas 的 interface 上抓包,看看幾個機器之間的交互過程
從 81.1.180.14 上 wget 81.1.180.81,VIP 從 81.1.180.12 獲取數據
# 81.1.180.14 通過 ARP 得到 81.1.180.81 的 MAC 10:20:33.070752 fa:16:3e:82:37:03 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Request who-has 81.1.180.81 tell 81.1.180.14, length 28 10:20:33.070779 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype ARP (0x0806), length 42: Ethernet (len 6), IPv4 (len 4), Reply 81.1.180.81 is-at fa:16:3e:7c:e5:ce, length 28 #發起 81.1.180.14.41882 到 81.1.180.81.80 的 TCP 連接(三次握手) 10:20:33.073829 fa:16:3e:82:37:03 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 74: (tos 0x0, ttl 64, id 24973, offset 0, flags [DF], proto TCP (6), length 60) 81.1.180.14.41882 > 81.1.180.81.80: Flags [S], cksum 0x380e (correct), seq 2662307254, win 14600, options [mss 1460,sackOK,TS val 62523 ecr 0,nop,wscale 2], length 0 10:20:33.073884 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype IPv4 (0x0800), length 74: (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) 81.1.180.81.80 > 81.1.180.14.41882: Flags [S.], cksum 0x0a91 (incorrect -> 0xc0ae), seq 1950187908, ack 2662307255, win 28960, options [mss 1460,sackOK,TS val 19810369 ecr 62523,nop,wscale 7], length 0 10:20:33.078375 fa:16:3e:82:37:03 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 24974, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.14.41882 > 81.1.180.81.80: Flags [.], cksum 0x5257 (correct), seq 1, ack 1, win 3650, options [nop,nop,TS val 62525 ecr 19810369], length 0 # HTTP 數據從 81.1.180.14.41882 發到 81.1.180.81.80 10:20:33.079796 fa:16:3e:82:37:03 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 140: (tos 0x0, ttl 64, id 24975, offset 0, flags [DF], proto TCP (6), length 126) 81.1.180.14.41882 > 81.1.180.81.80: Flags [P.], cksum 0x1a1e (correct), seq 1:75, ack 1, win 3650, options [nop,nop,TS val 62525 ecr 19810369], length 74 10:20:33.079902 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 51655, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.80 > 81.1.180.14.41882: Flags [.]^C, cksum 0x0a89 (incorrect -> 0x5f6a), seq 1, ack 75, win 227, options [nop,nop,TS val 19810371 ecr 62525], length 0 #發起 81.1.180.81.49215 > 81.1.180.12.80 的連接 10:20:33.080106 fa:16:3e:7c:e5:ce > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 74: (tos 0x0, ttl 64, id 49199, offset 0, flags [DF], proto TCP (6), length 60) 81.1.180.81.49215 > 81.1.180.12.80: Flags [S], cksum 0x0a8f (incorrect -> 0xb04a), seq 2295826540, win 29200, options [mss 1460,sackOK,TS val 19810371 ecr 0,nop,wscale 7], length 0 10:20:33.080936 fa:16:3e:2b:3e:2a > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 74: (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60) 81.1.180.12.80 > 81.1.180.81.49215: Flags [S.], cksum 0xeeb5 (correct), seq 1301661959, ack 2295826541, win 14480, options [mss 1460,sackOK,TS val 2352200 ecr 19810371,nop,wscale 2], length 0 10:20:33.081056 fa:16:3e:7c:e5:ce > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 49200, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.49215 > 81.1.180.12.80: Flags [.], cksum 0x0a87 (incorrect -> 0x5528), seq 1, ack 1, win 229, options [nop,nop,TS val 19810371 ecr 2352200], length 0 ##傳送 HTTP 數據 #數據從 81.1.180.81.49215 發到 81.1.180.12.80 10:20:33.081264 fa:16:3e:7c:e5:ce > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 170: (tos 0x0, ttl 64, id 49201, offset 0, flags [DF], proto TCP (6), length 156) 81.1.180.81.49215 > 81.1.180.12.80: Flags [P.], cksum 0x0aef (incorrect -> 0x06d5), seq 1:105, ack 1, win 229, options [nop,nop,TS val 19810371 ecr 2352200], length 104 10:20:33.093515 fa:16:3e:2b:3e:2a > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 11584, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.12.80 > 81.1.180.81.49215: Flags [.], cksum 0x4781 (correct), seq 1, ack 105, win 3620, options [nop,nop,TS val 2352200 ecr 19810371], length 0 #數據從 81.1.180.12.80 發到 81.1.180.81.49215 10:20:33.110793 fa:16:3e:2b:3e:2a > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 96: (tos 0x0, ttl 64, id 11585, offset 0, flags [DF], proto TCP (6), length 82) 81.1.180.12.80 > 81.1.180.81.49215: Flags [P.], cksum 0xcc98 (correct), seq 1:31, ack 105, win 3620, options [nop,nop,TS val 2352207 ecr 19810371], length 30 10:20:33.110904 fa:16:3e:7c:e5:ce > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 49202, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.49215 > 81.1.180.12.80: Flags [.], cksum 0x0a87 (incorrect -> 0x5494), seq 105, ack 31, win 229, options [nop,nop,TS val 19810378 ecr 2352207], length 0 # HTTP 數據從 81.1.180.81.80 發到 81.1.180.14.41882 10:20:33.111083 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype IPv4 (0x0800), length 96: (tos 0x0, ttl 64, id 51656, offset 0, flags [DF], proto TCP (6), length 82) 81.1.180.81.80 > 81.1.180.14.41882: Flags [P.], cksum 0x0aa7 (incorrect -> 0xe480), seq 1:31, ack 75, win 227, options [nop,nop,TS val 19810379 ecr 62525], length 30 10:20:33.124295 fa:16:3e:82:37:03 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 24976, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.14.41882 > 81.1.180.81.80: Flags [.], cksum 0x51db (correct), seq 75, ack 31, win 3650, options [nop,nop,TS val 62535 ecr 19810379], length 0 #關閉 81.1.180.12.80 > 81.1.180.81.49215 和 81.1.180.81.80 > 81.1.180.14.41882 的連接 (三次揮手。本來是四次,第四次可能有延遲) 10:20:33.124322 fa:16:3e:2b:3e:2a > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 11586, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.12.80 > 81.1.180.81.49215: Flags [F.], cksum 0x4751 (correct), seq 31, ack 105, win 3620, options [nop,nop,TS val 2352210 ecr 19810378], length 0 10:20:33.124444 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 51657, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.80 > 81.1.180.14.41882: Flags [F.], cksum 0x0a89 (incorrect -> 0x5f36), seq 31, ack 75, win 227, options [nop,nop,TS val 19810382 ecr 62535], length 0 10:20:33.173331 fa:16:3e:7c:e5:ce > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 49203, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.49215 > 81.1.180.12.80: Flags [.], cksum 0x0a87 (incorrect -> 0x5480), seq 105, ack 32, win 229, options [nop,nop,TS val 19810394 ecr 2352210], length 0 10:20:33.173567 fa:16:3e:82:37:03 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 24977, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.14.41882 > 81.1.180.81.80: Flags [F.], cksum 0x51cd (correct), seq 75, ack 32, win 3650, options [nop,nop,TS val 62544 ecr 19810382], length 0 10:20:33.173756 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 51658, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.80 > 81.1.180.14.41882: Flags [.], cksum 0x0a89 (incorrect -> 0x5f20), seq 32, ack 76, win 227, options [nop,nop,TS val 19810394 ecr 62544], length 0 10:20:33.176286 fa:16:3e:7c:e5:ce > fa:16:3e:2b:3e:2a, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 49204, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.81.49215 > 81.1.180.12.80: Flags [F.], cksum 0x0a87 (incorrect -> 0x547e), seq 105, ack 32, win 229, options [nop,nop,TS val 19810395 ecr 2352210], length 0 10:20:33.190055 fa:16:3e:2b:3e:2a > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 66: (tos 0x0, ttl 64, id 11587, offset 0, flags [DF], proto TCP (6), length 52) 81.1.180.12.80 > 81.1.180.81.49215: Flags [.], cksum 0x4731 (correct), seq 32, ack 106, win 3620, options [nop,nop,TS val 2352224 ecr 19810395], length 0
看看具體的數據包:
# 81.1.180.14 發送 GET 命令到 81.1.180.81
11:04:16.282603 fa:16:3e:82:37:03 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 140: (tos 0x0, ttl 64, id 53145, offset 0, flags [DF], proto TCP (6), length 126) 81.1.180.14.41883 > 81.1.180.81.80: Flags [P.], cksum 0xd178 (correct), seq 1:75, ack 1, win 3650, options [nop,nop,TS val 718394 ecr 20466170], length 74 0x0000: 4500 007e cf99 4000 4006 607e 5101 b40e E..~..@.@.`~Q... 0x0010: 5101 b451 a39b 0050 e1b8 39b0 359e 1dfd Q..Q...P..9.5... 0x0020: 8018 0e42 d178 0000 0101 080a 000a f63a ...B.x.........: 0x0030: 0138 49fa 4745 5420 2f20 4854 5450 2f31 .8I.GET./.HTTP/1 0x0040: 2e31 0d0a 486f 7374 3a20 3831 2e31 2e31 .1..Host:.81.1.1 0x0050: 3830 2e38 310d 0a55 7365 722d 4167 656e 80.81..User-Agen 0x0060: 743a 2057 6765 740d 0a43 6f6e 6e65 6374 t:.Wget..Connect 0x0070: 696f 6e3a 2063 6c6f 7365 0d0a 0d0a ion:.close....
#81.1.180.81 轉發上面的 GET 請求到 91.1.180.14,可見 Host 已經換成 VIP 的 IP 了,原始 IP 保存在X-Forwarded-For 中。
11:04:16.283954 fa:16:3e:7c:e5:ce > fa:16:3e:87:40:f3, ethertype IPv4 (0x0800), length 170: (tos 0x0, ttl 64, id 19645, offset 0, flags [DF], proto TCP (6), length 156) 81.1.180.81.46000 > 91.1.180.14.80: Flags [P.], cksum 0x14f1 (incorrect -> 0x41ae), seq 1:105, ack 1, win 229, options [nop,nop,TS val 20466172 ecr 17195073], length 104 0x0000: 4500 009c 4cbd 4000 4006 d93c 5101 b451 E...L.@.@..<Q..Q 0x0010: 5b01 b40e b3b0 0050 bc0d 0541 897a 262f [......P...A.z&/ 0x0020: 8018 00e5 14f1 0000 0101 080a 0138 49fc .............8I. 0x0030: 0106 6041 4745 5420 2f20 4854 5450 2f31 ..`AGET./.HTTP/1 0x0040: 2e31 0d0a 486f 7374 3a20 3831 2e31 2e31 .1..Host:.81.1.1 0x0050: 3830 2e38 310d 0a55 7365 722d 4167 656e 80.81..User-Agen 0x0060: 743a 2057 6765 740d 0a43 6f6e 6e65 6374 t:.Wget..Connect 0x0070: 696f 6e3a 2063 6c6f 7365 0d0a 582d 466f ion:.close..X-Fo 0x0080: 7277 6172 6465 642d 466f 723a 2038 312e rwarded-For:.81. 0x0090: 312e 3138 302e 3134 0d0a 0d0a 1.180.14....
#91.1.180.14 發回回復消息給 81.1.180.81
11:04:16.288376 fa:16:3e:87:40:f3 > fa:16:3e:7c:e5:ce, ethertype IPv4 (0x0800), length 96: (tos 0x0, ttl 63, id 18031, offset 0, flags [DF], proto TCP (6), length 82) 91.1.180.14.80 > 81.1.180.81.46000: Flags [P.], cksum 0x0676 (correct), seq 1:31, ack 105, win 3620, options [nop,nop,TS val 17195074 ecr 20466172], length 30 0x0000: 4500 0052 466f 4000 3f06 e0d4 5b01 b40e E..RFo@.?...[... 0x0010: 5101 b451 0050 b3b0 897a 262f bc0d 05a9 Q..Q.P...z&/.... 0x0020: 8018 0e24 0676 0000 0101 080a 0106 6042 ...$.v........`B 0x0030: 0138 49fc 4854 5450 2f31 2e32 2032 3030 .8I.HTTP/1.2.200 0x0040: 204f 4b0d 0a0d 0a73 6572 7665 725f 3135 .OK....server_15 0x0050: 320a 2.
#81.1.180.81 將上面的消息轉發到 81.1.180.14 2. 11:04:16.288639 fa:16:3e:7c:e5:ce > fa:16:3e:82:37:03, ethertype IPv4 (0x0800), length 96: (tos 0x0, ttl 64, id 21511, offset 0, flags [DF], proto TCP (6), length 82) 81.1.180.81.80 > 81.1.180.14.41883: Flags [P.], cksum 0x0aa7 (incorrect -> 0x9ae0), seq 1:31, ack 75, win 227, options [nop,nop,TS val 20466173 ecr 718394], length 30 0x0000: 4500 0052 5407 4000 4006 dc3c 5101 b451 E..RT.@.@..<Q..Q 0x0010: 5101 b40e 0050 a39b 359e 1dfd e1b8 39fa Q....P..5.....9. 0x0020: 8018 00e3 0aa7 0000 0101 080a 0138 49fd .............8I. 0x0030: 000a f63a 4854 5450 2f31 2e32 2032 3030 ...:HTTP/1.2.200 0x0040: 204f 4b0d 0a0d 0a73 6572 7665 725f 3135 .OK....server_15 0x0050: 320a 2.
3. Neutron LBaas 代碼分析
代碼在 https://github.com/openstack/neutron-lbaas/tree/stable/juno。代碼本身相對簡單。
代碼分布:
- Extension:負責處理 REST API 請求,並進行 schedule,然后將 REST API 請求轉發到 schedule 過程選出的 agent 上的 Plugin。
- Plugin:處理核心邏輯,管理 DB 的讀寫。
- Agent:處理並響應由 Plugin 發來的請求,管理 Driver。OpenStack Neutron 默認采用 HAProxy 作為 driver。
除此以外,LBaas 還提供提供CLI 和 Horizon 中的 GUI。
控制節點上的 Neutron server:
1. 根據配置文件,導入LBaas extension(service_plugins = *,lbaas,而 lbaas = neutron.services.loadbalancer.plugin:LoadBalancerPlugin)
2. 接到 lb-pool-create REST API 調用:
POST /v2.0/lb/pools.json HTTP/1.1
Route path: '/lb/pools.:(format)', defaults: {'action': u'create', 'controller': <wsgify at 139957175473808 wrapping <function resource at 0x7f4a51b962a8>>}
Match dict: {'action': u'create', 'controller': <wsgify at 139957175473808 wrapping <function resource at 0x7f4a51b962a8>>, 'format': u'json'}
Request body: {u'pool': {u'subnet_id': u'4ac56c61-84f3-4d00-b87a-1ab2441e8437', u'lb_method': u'ROUND_ROBIN', u'protocol': u'HTTP', u'name': u'test2', u'admin_state_up': True}}
3. 根據 Scheduler 算法找到一個放置該 pool 的 LBaas agent
Pool cc719e63-0e84-4c0f-9254-49ba7c67e86d is scheduled to lbaas agent 90c87c01-1cd1-48b0-8369-30f44c058574 schedule
4. 進入 LoadBalancerAgentApi,通過 RPC 調用該 agent 所在節點上的 LBaas Plugin
cast called with arguments (<neutron.context.Context object at 0x7f4a51498590>, {'args': {'driver_name': 'haproxy_ns', 'pool': {'status': 'PENDING_CREATE', 'lb_method': u'ROUND_ROBIN', 'protocol': u'HTTP', 'description': '', 'health_monitors': [], 'members': [], 'status_description': None, 'id': 'cc719e63-0e84-4c0f-9254-49ba7c67e86d', 'vip_id': None, 'name': u'test2', 'admin_state_up': True, 'subnet_id': u'4ac56c61-84f3-4d00-b87a-1ab2441e8437', 'tenant_id': u'74c8ada23a3449f888d9e19b76d13aab', 'health_monitors_status': [], 'provider': 'haproxy'}}, 'namespace': None, 'method': 'create_pool'}) {'topic': u'n-lbaas_agent.network', 'version': None}
關於 Agent scheduler:
一個 OpenStack 環境中可以有多個 LBaas agent,每個 agen 可能支持不同的物理負載均衡器,那么將新建的 pool 對應的物理負載均衡器部署到哪個 Agent 上就需要一個 Schedule 過程。你可以查看pool 所在的 agent:
s1@controller:~$ neutron lb-agent-hosting-pool 3b9d8ebd-9eea-4925-b14e-593a6111ff33
+--------------------------------------+---------+----------------+-------+
| id | host | admin_state_up | alive |
+--------------------------------------+---------+----------------+-------+
| 90c87c01-1cd1-48b0-8369-30f44c058574 | network | True | :-) |
+--------------------------------------+---------+----------------+-------+
Neutron 默認只實現了ChanceScheduler: loadbalancer_pool_scheduler_driver = neutron.services.loadbalancer.agent_scheduler.ChanceScheduler。你也可以實現你的 Scheduler。默認的 ChanceScheduler 從活動的、支持該 Pool Provider(比如 haproxy)的所有 agent 中隨機選擇一個。
網絡節點上的 LBaas Plugin (class LoadBalancerPlugin):
0. 初始化:從配置文件 device_driver 中讀取所有drivers
1. 接到 REST API 調用后:
1.1 執行 DB 操作
1.2. 調用 driver 操作
網絡節點上的 HAProxy LBaas driver:
1. 接收 RPC 通知,在 vip,pool,member,monitor 有變化時刷新 haproxy 進程
2. 通過 RPC 獲取該 pool 的所有邏輯配置
3. 根據邏輯配置生成 haproxy 配置文件
4. 在network namespace 中啟動 haproxy 進程
更詳細的過程可以參考 官網文章。
歡迎大家關注我的個人公眾號: