1. 程式人生 > >Octavia 的實現與分析(OpenStack Rocky)

Octavia 的實現與分析(OpenStack Rocky)

目錄

文章目錄

Octavia

Octavia is an open source, operator-scale load balancing solution designed to work with OpenStack.

自 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 應該作為一個獨立專案得到長足的發展,事實也是如此。

本篇基於 Rocky,記錄、分析 Octavia 作為 OpenStack LBaaS 的抽象設計,開發性設計及其程式碼實現,並從中感受社群開發者們對 Octavia 的寄予。

基本物件概念

LBaaS:對於 OpenStack 平臺而言,LB(負載均衡)被作為一種服務提供給使用者,使用者得以按需地、隨時地獲取可配置的業務負載均衡方案,這就是所謂 Load Balancing as a Service。

loadbalancer:負載均衡服務的根物件,使用者對負載均衡的定義、配置和操作都基於此。

VIP:與 loadbalancer 關聯的 IP 地址,每個 loadbalancer 最起碼有一個 VIP,作為外部對後端業務叢集的標準訪問入口。

Listener:下屬於 loadbalancer 的監聽器,使用者可配置監聽外部對 VIP 的訪問型別(e.g. 協議、埠)。

Pool:後端的真實業務雲主機叢集域,一般的,使用者會根據雲主機的業務型別進行劃分。

Member:業務雲主機,下屬於 Pool,對應傳統負載均衡體系中的 Real Server。

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 的設計思路:

  • Amphora 作為負載均衡器軟體(HAProxy)和高可用支撐(Keepalived)的執行載體,通過 Agent 與 Octavia 服務程序通訊
  • Octavia 服務程序接收到使用者的 loadbalancer 和 VIP 的配置引數,通過 Agent 動態修改 haproxy、keepalived 的配置檔案
  • 將 Member 處於的 Subnet 接入 Amphora,Amphora 通過 Member Socket (IP, Port) 分發請求資料包。

在這裡插入圖片描述

這裡再補充一下與 Octavia 相關的 image 和 security group 的內容。Amphora Instance 使用特定的映象啟動,Octavia 提供了專門的映象製作指令碼,支援 centos 和 ubuntu 兩種作業系統,也支援設定 password,不過在生成環境中還是建議使用 keypair 登入。至於安全組,從上圖可以看出 Amphora 的安全組最起碼要滿足 ingress:UDP/5555 和 egress:TCP/9443 兩條規則。

使用 amphora image 的步驟

  • Step 1. 上傳 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
  • Step 2. 配置 amphora image
    上傳 amphora image 後還需要配置到 [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 的步驟

  • Step 1. 建立 amphora 使用的安全組
$ 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
  • Step 2. 配置 amphora security group
[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 個元件。

    • Octavia Worker:負責完成 API 請求,是 Octavia 主幹功能的執行者。
    • Health Manager:負責保證負載均衡器的高可用性。
    • Housekeeping Manager:名副其實的 Housekeeping(家政)服務,保障 Octavia 的健康執行。實現了 SpaceAmphora、DatabaseCleanup 和 CertRotation。

NOTE:需要特別說明的是,架構圖中只給出了 Amphora 一種 LB Provider,但 Octavia 的 Driver 設計實際上是支援多種 LB Provider 的(e.g. F5)。其實社群一直有計劃要將 openstack/neutron-lbaas repo 實現的 drivers 遷移到 Octavia,只是一直缺人做。

服務程序清單

服務清單就是軟體架構的具象表現:

  • octavia-api
  • octaiva-worker
  • octavia-health-manager
  • octavia-housekeeping

程式碼結構

在這裡插入圖片描述

下面列舉一些關鍵的目錄:

  • amphora:amphora rest api 和 amphora-agent 的實現
  • api:Octavia API 的實現
  • certificates:CA 認證的實現,支援 amphora 與 Octavia Worker 的 HTTPS 通訊和 TLS 功能
  • compute:實現了 Compute Driver 的抽象和 novaclient 的封裝
  • network:實現了 Network Driver 的抽象和 neutronclient 的封裝
  • db:ORM 的實現
  • policies:定義了 API 請求的鑑權策略

繼續展開 controller 目錄:

在這裡插入圖片描述

  • healthmanager:Health Manager 的實現
  • housekeeping Manager:HouseKeeping 的實現
  • queue:內部 RPC 通訊的實現,應用了 cotyledon 框架和 oslo_messaging
    • 生產者:api/handlers/queue/producer.py
    • 消費者:controller/queue/consumer.py
  • worker:Octavia Worker 的實現,應用了 taskflow 框架
    • flows:任務流的封裝,每個任務都會被定義為一個 flow
    • tasks:任務的封裝,任務邏輯的抽象,使得任務可以高度重用

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 受到歡迎的原因之一,也從一個方面回答了上文中提出的問題:為什麼要抽象出這些物件?

loadbalancer 建立流程分析

要說最典型的 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 請求後處理的事情:

  1. 請求鑑權,判斷使用者是否有許可權執行 loadbalancer 的建立。
  2. 驗證 VIP 及其相關物件(e.g. port, subnet, network)是否可用,這裡可以通過 config secition [networking] 來配置Allow/disallow specific network object types when creating VIPs.
  3. 檢查使用者 Project 的 LB Quota,可以通過 config section [quotas] 來配置預設 Quota(e.g. 指定 Project1 只能建立 3 個 loadbalancer)。
  4. 建立 table load_balancer 和 vip 的資料庫記錄。
  5. 呼叫 Amphora driver(default lb provider)建立 VIP 對應的 port,並將 Port、VIP、LB 三者的資料庫記錄關聯起來。
  6. 以 Graph flow(圖流)的方式建立 loadbalancer 下屬的 Listeners 和 Pools。
  7. 準備傳入 create_loadbalancer_flow 的 stores。
  8. 非同步呼叫 octavia-worker service 執行 create_loadbalancer_flow。

其中有幾點值得注意:

  • loadbalancer quota 依舊是通過指令 openstack quota set 設定。
  • 雖然 openstack loadbalancer create 指令沒有給出 --listeners or --pools 之類的選項 在建立 loadbalancer 的同時建立下屬的 listeners 及 pools,但實際上 POST /v2.0/lbaas/loadbalancers 是可以接收這兩個屬性的。所以 Dashboard 的 UI/UE 可以作此項優化 。
  • 如果指定的 VIP port 不存在,那麼 octavia-api service 會先呼叫 neutronclient 建立並命名為 loadbalancer-<load_balancer_id>,所以你會在 vip-net 可以看見此類 Port。
  • VIP 支援通過 network、subnet 和 port 的任意一種方式進行建立,還支援設定 VIP 的 Qos。

Create LB 的 Octavia Controller Worker UML 圖

在這裡插入圖片描述

展開 3. get_create_load_balancer_flow 的 UML 圖

在這裡插入圖片描述

可以看出 create loadbalancer flow 主要有兩點:

  1. create loadbalancer topology
  2. create networking for amphora(e)

首先解釋第一點,所謂 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 相關的幾個關鍵任務:

  • network_tasks.AllocateVIP
  • network_tasks.PlugVIP
  • amphora_driver_tasks.AmphoraePostVIPPlug
  • amphora_driver_tasks.AmphoraVRRPUpdate
  • amphora_driver_tasks.AmphoraVRRPStart

這些都是需要我們重點關注的實現,因為在實際操作中,筆者認為 Octavia Networking 是實現的重點,也是出現問題的高發區,只有掌握了底層實現才可以更好的精準定位問題。

network_tasks.AllocateVIP

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 落庫持久化。

network_tasks.PlugVIP

AllocateVIP 負載從 Neutron 分配 VIP,PlugVIP 則負責將 VIP 插入到 Amphora。

PlugVIP 的 UML 圖

在這裡插入圖片描述

在 PlugVIP 的邏輯實現主要有兩個方面:

  1. update VIP port 的 security_group_rules。因為外部是通過 VIP 來訪問業務的,而且 Listener 也是依附於 VIP 的,所以 VIP 的安全組規則實際上是動態的。例如:為 loadbalancer 添加了一個 HTTP:8080 的 Listener,則會在相應的 VIP 上 Upload ingress HTTP:8080 規則。

  2. 輪詢 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

上文我們提到過,Amphora 本質是一個 instance,作為 HAProxy 和 Keepalived 的執行載體。筆者認為 Amphora 是一個非常經典的類 Proxy 實現,在解決「Proxy 應該如何與中控(Octavia Controller Worker) 進行安全通訊?如何自定義心跳協議?如何降低對宿主機執行環境的影響?」這些問題上作出了優秀的示範,非常值得借鑑和研究。

amphora-agent 與 Octavia Controller Worker 的通訊模型圖

在這裡插入圖片描述

首先我們來看看 amphora-agent 與 AmphoraAPIClient 是如何建立通訊的。

Amphora Agent

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

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 的通訊架構:

  • Octavia API:提供面向外部的 REST API 通訊
  • Queue:提供面向內部的 RPC 通訊
  • Amphora Agent:提供 Amphora 與 Octavia Controller Worker 之間的 REST API 通訊

在這裡插入圖片描述

AmphoraePostVIPPlug

回過頭來繼續看 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 通訊的埠:

[email protected]:~# 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。

[email protected]:~# 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 的配置為:

[email protected]:~# ip netns exec amphora-haproxy cat /etc/network/interfaces.d/eth1
auto eth1
iface eth1 inet dhcp
[email protected]:~# 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

啟動 Keepalived 服務程序

只有當 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

從該檔案可以得知:

  1. 真正的 keepalived 服務程序啟動在了 namespace amphora-haproxy 中。
  2. keepalived 的配置檔案路勁為 /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 漂移的判斷依據。

[email protected]:~# 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 爾,但其間有很多細節值得我們細細品味。

listener 建立流程分析

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 的安全組規則中。

啟動 haproxy 服務程序

登入 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:8080mode 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 配置的變更。

pool 建立流程分析

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 httpbalance 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 建立流程分析

使用下述指令建立一個 member 到 default pool,選項指定雲主機所在的 subnet、ipaddress 以及接收資料轉發的 protocol-port。

[[email protected] ~]# 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。

CalculateDelta

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
            
           

相關推薦

Octavia實現分析OpenStack Rocky

目錄 文章目錄 目錄 Octavia 基本物件概念 基本使用流程 軟體架構 服務程序清單 程式碼結構 loadbalancer 建立流程分析 network_tasks.A

octavia實現分析·原理,架構基本流程

【瞭解】 其實說白了,Octavia就是將使用者的API請求經過邏輯處理,轉換成Haproxy或者Nginx的配置引數,下發到amphora虛機中。 Octavia的內部實現中,邏輯流程的處理主要使用TaskFlow庫。     【基本概念】 ·LBaas

Centos7 Openstack nova模組安裝分析Queens版本

一、Nova框架 Nova Api              :提供統一Rest-api風格API介面,作為Nova元件的入口,接受使用者的請求 Nova Scheduler  :負責排程,將例項分配到具體計算節點 Nova Conductor  :負責Nova與資料庫進行

X.509證書的讀取操作分析Python版

X.509 1. 內容解析 該部分所使用知識均來自於維基百科與 CSDN 1.1 定義 X.509 是密碼學裡公鑰證書的格式標準。 X.509 證書己應用在包括 TLS/SSL在內的眾多 Intenet 協議裡.同時它也用在很多非線上應用場景裡,比如電子簽名服務。

X.509證書的讀取操作分析Java版

文章目錄 X.509證書的讀取操作與分析 1. X.509 1.1 定義 1.2 組成結構 1.3 安全性 1.4 證書檔名擴充套件型別 2. 讀取操作程式 2.1

演算法設計分析十一

300. Longest Increasing Subsequence Given an unsorted array of integers, find the length of longest increasing subsequence. Example

線性表——順序表的實現講解C++描述

線性表 引言 新生安排體檢,為了 便管理與統一資料,學校特地規定了排隊的方式,即按照學號排隊,誰在前誰在後,這都是規定好的,所以誰

雙鏈表的基本實現講解C++描述

雙鏈表 雙鏈表的意義 單鏈表相對於順序表,確實在某些場景下解決了一些重要的問題,例如在需要插入或者刪除大量元素的時候,它並不需要

redis原始碼分析思考十七——有序集合型別的命令實現(t_zset.c)

    有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令: 有序集合命令 命令 對應操作 時

redis原始碼分析思考十六——集合型別的命令實現(t_set.c)

    集合型別是用來儲存多個字串的,與列表型別不一樣,集合中不允許有重複的元素,也不能以索引的方式來通過下標獲取值,集合中的元素還是無序的。在普通的集合上增刪查改外,集合型別還實現了多個集合的取交集、並集、差集,集合的命令如下表所示: 集合命

redis原始碼分析思考十五——雜湊型別的命令實現(t_hash.c)

    雜湊型別又叫做字典,在redis中,雜湊型別本身是一個鍵值對,而雜湊型別裡面也存貯著鍵值對,其對應關係是,每個雜湊型別的值對應著一個鍵值對或多對鍵值對,如圖所示: 雜湊型別命令 命令 對應操

redis原始碼分析思考十四——列表型別的命令實現(t_list.c)

    列表型別是用來存貯多個字串物件的結構。一個列表可以存貯232-1個元素,可以對列表兩端進行插入(push)、彈出(pop),還可以獲取指定範圍內的元素列表、獲取指定索引的元素等等,它可以靈活的充當棧和佇列的角色。下面列出列表的命令: 列

redis原始碼分析思考十七——有序集合型別的命令實現(t_set.c)

    有序集合是集合的延伸,它儲存著集合元素的不可重複性,但不同的是,它是有序的,它利用每一個元素的分數來作為有序集合的排序依據,現在列出有序集合的命令: 有序集合命令 命令 對應操作 時間複

影象處理中,SIFT,FAST,MSER,STAR等特徵提取演算法的比較分析利用openCV實現

本人為研究生,最近的研究方向是物體識別。所以就將常用的幾種特徵提取方式做了一個簡要的實驗和分析。這些實驗都是藉助於openCV在vs2010下完成的。基本上都是使用的openCV中內建的一些功能函式。 1. SIFT演算法 尺度不變特徵轉換(Scale-inva

應用統計學R語言實現學習筆記十一——判別分析

Chapter 11 Discriminant Analysis 筆者最近任務繁重,斷更了一頓時間,最近會開始慢慢把這個系列寫完。本篇是第十一章,內容是判別分析。 1 判別分析應用 判別分析(Discriminant Analysis)——判別分

平衡二叉搜尋樹:AVL樹的實現分析 以及 統一重平衡演算法 C++

平衡二叉搜尋樹 既然二叉搜尋樹的效能主要取決於高度,故在節點數目固定的前提下,應儘可能地降低高度。 相應地,應儘可能地使兄弟子樹的高度彼此接近,即全樹儘可能地平衡。 等價變換 等價二叉搜尋樹:若兩棵二叉搜尋樹的中序遍歷序列相同,則稱它們彼此等價;反之亦然。

3D模型體素化Voxelization過程實現分析

體素化方法 體素化能夠對模型進行簡化,得到均勻的網格,在求模型的測地線,求交等過程中有較好的應用。個人理解,把體素化分為基於CPU的方法和基於GPU渲染的方法。輸入是三角面片,輸出體素化格子。 基於CPU的體素化方法 體素化無非是對模型所在空

大數據江湖之即席查詢分析下篇--手把手教你搭建即席查詢分析Demo

dmi 安裝centos 用戶 author sla repo 相關 中文 plugin 上篇小弟分享了幾個“即席查詢與分析”的典型案例,引起了不少共鳴,好多小夥伴迫不及待地追問我們:說好的“手把手教你搭建即席查詢與分析Demo”啥時候能出?說到就得做到,差啥不能差

keras系列︱seq2seq系列相關實現案例feedback、peek、attention類型

課堂 簡單實現 htm 構建 氣象 use 然而 示例 tps 之前在看《Semi-supervised Sequence Learning》這篇文章的時候對seq2seq半監督的方式做文本分類的方式產生了一定興趣,於是開始簡單研究了seq2seq。先來簡單說一下這篇pap

SQL Server橫向擴展:設計,實現維護2- 分布式分區視圖

做的 img attach one 遠程 cnblogs ole out 不同的 為了使得朋友們對分布式分區視圖有個概念,也為了方便後面的內容展開,我們先看看下面一個圖: 講述分布式分區視圖之前,很有必要將之與我們常常熟悉的分區表和索引