Octavia is an open source, operator-scale load balancing solution designed to work with OpenStack.html
自 Pike 以來 OpenStack 推薦使用 Octavia 代替 neutron-lbaas extension 做爲 Load Balancing as a Service 的首選方案,並在 Queens 中將 neutron-lbaas 標記爲廢棄 —— Neutron-lbaas is now deprecated。前端
社區推崇 Octavia 的緣由有不少,它解決了 neutron-lbaas 遺留的歷史包袱,可以對外提供獨立而穩定的 API(Neutron/LBaaS/Deprecation)。簡單的說,社區認爲 neutron-lbaas 使 Neutron 的項目管理變得拖沓,LBaaS 應該做爲一個獨立項目獲得長足的發展,事實也是如此。python
本篇基於 Rocky,記錄、分析 Octavia 做爲 OpenStack LBaaS 的抽象設計,開發性設計及其代碼實現,並從中感覺社區開發者們對 Octavia 的寄予。web
LBaaS:對於 OpenStack 平臺而言,LB(負載均衡)被做爲一種服務提供給用戶,用戶得以按需地、隨時地獲取可配置的業務負載均衡方案,這就是所謂 Load Balancing as a Service。redis
loadbalancer:負載均衡服務的根對象,用戶對負載均衡的定義、配置和操做都基於此。算法
VIP:與 loadbalancer 關聯的 IP 地址,每一個 loadbalancer 最起碼有一個 VIP,做爲外部對後端業務集羣的標準訪問入口。數據庫
Listener:下屬於 loadbalancer 的監聽器,用戶可配置監聽外部對 VIP 的訪問類型(e.g. 協議、端口)。json
Pool:後端的真實業務雲主機集羣域,通常的,用戶會根據雲主機的業務類型進行劃分。flask
Member:業務雲主機,下屬於 Pool,對應傳統負載均衡體系中的 Real Server。ubuntu
Health Monitor:掛靠於 Pool,週期性對 Pool 中的 Member(s) 進行健康檢查。
L7 Policy:七層轉發策略,描述了數據包轉發的動做(e.g. 轉發至 Pool,轉發至 URL,拒絕轉發)
L7 Rule:七層轉發規則,下屬於 L7 Policy,描述了數據包轉發的匹配域(e.g. 轉發至 Pool 中全部已 webserver 開頭的 Members)
上圖是一個簡易的「動靜頁面分離」負載均衡應用架構,輔助理解這些概念及其個體與總體間的關係。
至此,咱們嘗試提出一個問題:爲何要抽象出這些對象?
繼續從使用的角度來感性瞭解 Octavia 的樣子。
上圖是一個標準的 Octavia 網絡架構,包含了:
Amphora(e):實體爲雲主機,做爲負載均衡器的載體,也是 Octavia 的 Default Loadbalancer Provider。
lb-mgmt-net:是一個與 OpenStack Management/API Network 打通的網絡,project admin 可見,東側鏈接 Amphora Instance、西側鏈接 Octavia 服務進程。
tenant-net:業務雲主機所在的網絡
vip-net:提供 VIP 地址池的網絡
NOTE:vip-net 和 tenant-net 能夠是同一個網絡,但在生產環境中,咱們建議分開,以更有針對性的施加安全策略,劃分不一樣級別的網絡安全隔離域。
Step 1. 設定 loadbalancer 的 VIP。能夠直接指定 VIP,或由 DHCP 分配。
Step 2. 設定 listener 監聽的協議及端口。監聽外部訪問 http://<VIP>:8080/
。
Step 3. 設定 pool 的負載均衡算法。這裏選擇 RR 輪詢分發算法。
Step 4. 設定 pool 下屬的 member 成員。設定 members 須要指定端口和權重,前者表示接受數據轉發的 socket,後者表示分發的優先級。
Step 5. 設定 health monitor 的健康檢查規則。若是 member 出現 PING 不一樣的狀況,則會被標記爲故障,再也不接受分發。
如今的網絡拓撲變動以下圖,能夠看出,Amphorae 在之中起到了關鍵做用,使用端口掛載的方式將 3 個不一樣網絡中的 VIP、Member 及 Octava 服務進程串連起來,Amphorae(雙耳壺)也所以得名。
如今,咱們粗淺的梳理一下 Octavia Amphora Provider 的設計思路:
這裏再補充一下與 Octavia 相關的 image 和 security group 的內容。Amphora Instance 使用特定的鏡像啓動,Octavia 提供了專門的鏡像製做腳本,支持 centos 和 ubuntu 兩種操做系統,也支持設定 password,不過在生成環境中仍是建議使用 keypair 登陸。至於安全組,從上圖能夠看出 Amphora 的安全組最起碼要知足 ingress:UDP/5555 和 egress:TCP/9443 兩條規則。
使用 amphora image 的步驟:
$ /opt/rocky/octavia/diskimage-create/diskimage-create.sh -i ubuntu $ openstack image create amphora-x64-haproxy \ --public \ --container-format=bare \ --disk-format qcow2 \ --file /opt/rocky/octavia/diskimage-create/amphora-x64-haproxy.qcow2 \ --tag amphora
[controller_worker] amp_image_owner_id, amp_image_tag
中指定使用,e.g.[controller_worker] amp_image_owner_id = 9e4fe13a6d7645269dc69579c027fde4 amp_image_tag = amphora ...
使用 amphora security group 的步驟:
$ openstack security group create amphora-sec-grp --project <admin project id> $ openstack security group rule create --remote-ip "0.0.0.0/0" --dst-port 9443 --protocol tcp --ingress --ethertype IPv4 --project <admin project id> amphora-sec-grp $ openstack security group rule create --remote-ip "0.0.0.0/0" --dst-port 5555 --protocol udp --egress --ethertype IPv4 --project <admin project id> amphora-sec-grp
[controller_worker] amp_secgroup_list = <amphora-sec-grp id> ...
(注:圖源自 Octavia 官方文檔)
Octavia 的軟件架構設計依舊是常見的「生產者-消費者」模型,API 與 Worker 分離並經過 MessageQueens 進行通訊。
Octavia API:標準 RESTful API,Octavia v2 API(default enabled)是 LBaaS v2 API 的超集,徹底向後兼容。因此版本滯後的 OS 平臺也可經過 Neutron Octavia Driver 進行集成。
Octavia Controller Worker:Octavia 的核心,底層採用 Driver & Plugin 的方式表明了 OS 平臺的開放性,並支撐上層實現的 3 個組件。
NOTE:須要特別說明的是,架構圖中只給出了 Amphora 一種 LB Provider,但 Octavia 的 Driver 設計其實是支持多種 LB Provider 的(e.g. F5)。其實社區一直有計劃要將 openstack/neutron-lbaas repo 實現的 drivers 遷移到 Octavia,只是一直缺人作。
服務清單就是軟件架構的具象表現:
下面列舉一些關鍵的目錄:
繼續展開 controller 目錄:
api/handlers/queue/producer.py
controller/queue/consumer.py
PS:cotyledon 是由社區開發用於替代 oslo.service 的第三方開源庫。
Cotyledon provides a framework for defining long-running services. It provides handling of Unix signals, spawning of workers, supervision of children processes, daemon reloading, sd-notify, rate limiting for worker spawning, and more.
This library is mainly used in OpenStack Telemetry projects, in replacement of oslo.service. However, as oslo.service depends on eventlet, a different library was needed for project that do not need it. When an application do not monkeypatch the Python standard library anymore, greenlets do not in timely fashion. That made other libraries such as Tooz or oslo.messaging to fail with e.g. their heartbeat systems. Also, processes would not exist as expected due to greenpipes never being processed.
—— 摘自 cotyledon 官方文檔
小結一下 Octavia 的架構設計,做爲 OpenStack 的獨立項目,繼承了一向優秀的開放性設計思想,在 LB Provider、Certificates Driver、Compute Driver 和 Network Driver 這些外部支撐節點都高度抽象出了 Driver 類,使得 Vendors 和 Users 更便於對接現有的基礎設施。這無疑是 Octavia 甚至 OpenStack 受到歡迎的緣由之一,也從一個方面回答了上文中提出的問題:爲何要抽象出這些對象?
要說最典型的 Octavia 實現規範,非 loadbalancer 的建立流程莫屬。咱們就以此做爲切入點,輔以 UML 圖繼續深刻 Octavia 的代碼實現。
CLI:
$ openstack loadbalancer create --vip-subnet-id lb-vip-subnet --name lb1
API:
POST /v2.0/lbaas/loadbalancers
REQ body:
{ "loadbalancer": { "vip_subnet_id": "c55e7725-894c-400e-bd00-57a04ae1e676", "name": "lb1", "admin_state_up": true } }
RESP:
{ "loadbalancer": { "provider": "octavia", "flavor_id": "", "description": "", "provisioning_status": "PENDING_CREATE", "created_at": "2018-10-22T02:52:04", "admin_state_up": true, "updated_at": null, "vip_subnet_id": "c55e7725-894c-400e-bd00-57a04ae1e676", "listeners": [], "vip_port_id": "6629fef4-fe14-4b41-9b73-8230105b2e36", "vip_network_id": "1078e169-61cb-49bc-a513-915305995be1", "vip_address": "10.0.1.7", "pools": [], "project_id": "2e560efadb704e639ee4bb3953d94afa", "id": "5bcf8e3d-9e58-4545-bf80-4c0b905a49ad", "operating_status": "OFFLINE", "name": "lb1" } }
Create LB 的 Octavia API UML 圖:
展開 2. _validate_vip_request_object 的 UML 圖:
小結 octavia-api service 接收到 POST /v2.0/lbaas/loadbalancers
請求後處理的事情:
config secition [networking]
來配置Allow/disallow specific network object types when creating VIPs.config section [quotas]
來配置默認 Quota(e.g. 指定 Project1 只能建立 3 個 loadbalancer)。其中有幾點值得注意:
openstack quota set
設定。openstack loadbalancer create
指令沒有給出 --listeners
or --pools
之類的選項 在建立 loadbalancer 的同時建立下屬的 listeners 及 pools,但實際上 POST /v2.0/lbaas/loadbalancers
是能夠接收這兩個屬性的。因此 Dashboard 的 UI/UE 能夠做此項優化 。Create LB 的 Octavia Controller Worker UML 圖:
展開 3. get_create_load_balancer_flow 的 UML 圖:
能夠看出 create loadbalancer flow 主要有兩點:
首先解釋第一點,所謂 loadbalancer topology 實質指的是 amphorae 的高可用拓撲。支持 SINGLE、ACTIVE_STANDBY 兩種類型。SINGLE 顧名思義就是單節點的 amphora,不具有高可用性,也不建議在生產環境中使用;而 ACTIVE_STANDBY 則是實現了依賴 Keepalived Master/Backend 主從模式的 double amphorae。因此,本篇將不會討論 SINGLE topology。
Create amphora topology 的 UML 圖:
特地強調幾個細節:
若是 loadbalancer topology 爲 ACTIVE_STANDBY,還能夠同時配置 [nova] enable_anti_affinity = True
,應用 Nova 的反親和性機制進一步提高高可用性。
爲 loadbalancer 準備 amphora 能夠直接從 space amphora pool 獲取,而無需即時建立 new amphora 致使浪費時間。amphora for lb flow 會先檢查 space amphora pool 是否存在空閒的 amphora 能夠映射到 loadbalancer。若是存在則直接映射,不然才須要啓用 create new amphora 的任務流。space amphora pool 由 Housekeeping Manager 機制維護,根據配置 [house_keeping] spare_amphora_pool_size=2
設定 pool size。
amphora for lb flow 使用的是 graph flow(圖流)類型,其特色就是無定向,便可自定義流向,開發者能夠經過自定義的判斷條件(amp_for_lb_flow.link
)來控制任務流向。在該 flow 中定義的判斷條件爲:
if loadbalancer mapping Amphora instance SUCCESS: Upload database associations for loadbalancer and amphora else: Create amphora first Upload database associations for loadbalancer and amphora
再說第二點 amphora 初始只掛靠在 lb-mgmt-net 上,被分配到 loadbalancer 後,amphora 還須要掛靠到 vip-net 上。octavia-api 階段在 vip-net 建立的 port:loadbalancer-<load_balancer_id> 此刻就被使用上了。並且若是採用的是 ACTIVE_STANDBY topology,那麼還會在 vip-net 上建立兩個 VRRP_port(octavia-lb-vrrp-<amphora_id>)分別掛載到兩個 amphorae 做爲 Keepalived VIP 漂移的載體。
create networking for amphora(e) 的 UML 圖:
列出與 Amphora Networking 相關的幾個關鍵任務:
這些都是須要咱們重點關注的實現,由於在實際操做中,筆者認爲 Octavia Networking 是實現的重點,也是出現問題的高發區,只有掌握了底層實現才能夠更好的精準定位問題。
AllocateVIP 調用的是 Neutron 的接口封裝 AllowedAddressPairsDriver.allocate_vip
method,負責確保 VIP 的 port 存在並返回一個關聯了 Port、VIP 和 LB 三者的 data_models.Vip 對象。該 method 早在 octavia-api 就會被調用一次,因此程序流到 octavia-worker 通常已經把 VIP 的 Port 建立好了,以後會經過 Task:UpdateAmphoraVIPData 將 data_models.Vip 落庫持久化。
AllocateVIP 負載從 Neutron 分配 VIP,PlugVIP 則負責將 VIP 插入到 Amphora。
PlugVIP 的 UML 圖:
在 PlugVIP 的邏輯實現主要有兩個方面:
update VIP port 的 security_group_rules。由於外部是經過 VIP 來訪問業務的,並且 Listener 也是依附於 VIP 的,因此 VIP 的安全組規則其實是動態的。例如:爲 loadbalancer 添加了一個 HTTP:8080 的 Listener,則會在相應的 VIP 上 Upload ingress HTTP:8080 規則。
輪詢 loadbalancer 全部的 amphorae,檢查 Amphora 是否徹底具有當前所須要的 Ports,若是沒有則調用 Neutron API 建立出來只有再調研 Nova API 掛載到 Amphora Instance。
create lb flow 在通過了 TASK:AllocateVIP 和 TASK:PlugVIP 以後就基本完成了 Amphora 的外部資源準備,接下來的程序流將會進入到 Amphora 的內部實現。由於之間還涉及了 Octavia Controller Worker 和 Amphora Agent 如何進行安全通訊的問題。因此,咱們不妨先討論一下 Amphora Agent 和 AmphoraAPIClient 的通訊實現,再回過頭來看看剩下沒有聊到的 3 個任務。
上文咱們提到過,Amphora 本質是一個 instance,做爲 HAProxy 和 Keepalived 的運行載體。筆者認爲 Amphora 是一個很是經典的類 Proxy 實現,在解決「Proxy 應該如何與中控(Octavia Controller Worker) 進行安全通訊?如何自定義心跳協議?如何下降對宿主機運行環境的影響?」這些問題上做出了優秀的示範,很是值得借鑑和研究。
amphora-agent 與 Octavia Controller Worker 的通訊模型圖:
首先咱們來看看 amphora-agent 與 AmphoraAPIClient 是如何創建通訊的。
amphora-agent 服務進程隨 Launch Amphora 一同啓動,應用了 Flask & gunicorn 的實現,前者提供 Web Application,後者充當 WSGI HTTP Server。服務進程的 main 函數爲 from octavia.cmd.agent import main
。
# file: /opt/rocky/octavia/octavia/amphorae/backends/agent/api_server/server.py class Server(object): def __init__(self): self.app = flask.Flask(__name__) ... self.app.add_url_rule(rule=PATH_PREFIX + '/listeners/<amphora_id>/<listener_id>/haproxy', view_func=self.upload_haproxy_config, methods=['PUT']) ...
上述 Server 類完成了 amphora-agent API 的路由定義和視圖函數,是一個輕量級的 Flask 框架封裝實現,app 對象最終被會 gunicorn 加載運行。配合官方文檔 Octavia HAProxy Amphora API 便可理解各個 route_url 的含義,這裏再也不贅述。
AmphoraAPIClient 就是 amphora-agent REST API 的客戶端實現,封裝了全部 Octavia HAProxy Amphora API 的 URL 請求,以供上層服務調用。
# file: /opt/rocky/octavia/octavia/amphorae/drivers/haproxy/rest_api_driver.py class AmphoraAPIClient(object): def __init__(self): super(AmphoraAPIClient, self).__init__() self.secure = False ...
回顧一下 Octavia 的通信架構:
回過頭來繼續看 TASK:AmphoraePostVIPPlug 的實現。AmphoraePostVIPPlug 會輪詢爲全部 Amphorae 分別調用 AmphoraAPIClient 發送 PUT plug/vip/{vip}
請求到 amphora-agent 更新虛擬機的網卡配置文件和添加路由規則。爲了防止出現 networks 的地址覆蓋,和保證 Amphora 操做系統的清潔,AmphoraePostVIPPlug 會建立出 network namespace。將除了 Amphora 接入 lb-mgmt-net 以外的全部 NICs 都被劃分到其中。
可見,AmphoraePostVIPPlug 的語義就是爲 VIP 建立網卡設備文件,並將 vip-net Port 的網絡信息注入到其中。具體實現爲 Plug:plug_vip
method,下面給出該任務的執行效果。
Amphora 初始狀態下只有一個用於與 lb-mgmt-net 通訊的端口:
root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ifconfig ens3 Link encap:Ethernet HWaddr fa:16:3e:b6:8f:a5 inet addr:192.168.0.9 Bcast:192.168.0.255 Mask:255.255.255.0 inet6 addr: fe80::f816:3eff:feb6:8fa5/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:19462 errors:14099 dropped:0 overruns:0 frame:14099 TX packets:70317 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:1350041 (1.3 MB) TX bytes:15533572 (15.5 MB) lo Link encap:Local Loopback inet addr:127.0.0.1 Mask:255.0.0.0 inet6 addr: ::1/128 Scope:Host UP LOOPBACK RUNNING MTU:65536 Metric:1 RX packets:0 errors:0 dropped:0 overruns:0 frame:0 TX packets:0 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1 RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
在 Amphora 被分配到 loadbalancer 以後會添加一個 vrrp_port 類型的端口,vrrp_port 充當着 Keepalived 虛擬路由的一張網卡,被注入到 namespace 中,通常是 eth1。
root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy 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:12705 errors:0 dropped:0 overruns:0 frame:0 TX packets:613211 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:762300 (762.3 KB) TX bytes:36792968 (36.7 MB) 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
VRRP IP: 172.16.1.3 和 VIP: 172.16.1.10 均由 lb-vip-network 的 DHCP 分配,分別對應 lb-vip-network 上的 ports octavia-lb-vrrp-<amphora_uuid> 與 octavia-lb-<loadbalancer_uuid>。其中 interface eth1 的配置爲:
root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy cat /etc/network/interfaces.d/eth1 auto eth1 iface eth1 inet dhcp root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy cat /etc/network/interfaces.d/eth1.cfg # Generated by Octavia agent auto eth1 eth1:0 iface eth1 inet static address 172.16.1.3 broadcast 172.16.1.255 netmask 255.255.255.0 gateway 172.16.1.1 mtu 1450 iface eth1:0 inet static address 172.16.1.10 broadcast 172.16.1.255 netmask 255.255.255.0 # Add a source routing table to allow members to access the VIP post-up /sbin/ip route add 172.16.1.0/24 dev eth1 src 172.16.1.10 scope link table 1 post-up /sbin/ip route add default via 172.16.1.1 dev eth1 onlink table 1 post-down /sbin/ip route del default via 172.16.1.1 dev eth1 onlink table 1 post-down /sbin/ip route del 172.16.1.0/24 dev eth1 src 172.16.1.10 scope link table 1 post-up /sbin/ip rule add from 172.16.1.10/32 table 1 priority 100 post-down /sbin/ip rule del from 172.16.1.10/32 table 1 priority 100 post-up /sbin/iptables -t nat -A POSTROUTING -p udp -o eth1 -j MASQUERADE post-down /sbin/iptables -t nat -D POSTROUTING -p udp -o eth1 -j MASQUERADE
只有當 loadbalancer_topology = ACTIVE_STANDBY
時纔會執行 Keepalived 啓動流程,提供高可用服務。TASK:AmphoraVRRPUpdate 和 TASK:AmphoraVRRPStart 就分別負責了編輯 Keepalived 配置文件內容和啓動 Keepalived 服務進程的邏輯。
TASK:AmphoraVRRPUpdate 的邏輯相對簡單,就是根據 amphora topology 的 VIP port、VRRP_ports 的網絡信息渲染到 keepalived.conf 配置文件的 Jinja 模板,而後經過 AmphoraAPIClient 發送 PUT vrrp/upload
請求到 amphora-agent 更新 Keepalived 配置文件的內容。
TASK:AmphoraVRRPStart 則是經過 AmphoraAPIClient 發送 PUT vrrp/start
請求執行 amphora-agent 的 view_func:manage_service_vrrp(action=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-agent 是經過執行指令 /usr/sbin/service octavia-keepalived start
來啓動 keepalived 服務進程的。再看一看 octavia-keepalived.service 的內容:
# 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
從該文件能夠得知:
/var/lib/octavia/vrrp/octavia-keepalived.conf
。除了 start 以外,view_func:manage_service_vrrp 還支持 stop 和 reload 操做,而 keepalived 配置文件的更新則交由 view_func:upload_keepalived_config 來完成。下面繼續看一看 keepalived 配置文件的內容:
# file: /var/lib/octavia/vrrp/octavia-keepalived.conf vrrp_script check_script { script /var/lib/octavia/vrrp/check_script.sh # VRRP check 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 使用了 eth1 做爲 VRRP IP 和 VIP 的 interface,並且 eth1 早在 TASK:AmphoraePostVIPPlug 就已經準備好了在 namespace amphora 中。
其中腳本 check_script.sh 用於檢查各個 Amphorae 的 HAProxy 的健康情況,以此做爲 VIP 漂移的判斷依據。
root@amphora-caa6ba0f-1a68-4f22-9be9-8521695ac4f4:~# cat /var/lib/octavia/vrrp/check_scripts/haproxy_check_script.sh haproxy-vrrp-check /var/lib/octavia/d367b5ec-24dd-44b3-b947-e0ff72c75e66.sock; exit $?
Amphora Instance 除了會運行 amphora-agent 和 keepalived 這兩個服務進程以外,確定還少不了 haproxy。由於 haproxy 只有在建立了 listener 以後纔會啓動,因此咱們等到分析 listener 穿件流程的時候再談。
自此,建立 loadbalancer 的流程就終於分析完了,一言以蔽之無非準備 amphorae 和將 amphorae 接入 vip-net 爾,但其間有不少細節值得咱們細細品味。
create listener flow 的 UML 圖:
從上圖可知,執行指令 openstack loadbalancer listener create --protocol HTTP --protocol-port 8080 lb-1
建立 Listener 時會執行到 Task:ListenersUpdate,在該任務中,AmphoraAPIClient 會調用:
PUT listeners/{amphora_id}/{listener_id}/haproxy
:更新 haproxy 配置文件PUT listeners/{listener_id}/reload
重啓 haproxy 服務進程因此,只有當爲 loadbalancer 建立 listener 時纔會啓動 haproxy 服務進程。還有一點補充的是建立 Listener 時也會執行 Task:UpdateVIP,這是由於 Lisenter 含有的協議及端口信息都須要被更新到 VIP 的安全組規則中。
登陸 amphora 查看 haproxy 的配置文件:
# file: /var/lib/octavia/1385d3c4-615e-4a92-aea1-c4fa51a75557/haproxy.cfg, Listener UUID: 1385d3c4-615e-4a92-aea1-c4fa51a75557 # 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 指定了監聽 HTTP 協議和端口 8080,因此 frontend section 也被渲染了的 bind 172.16.1.10:8080
和 mode http
配置項。
在 Amphora 操做系統啓動的 haproxy 進程是 haproxy-1385d3c4-615e-4a92-aea1-c4fa51a75557.service(ListenerUUID:1385d3c4-615e-4a92-aea1-c4fa51a75557),查看該進程的 service 配置:
# 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 中,從日誌能夠了解到它所作的事情就是調用了 /usr/sbin/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
除了 Listener 以外,Pool、Member、L7policy、L7rule 以及 Health Monitor 等對象的建立也會影響 haproxy 配置的變動。
create pool flow 的 UML 圖:
create pool flow 最關鍵的任務依然是 Task:ListenersUpdate,更新 haproxy 的配置文件。當執行指令 openstack loadbalancer pool create --protocol HTTP --lb-algorithm ROUND_ROBIN --listener 1385d3c4-615e-4a92-aea1-c4fa51a75557
爲 listener 建立一個 default pool,haproxy.cfg 就會添加一個 backend section,而且根據指令傳入的參數渲染 backend mode http
和 balance roundrobin
配置項。
# 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 default_backend 8196f752-a367-4fb4-9194-37c7eab95714 # UUID of pool timeout client 50000 backend 8196f752-a367-4fb4-9194-37c7eab95714 mode http balance roundrobin fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000
值得注意的是,建立 pool 時能夠指定一個 listener uuid 或 loadbalancer uuid。當指定了前者時,意味着爲 listener 指定了一個 default pool,listener 只能有一個 default pool,後續重複指定 default pool 則會觸發異常;當指定了 loadbalancer uuid 時,則建立了一個 shared pool。shared pool 能被同屬一個 loadbalancer 下的全部 listener 共享,常被用於輔助實現 l7policy 的功能。當 listener 的 l7policy 動做被設定爲爲「轉發至另外一個 pool」時,此時就能夠選定一個 shared pool。shared pool 能夠接受同屬 loadbalancer 下全部 listener 的轉發請求。執行指令建立一個 shared pool:
$ openstack loadbalancer pool create --protocol HTTP --lb-algorithm ROUND_ROBIN --loadbalancer 01197be7-98d5-440d-a846-cd70f52dc503 +---------------------+--------------------------------------+ | Field | Value | +---------------------+--------------------------------------+ | admin_state_up | True | | created_at | 2018-11-20T03:35:08 | | description | | | healthmonitor_id | | | id | 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 | | lb_algorithm | ROUND_ROBIN | | listeners | | | loadbalancers | 01197be7-98d5-440d-a846-cd70f52dc503 | | members | | | name | | | operating_status | OFFLINE | | project_id | 9e4fe13a6d7645269dc69579c027fde4 | | protocol | HTTP | | provisioning_status | PENDING_CREATE | | session_persistence | None | | updated_at | None | +---------------------+--------------------------------------+
注意,單純的建立 shared pool 不將其綁定到 listener 的話,haproxy.cfg 配置文件是不會當即更改的。
使用下述指令建立一個 member 到 default pool,選項指定雲主機所在的 subnet、ipaddress 以及接收數據轉發的 protocol-port。
[root@control01 ~]# openstack loadbalancer member create --subnet-id 2137f3fb-00ee-41a9-b66e-06705c724a36 --address 192.168.1.14 --protocol-port 80 8196f752-a367-4fb4-9194-37c7eab95714 +---------------------+--------------------------------------+ | Field | Value | +---------------------+--------------------------------------+ | address | 192.168.1.14 | | admin_state_up | True | | created_at | 2018-11-20T06:09:58 | | id | b6e464fd-dd1e-4775-90f2-4231444a0bbe | | name | | | operating_status | NO_MONITOR | | project_id | 9e4fe13a6d7645269dc69579c027fde4 | | protocol_port | 80 | | provisioning_status | PENDING_CREATE | | subnet_id | 2137f3fb-00ee-41a9-b66e-06705c724a36 | | updated_at | None | | weight | 1 | | monitor_port | None | | monitor_address | None | | backup | False | +---------------------+--------------------------------------+
在 octavia-api 層先會經過配置 CONF.networking.reserved_ips
驗證該 member 的 ipaddress 是否可用,驗證 member 所在的 subnet 是否存在,而後再進入 octavia-worker 的流程。
下面展開幾個關鍵的 TASKs。
TASK:CalculateDelta 輪詢 loadbalancer 下屬的 Amphorae 執行 Task:CalculateAmphoraDelta,計算 Amphora 現有的 NICs 集合與預期須要的 NICs 集合之間的 「差值」。
# file: /opt/rocky/octavia/octavia/controller/worker/tasks/network_tasks.py class CalculateAmphoraDelta(BaseNetworkTask): default_provides = constants.DELTA def execute(self, loadbalancer, amphora): LOG.debug("Calculating network delta for amphora id: %s", amphora.id) # Figure out what networks we want # seed with lb network(s) vrrp_port = self.network_driver.get_port(amphora.vrrp_port_id) desired_network_ids = {vrrp_port.network_id}.union( CONF.controller_worker.amp_boot_network_list) for pool in loadbalancer.pools: member_networks = [ self.network_driver.get_subnet(member.subnet_id).network_id for member in pool.members if member.subnet_id ] desired_network_ids.update(member_networks) nics = self.network_driver.get_plugged_networks(amphora.compute_id) # assume we don't have two nics in the same network actual_network_nics = dict((nic.network_id, nic) for nic in nics) del_ids = set(actual_network_nics) - desired_network_ids delete_nics = list( actual_network_nics[net_id] for net_id in del_ids) add_ids = desired_network_ids - set(actual_network_nics) add_nics = list(n_data_models.Interface( network_id=net_id) for net_id in add_ids) delta = n_data_models.Delta( amphora_id=amphora.id, compute_id=amphora.compute_id, add_nics=add_nics, delete_nics=delete_nics) return delta
簡單的說,首先獲得預期須要的 desired_network_ids 和已經存在的 actual_network_nics。而後計算出待刪除的 delete_nics 和待添加的 add_nics,並最終 returns 一個 Delta data models 到 Task:HandleNetworkDeltas 執行實際的 Amphora NICs 掛載和卸載。
Task:HandleNetworkDelta 負載基於 Amphora Delta 掛載和卸載 networks。
# file: /opt/rocky/octavia/octavia/controller/worker/tasks/network_tasks.py class HandleNetworkDelta(BaseNetworkTask): """Task to plug and unplug networks Plug or unplug networks based on delta """ def execute(self, amphora, delta): """Handle network plugging based off deltas.""" added_ports = {} added_ports[amphora.id] = [] for nic in delta.add_nics: interface = self.network_driver.plug_network(delta.compute_id, nic.network_id) port = self.network_driver.get_port(interface.port_id) port.network = self.network_driver.get_network(port.network_id) for fixed_ip in port.fixed_ips: fixed_ip.subnet = self.network_driver.get_subnet( fixed_ip.subnet_id) added_ports[amphora.id].append(port) for nic in delta.delete_nics: try: self.network_driver.unplug_network(delta.compute_id, nic.network_id) except base.NetworkNotFound: LOG.debug("Network %d not found ", nic.network_id) except Exception: LOG.exception("Unable to unplug network") return added_ports
最後返回 added_port return 給後續的 TASK:AmphoraePostNetworkPlug 使用。
Task:AmphoraePostNetworkPlug 負責將 member 所處網絡的 port 的信息注入到 network namespace 中。須要注意區分 AmphoraePostNetworkPlug 與 AmphoraePostVIPPlug,前者在 create menber flow 中生效,用於添加連通 member tenant-net 的 interface;後者在 create lb flow 中生效,用於添加連通 vip-net 的 interface。固然了,若是說 member 與 VIP 來自同一個網絡,則再也不須要爲 amphora 添加新的 inferface 了。
添加 member 以後再次查看 Amphora 的網絡狀況:
root@amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77:~# ip netns exec amphora-haproxy 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:12705 errors:0 dropped:0 overruns:0 frame:0 TX packets:613211 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:762300 (762.3 KB) TX bytes:36792968 (36.7 MB) 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 eth2 Link encap:Ethernet HWaddr fa:16:3e:18:23:7a inet addr:192.168.1.3 Bcast:192.168.1.255 Mask:255.255.255.0 inet6 addr: fe80::f816:3eff:fe18:237a/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1450 Metric:1 RX packets:8 errors:2 dropped:0 overruns:0 frame:2 TX packets:8 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:2156 (2.1 KB) TX bytes:808 (808.0 B)
配置文件以下:
# Generated by Octavia agent auto eth2 iface eth2 inet static address 192.168.1.3 broadcast 192.168.1.255 netmask 255.255.255.0 mtu 1450 post-up /sbin/iptables -t nat -A POSTROUTING -p udp -o eth2 -j MASQUERADE post-down /sbin/iptables -t nat -D POSTROUTING -p udp -o eth2 -j MASQUERADE
最後 haproxy 配置的變動依舊是由 Task:ListenersUpdate 完成。
# 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 default_backend 8196f752-a367-4fb4-9194-37c7eab95714 timeout client 50000 backend 8196f752-a367-4fb4-9194-37c7eab95714 mode http balance roundrobin fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000 server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14:80 weight 1
實際上,添加 member 就是在 backend(default pool)中加入了 server <member_id> 192.168.1.14:80 weight 1
項目,表示該雲主機做爲了 default pool 的一部分。
L7policy 對象的語義是用於描述轉發的動做類型(e.g. 轉發至 pool、轉發至 URL 或拒絕轉發)以及 L7rule 的容器,下屬於 Listener。
L7Rule 對象的語義是數據轉發的匹配域,描述了轉發的路由關係,下屬於 L7policy。
Health Monitor 對象用於對 Pool 中 Member 的健康狀態進行監控,本質就是一條數據庫記錄,描述了健康檢查的的規則,下屬於 Pool。
爲何將三者一同分析?從上述三個 UML 圖能夠感覺到 Create L7policy、L7rule、Health-Monitor 和 Pool 其實是很是相似,關鍵都是在於 TASK:ListenersUpdate 對 haproxy 配置文件內容的更新。因此,咱們主要經過一些例子來觀察 haproxy 配置文件的更改規律便可。
EXAMPLE 1. 轉發至 default pool
$ openstack loadbalancer healthmonitor create --name healthmonitor1 --type PING --delay 5 --timeout 10 --max-retries 3 8196f752-a367-4fb4-9194-37c7eab95714 $ openstack loadbalancer l7policy create --name l7p1 --action REDIRECT_TO_POOL --redirect-pool 8196f752-a367-4fb4-9194-37c7eab95714 1385d3c4-615e-4a92-aea1-c4fa51a75557 $ openstack loadbalancer l7rule create --type HOST_NAME --compare-type STARTS_WITH --value "server" 87593985-e02f-4880-b80f-22a4095c05a7
haproxy.cfg:
# 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 external-check 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 # 前端監聽 http://172.16.1.10:8080 bind 172.16.1.10:8080 mode http # ACL 轉發規則 acl 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 req.hdr(host) -i -m beg server # if ACL 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 知足,則轉發至 backend 8196f752-a367-4fb4-9194-37c7eab95714 use_backend 8196f752-a367-4fb4-9194-37c7eab95714 if 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 # 若是沒有匹配到任何 ACL 規則,則轉發至默認 backend 8196f752-a367-4fb4-9194-37c7eab95714 default_backend 8196f752-a367-4fb4-9194-37c7eab95714 timeout client 50000 backend 8196f752-a367-4fb4-9194-37c7eab95714 # 後端監聽協議爲 http mode http # 負載均衡算法爲 RR 輪詢 balance roundrobin timeout check 10s option external-check # 使用腳本 ping-wrapper.sh 對 server 進行健康檢查 external-check command /var/lib/octavia/ping-wrapper.sh fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000 # 後端真實服務器(real server),服務端口爲 80,監控檢查規則爲 inter 5s fall 3 rise 3 server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14:80 weight 1 check inter 5s fall 3 rise 3
健康檢查腳本,正如咱們設定的 PING 方式:
#!/bin/bash if [[ $HAPROXY_SERVER_ADDR =~ ":" ]]; then /bin/ping6 -q -n -w 1 -c 1 $HAPROXY_SERVER_ADDR > /dev/null 2>&1 else /bin/ping -q -n -w 1 -c 1 $HAPROXY_SERVER_ADDR > /dev/null 2>&1 fi
EXAMPLE 2. 轉發至 shared pool
$ openstack loadbalancer healthmonitor create --name healthmonitor1 --type PING --delay 5 --timeout 10 --max-retries 3 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 $ openstack loadbalancer l7policy create --name l7p1 --action REDIRECT_TO_POOL --redirect-pool 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 1385d3c4-615e-4a92-aea1-c4fa51a75557 $ openstack loadbalancer l7rule create --type HOST_NAME --compare-type STARTS_WITH --value "server" fb90a3b5-c97c-4d99-973e-118840a7a236
haproxy.cfg:
# 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 external-check 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 acl 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 req.hdr(host) -i -m beg server use_backend 8196f752-a367-4fb4-9194-37c7eab95714 if 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 acl c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 req.hdr(host) -i -m beg server use_backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 if c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 default_backend 8196f752-a367-4fb4-9194-37c7eab95714 timeout client 50000 backend 8196f752-a367-4fb4-9194-37c7eab95714 mode http balance roundrobin timeout check 10s option external-check external-check command /var/lib/octavia/ping-wrapper.sh fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000 server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14:80 weight 1 check inter 5s fall 3 rise 3 backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 mode http balance roundrobin timeout check 10s option external-check external-check command /var/lib/octavia/ping-wrapper.sh fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000 server 7da6f176-36c6-479a-9d86-c892ecca6ae5 192.168.1.6:80 weight 1 check inter 5s fall 3 rise 3
可見,在爲 listener 添加了 shared pool 以後,會在增長一個 backend section 對應 shared pool 822f78c3-ea2c-4770-bef0-e97f1ac2eba8。
繼續看看 amphora-agent 與 Octavia Controller Worker 是如何創建安全通訊的。咱們不妨先思考一下:爲何 Octavia 須要自建 CA 證書?
Note: For production use the ca issuing the client certificate and the ca issuing the server certificate need to be different so a hacker can’t just use the server certificate from a compromised amphora to control all the others.
能夠想象,若是 Octavia 與 Dashboard 共用證書,則不異於將 OpenStack Management/API Network 公示於衆。簡而言之,Octavia 自建 CA 證書主要有兩個必要:
Octavia 一樣提供了自動化腳本,使用 OpenSSL 來建立 CA 中心。執行下述指令便可完成:
$ source /opt/rocky/octavia/bin/create_certificates.sh /etc/octavia/certs/ /opt/rocky/octavia/etc/certificates/openssl.cnf
對於 CA 中心,這裏多說兩句。所謂 CA,其外在表現就是一個 Directory,包含了各類類型的證書;而內在表現就是一個第三方信任機構,提供證書籤發和管理服務,能夠有效解決了非對稱加密系統的中間人攻擊問題。更多詳情轉《使用 OpenSSL 自建 CA 並簽發證書》,這裏再也不贅述。
Octavia 自建的 CA 中心:
$ ll /etc/octavia/certs/ total 44 -rw-r--r-- 1 stack stack 1294 Oct 26 12:51 ca_01.pem -rw-r--r-- 1 stack stack 989 Oct 26 12:51 client.csr -rw-r--r-- 1 stack stack 1708 Oct 26 12:51 client.key -rw-r--r-- 1 stack stack 4405 Oct 26 12:51 client-.pem -rw-r--r-- 1 stack stack 6113 Oct 26 12:51 client.pem -rw-r--r-- 1 stack stack 71 Oct 26 12:51 index.txt -rw-r--r-- 1 stack stack 21 Oct 26 12:51 index.txt.attr -rw-r--r-- 1 stack stack 0 Oct 26 12:51 index.txt.old drwxr-xr-x 2 stack stack 20 Oct 26 12:51 newcerts drwx------ 2 stack stack 23 Oct 26 12:51 private -rw-r--r-- 1 stack stack 3 Oct 26 12:51 serial -rw-r--r-- 1 stack stack 3 Oct 26 12:51 serial.old
下面列舉與 CA 認證相關的配置項:
# 應用於 create new amphora flow 的 TASK:GenerateServerPEMTask,生成 amphora 服務端證書 [certificates] ca_private_key_passphrase = foobar ca_private_key = /etc/octavia/certs/private/cakey.pem ca_certificate = /etc/octavia/certs/ca_01.pem # 應用於 AmphoraAPIClient,拿着 client.pem(包含 Server 證書、Server 私鑰)和 CA 證書(公鑰)向 amphora-agent 發起 SSL 通訊 [haproxy_amphora] server_ca = /etc/octavia/certs/ca_01.pem client_cert = /etc/octavia/certs/client.pem # 應用於 Task:CertComputeCreate,指定 CA 證書的路徑 [controller_worker] client_ca = /etc/octavia/certs/ca_01.pem
先簡單梳理一下創建 SSL 通訊的流程,而後再細看具體實現:
首先看爲 amphora 生成證書的實現:
# file: /opt/rocky/octavia/octavia/controller/worker/tasks/cert_task.py class GenerateServerPEMTask(BaseCertTask): """Create the server certs for the agent comm Use the amphora_id for the CN """ def execute(self, amphora_id): cert = self.cert_generator.generate_cert_key_pair( cn=amphora_id, validity=CERT_VALIDITY) return cert.certificate + cert.private_key
Octavia Certificates 提供了 local_cert_generator(default)
和 anchor_cert_generator
兩種證書生成器,經過配置 [certificates] cert_generator
選用。
# file: /opt/rocky/octavia/octavia/certificates/generator/local.py @classmethod def generate_cert_key_pair(cls, cn, validity, bit_length=2048, passphrase=None, **kwargs): pk = cls._generate_private_key(bit_length, passphrase) csr = cls._generate_csr(cn, pk, passphrase) cert = cls.sign_cert(csr, validity, **kwargs) cert_object = local_common.LocalCert( certificate=cert, private_key=pk, private_key_passphrase=passphrase ) return cert_object
上述 LocalCertGenerator.generate_cert_key_pair 的語義是:
屬於常規的證書建立流程,與 create_certificates.sh 腳本的區別在於,Octavia Certificates 應用了 cryptography 庫來實現。
TASK:GenerateServerPEMTask 最終 return 了 Amphora 私鑰和證書,而後被 TASK:CertComputeCreate 經過 Nova 的 userdata 和 Nova Store metadata on a configuration drive 機制將這些文件注入到 amphora instance 內。登陸 amphora 便可查看這些文件,路徑記錄在配置文件中:
# file: /etc/octavia/amphora-agent.conf [amphora_agent] agent_server_ca = /etc/octavia/certs/client_ca.pem agent_server_cert = /etc/octavia/certs/server.pem
Gunicorn HTTP Server 啓動時就會將證書文件加載, 加載證書的 options 以下:
options = { 'bind': bind_ip_port, 'workers': 1, 'timeout': CONF.amphora_agent.agent_request_read_timeout, 'certfile': CONF.amphora_agent.agent_server_cert, 'ca_certs': CONF.amphora_agent.agent_server_ca, 'cert_reqs': True, 'preload_app': True, 'accesslog': '/var/log/amphora-agent.log', 'errorlog': '/var/log/amphora-agent.log', 'loglevel': 'debug', }
key:certfile
:上傳 amphora-agent 的私鑰和證書。key:ca_certs
:上傳 amphora-agent 的 CA 證書class AmphoraAPIClient(object): def __init__(self): super(AmphoraAPIClient, self).__init__() ... self.session = requests.Session() self.session.cert = CONF.haproxy_amphora.client_cert self.ssl_adapter = CustomHostNameCheckingAdapter() self.session.mount('https://', self.ssl_adapter) ... def request(self, method, amp, path='/', timeout_dict=None, **kwargs): ... LOG.debug("request url %s", path) _request = getattr(self.session, method.lower()) _url = self._base_url(amp.lb_network_ip) + path LOG.debug("request url %s", _url) reqargs = { 'verify': CONF.haproxy_amphora.server_ca, 'url': _url, 'timeout': (req_conn_timeout, req_read_timeout), } reqargs.update(kwargs) headers = reqargs.setdefault('headers', {}) ...
上述代碼是 requests 庫啓用 HTTPS 請求的常規實現:
self.session.cert
:上傳 Octavia(AmphoraAPIClient)的私鑰和證書reqargs = {'verify': CONF.haproxy_amphora.server_ca, ...}
:發送攜帶的 CA 證書的請求最後簡單終結一下 Octavia 經過自建 CA 實現 Amphora 與 Octavia Controller Worker 進行 HTTPS 通訊的流程:當 AmphoraAPIClient 第一次請求 amphora-agent 時,AmphoraAPIClient 首先會拉下 amphora-agent 證書,而後與本地的 CA 證書進行驗證。由於 amphora-agent 證書也是使用 CA 私鑰加密的,因此能夠被 CA 證書解密。若經過驗證則得到 amphora-agent 的公鑰,與 amphora-agent 上傳的私鑰比對,創建 SSL 通道。
Health Manager - This subcomponent monitors individual amphorae to ensure they are up and running, and otherwise healthy. It also handles failover events if amphorae fail unexpectedly.
簡單的說,Health Manager 用於監控每一個 amphora 的監控狀態,若是 amphora 出現故障,則啓動故障轉移流程,以此來保障 LB 的高可用性。
那麼掌握 Health Manager Service,就是要搞清楚它是如何監控 amphora 的健康狀態的,而後再弄明白故障轉移的流程細節。
仍是從程序入口(octavia/cmd/health_manager.py)看起 ,啓動 octavia-health-manager service 加載了 UDPStatusGetter.check()
和 HealthManager.health_check()
兩個 method,咱們先看看前者的實現。
# file: /opt/rocky/octavia/octavia/amphorae/drivers/health/heartbeat_udp.py class UDPStatusGetter(object): """This class defines methods that will gather heatbeats The heartbeats are transmitted via UDP and this class will bind to a port and absorb them """ def __init__(self): self.key = cfg.CONF.health_manager.heartbeat_key self.ip = cfg.CONF.health_manager.bind_ip self.port = cfg.CONF.health_manager.bind_port self.sockaddr = None LOG.info('attempting to listen on %(ip)s port %(port)s', {'ip': self.ip, 'port': self.port}) self.sock = None self.update(self.key, self.ip, self.port) self.executor = futures.ProcessPoolExecutor( max_workers=cfg.CONF.health_manager.status_update_threads) self.repo = repositories.Repositories().amphorahealth def update(self, key, ip, port): """Update the running config for the udp socket server :param key: The hmac key used to verify the UDP packets. String :param ip: The ip address the UDP server will read from :param port: The port the UDP server will read from :return: None """ self.key = key for addrinfo in socket.getaddrinfo(ip, port, 0, socket.SOCK_DGRAM): ai_family = addrinfo[0] self.sockaddr = addrinfo[4] if self.sock is not None: self.sock.close() self.sock = socket.socket(ai_family, socket.SOCK_DGRAM) self.sock.settimeout(1) self.sock.bind(self.sockaddr) if cfg.CONF.health_manager.sock_rlimit > 0: rlimit = cfg.CONF.health_manager.sock_rlimit LOG.info("setting sock rlimit to %s", rlimit) self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, rlimit) break # just used the first addr getaddrinfo finds if self.sock is None: raise exceptions.NetworkConfig("unable to find suitable socket")
Class:UDPStatusGetter 在 octavia-health-manager service 中負責接收從 amphora 發送過來的 heatbeats(心跳包),而後 prepare heatbeats 中的數據並持久化到數據庫中。從 __init__()
看出 amphora 與 octavia-health-manager service 的通訊實現是 UDP socket,socket 爲 (CONF.health_manager.bind_ip, CONF.health_manager.bind_port)
。
NOTE:這裏須要強調一下 amphora 與 octavia-health-manager service 通訊的網絡拓撲細節。
若是部署 Octavia 時,直接使用 ext-net 做爲 octavia 的 「lb-mgmt-net」,那麼 CONF.health_manager.bind_ip 應該是物理主機的 IP 地址,amphora 與 octavia-health-manager service 直接經過 OpenStack Management Network 進行通訊。不過這種方式,amphora 會佔用 ext-net 的 fixed ip,因此在生產環境中並不建議使用該方式。
若是部署 Octavia 時,使用另外建立的 tenant network 做爲 lb-mgmt-net,那麼 CONF.health_manager.bind_ip 就應該是 lb-mgmt-net IP pool 中的地址。那麼就須要解決 lb-mgmt-net 與 OpenStack Management Network 互通的問題。其中 devstack 的作法是將 lb-mgmt-net 的一個 port 掛載到 ex-int 上,lb-mgmt-net 中的 amphora 就能夠經過這個 port 與運行在物理主機上的 octavia-health-manager service 進行通訊了。而在生產環境中,就須要結合現場環境的網絡環境由網管進行配置了。
Devstack 打通本地網絡的指令:
$ neutron port-create --name octavia-health-manager-standalone-listen-port \ --security-group <lb-health-mgr-sec-grp> \ --device-owner Octavia:health-mgr \ --binding:host_id=<hostname> lb-mgmt-net \ --tenant-id <octavia service> $ ovs-vsctl --may-exist add-port br-int o-hm0 \ -- set Interface o-hm0 type=internal \ -- set Interface o-hm0 external-ids:iface-status=active \ -- set Interface o-hm0 external-ids:attached-mac=<Health Manager Listen Port MAC> \ -- set Interface o-hm0 external-ids:iface-id=<Health Manager Listen Port ID> # /etc/octavia/dhcp/dhclient.conf request subnet-mask,broadcast-address,interface-mtu; do-forward-updates false; $ ip link set dev o-hm0 address <Health Manager Listen Port MAC> $ dhclient -v o-hm0 -cf /etc/octavia/dhcp/dhclient.conf o-hm0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1450 inet 192.168.0.4 netmask 255.255.255.0 broadcast 192.168.0.255 inet6 fe80::f816:3eff:fef0:b9ee prefixlen 64 scopeid 0x20<link> ether fa:16:3e:f0:b9:ee txqueuelen 1000 (Ethernet) RX packets 1240893 bytes 278415460 (265.5 MiB) RX errors 0 dropped 45 overruns 0 frame 0 TX packets 417078 bytes 75842972 (72.3 MiB) TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
回到主題,UDPStatusGetter.check() 的實現是:
def check(self): try: obj, srcaddr = self.dorecv() except socket.timeout: # Pass here as this is an expected cycling of the listen socket pass except exceptions.InvalidHMACException: # Pass here as the packet was dropped and logged already pass except Exception as e: LOG.warning('Health Manager experienced an exception processing a' 'heartbeat packet. Ignoring this packet. ' 'Exception: %s', e) else: self.executor.submit(update_health, obj, srcaddr) self.executor.submit(update_stats, obj, srcaddr)
self.dorecv()
接收數據self.executor.submit(update_health, obj, srcaddr)
將 health 持久化到 table amphora_healthself.executor.submit(update_stats, obj, srcaddr)
將 stats 持久化到 table listener_statistics繼續看 amphora 是怎麼發出 heatbeats。
# file: /opt/rocky/octavia/octavia/cmd/agent.py def main(): # comment out to improve logging service.prepare_service(sys.argv) gmr.TextGuruMeditation.setup_autorun(version) health_sender_proc = multiproc.Process(name='HM_sender', target=health_daemon.run_sender, args=(HM_SENDER_CMD_QUEUE,)) health_sender_proc.daemon = True health_sender_proc.start() # Initiate server class server_instance = server.Server() bind_ip_port = utils.ip_port_str(CONF.haproxy_amphora.bind_host, CONF.haproxy_amphora.bind_port) options = { 'bind': bind_ip_port, 'workers': 1, 'timeout': CONF.amphora_agent.agent_request_read_timeout, 'certfile': CONF.amphora_agent.agent_server_cert, 'ca_certs': CONF.amphora_agent.agent_server_ca, 'cert_reqs': True, 'preload_app': True, 'accesslog': '/var/log/amphora-agent.log', 'errorlog': '/var/log/amphora-agent.log', 'loglevel': 'debug', } AmphoraAgent(server_instance.app, options).run()
在啓動 amphora-agent 服務進程時,加載了 health_daemon.run_sender
這就是 amphora 向 octavia-health-manager service 發送心跳包的實現。
# file: /opt/rocky/octavia/octavia/amphorae/backends/health_daemon/health_daemon.py def run_sender(cmd_queue): LOG.info('Health Manager Sender starting.') sender = health_sender.UDPStatusSender() keepalived_cfg_path = util.keepalived_cfg_path() keepalived_pid_path = util.keepalived_pid_path() while True: try: # If the keepalived config file is present check # that it is running, otherwise don't send the health # heartbeat if os.path.isfile(keepalived_cfg_path): # 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) message = build_stats_message() sender.dosend(message) except IOError as e: # Missing PID file, skip health heartbeat if e.errno == errno.ENOENT: LOG.error('Missing keepalived PID file %s, skipping health ' 'heartbeat.', keepalived_pid_path) else: LOG.error('Failed to check keepalived and haproxy status due ' 'to exception %s, skipping health heartbeat.', e) except OSError as e: # Keepalived is not running, skip health heartbeat if e.errno == errno.ESRCH: LOG.error('Keepalived is configured but not running, ' 'skipping health heartbeat.') else: LOG.error('Failed to check keepalived and haproxy status due ' 'to exception %s, skipping health heartbeat.', e) except Exception as e: LOG.error('Failed to check keepalived and haproxy status due to ' 'exception %s, skipping health heartbeat.', e) try: cmd = cmd_queue.get_nowait() if cmd == 'reload': LOG.info('Reloading configuration') CONF.reload_config_files() elif cmd == 'shutdown': LOG.info('Health Manager Sender shutting down.') break except queue.Empty: pass time.sleep(CONF.health_manager.heartbeat_interval)
run_sender function 調用了 build_stats_message()
構建 heatbeats,而後調用 UDPStatusSender.dosend()
來發送數據。注意,當 keepalived 服務進程沒有正常運行的時候,是不會發送 heatbeats 的。也就是說 keepalived 不正常的 amphora 就會被看成故障 amphora 處理。數據發送依舊使用了 UDP socket,目標 URL 由 CONF.health_manager.controller_ip_port_list
設定。
# file: /etc/octavia/octavia.conf [health_manager] bind_port = 5555 bind_ip = 192.168.0.4 controller_ip_port_list = 192.168.0.4:5555
簡而言之,octavia-health-manager 與 amphora-agent 之間實現了週期性的心跳協議來監控 amphora 的健康狀態。
故障轉移機制由 health_manager.HealthManager.health_check()
週期性監控和觸發。
health_check method 週期性的從 table amphora_health 獲取所謂的 stale amphora 記錄,也就是過時沒有上報 heatbeats 被斷定爲故障的 amphora:
# file: /opt/rocky/octavia/octavia/db/repositories.py def get_stale_amphora(self, session): """Retrieves a stale amphora from the health manager database. :param session: A Sql Alchemy database session. :returns: [octavia.common.data_model] """ timeout = CONF.health_manager.heartbeat_timeout expired_time = datetime.datetime.utcnow() - datetime.timedelta( seconds=timeout) amp = session.query(self.model_class).with_for_update().filter_by( busy=False).filter( self.model_class.last_update < expired_time).first() if amp is None: return None amp.busy = True return amp.to_data_model()
若是存在 stale amphora 而且 loadbalancer status 不處於 PENDING_UPDATE,那麼就會進入 failover amphora 流程,failover amphora 的 taskflow 是 self._amphora_flows.get_failover_flow
。
failover 的 UML 圖:
很明顯,整個 failover_flow 分爲 delete old amphora 和 get a new amphora 兩大部分。並且大部分的 TASKs 咱們前文都有分析,因此下面簡單羅列任務的功能便可。
NOTE:若是故障的 amphora 是一個 free amphora,那麼直接刪除掉便可。
簡單終結一下 amphora failover 的思路,首先刪除故障的 old amphora,而後獲取一個可用的 new amphora,將 old 的關聯繫數據(e.g. database)以及對象(e.g. 網絡模型)轉移的 new。
須要注意的是:
It seems intuitive to boot an amphora prior to deleting the old amphora, however this is a complicated issue. If the target host (due to anit-affinity) is resource constrained, this will fail where a post-delete will succeed. Since this is async with the API it would result in the LB ending in ERROR though the amps are still alive. Consider in the future making this a complicated try-on-failure-retry flow, or move upgrade failovers to be synchronous with the API. For now spares pool and act/stdby will mitigate most of this delay.
雖然故障轉移就是 delete old amphora 而後 get new amphora,但實際上過程倒是複雜的。例如:在刪除 old amphora 成功後,建立 new amphora 卻可能會因爲資源限制致使失敗;再例如:因爲異步的 API 調用,因此也有可能 create new amphora 成功了,但 loadbalancer 的狀態已變成 ERROR。對於異步 API 的問題,未來可能會考慮使用同步 API 來解決,但就目前來講更加依賴於 space amphora 來緩解異步建立的時延問題。
關閉 MASTER amphora 的電源,octavia-health-manager service 觸發 amphora failover。
Nov 22 11:22:31 control01 octavia-health-manager[29147]: INFO octavia.controller.healthmanager.health_manager [-] Stale amphora's id is: cd444019-ce8f-4f89-be6b-0edf76f41b77 Nov 22 11:22:31 control01 octavia-health-manager[29147]: INFO octavia.controller.healthmanager.health_manager [-] Waiting for 1 failovers to finish
old amphorae:
2ddc4ba5-b829-4962-93d8-562de91f1dab | amphora-4ff5d6fe-854c-4022-8194-0c6801a7478b | ACTIVE | lb-mgmt-net=192.168.0.23 | amphora-x64-haproxy | m1.amphora | | b237b2b8-afe4-407b-83f2-e2e60361fa07 | amphora-bcff6f9e-4114-4d43-a403-573f1d97d27e | ACTIVE | lb-mgmt-net=192.168.0.11 | amphora-x64-haproxy | m1.amphora | | 46eccf47-be10-47ec-89b2-0de44ea3caec | amphora-cd444019-ce8f-4f89-be6b-0edf76f41b77 | ACTIVE | lb-mgmt-net=192.168.0.9; web-server-net=192.168.1.3; lb-vip-net=172.16.1.3 | amphora-x64-haproxy | m1.amphora | | bc043b23-d481-45c4-9410-f7b349987c98 | amphora-a1c1ba86-6f99-4f60-b469-a4a29d7384c5 | ACTIVE | lb-mgmt-net=192.168.0.3; web-server-net=192.168.1.12; lb-vip-net=172.16.1.7 | amphora-x64-haproxy | m1.amphora |
new amphoras:
| 712ff785-c082-4b53-994c-591d1ec0bf7b | amphora-caa6ba0f-1a68-4f22-9be9-8521695ac4f4 | ACTIVE | lb-mgmt-net=192.168.0.13 | amphora-x64-haproxy | m1.amphora | | 2ddc4ba5-b829-4962-93d8-562de91f1dab | amphora-4ff5d6fe-854c-4022-8194-0c6801a7478b | ACTIVE | lb-mgmt-net=192.168.0.23; web-server-net=192.168.1.4; lb-vip-net=172.16.1.3 | amphora-x64-haproxy | m1.amphora | | b237b2b8-afe4-407b-83f2-e2e60361fa07 | amphora-bcff6f9e-4114-4d43-a403-573f1d97d27e | ACTIVE | lb-mgmt-net=192.168.0.11 | amphora-x64-haproxy | m1.amphora | | bc043b23-d481-45c4-9410-f7b349987c98 | amphora-a1c1ba86-6f99-4f60-b469-a4a29d7384c5 | ACTIVE | lb-mgmt-net=192.168.0.3; web-server-net=192.168.1.12; lb-vip-net=172.16.1.7 | amphora-x64-haproxy | m1.amphora |
new amphora haproxy config:
# 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 external-check defaults log global retries 3 option redispatch peers 1385d3c4615e4a92aea1c4fa51a75557_peers peer 3dVescsRZ-RdRBfYVLW6snVI9gI 172.16.1.3:1025 peer l_Ustq0qE-h-_Q1dlXLXBAiWR8U 172.16.1.7:1025 frontend 1385d3c4-615e-4a92-aea1-c4fa51a75557 option httplog maxconn 1000000 bind 172.16.1.10:8080 mode http acl 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 req.hdr(host) -i -m beg server use_backend 8196f752-a367-4fb4-9194-37c7eab95714 if 8d9b8b1e-83d7-44ca-a5b4-0103d5f90cb9 acl c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 req.hdr(host) -i -m beg server use_backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 if c76f36bc-92c0-4f48-8d57-a13e3b1f09e1 default_backend 8196f752-a367-4fb4-9194-37c7eab95714 timeout client 50000 backend 8196f752-a367-4fb4-9194-37c7eab95714 mode http balance roundrobin timeout check 10s option external-check external-check command /var/lib/octavia/ping-wrapper.sh fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000 server b6e464fd-dd1e-4775-90f2-4231444a0bbe 192.168.1.14:80 weight 1 check inter 5s fall 3 rise 3 backend 822f78c3-ea2c-4770-bef0-e97f1ac2eba8 mode http balance roundrobin timeout check 10s option external-check external-check command /var/lib/octavia/ping-wrapper.sh fullconn 1000000 option allbackups timeout connect 5000 timeout server 50000 server 7da6f176-36c6-479a-9d86-c892ecca6ae5 192.168.1.6:80 weight 1 check inter 5s fall 3 rise 3
new amphora keepalived config:
vrrp_script check_script { script /var/lib/octavia/vrrp/check_script.sh interval 5 fall 2 rise 2 } vrrp_instance 01197be798d5440da846cd70f52dc503 { state MASTER interface eth1 virtual_router_id 1 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 unicast_peer { 172.16.1.7 } virtual_ipaddress { 172.16.1.10 } track_script { check_script } }
new amphora 的配置文件和網絡設置與 old amphora 一致,遷移成功。
在用戶問過最多的問題中,LBaaS v2 API 與 Octavia v2 API 傻傻分不清當屬排行第一位,這裏簡單對上述概念作一個標誌性的區分。