
你好,哈布爾!我是 Artem Karamyshev,系統管理團隊負責人 。在過去的一年裡,我們推出了許多新產品。我們希望確保 API 服務易於擴展、容錯,並為用戶負載的快速成長做好準備。我們的平台是在OpenStack上實現的,我想告訴你我們必須解決哪些元件容錯問題才能獲得容錯系統。我認為這對於那些也在 OpenStack 上開發產品的人來說會很有趣。
平台的整體容錯能力由其組件的彈性組成。因此,我們將逐步完成我們識別風險並關閉風險的所有等級。
本文的視訊版本,其主要來源是正常運行時間第 4 天會議的報告,該會議由 ,你可以看到 .
物理架構的彈性
MCS雲的公共部分現在基於兩個Tier III資料中心,它們之間有自己的暗光纖,透過不同的路由在物理層面預留,吞吐量為200 Gbit/s。第三層為實體基礎設施提供必要的容錯等級。
暗光纖在物理層和邏輯層都有預留。通道預留過程是迭代的,問題也出現了,我們正在不斷改進資料中心之間的通訊。
例如,不久前,在資料中心附近的一口井中工作時,一台挖土機打破了一根管道,而該管道內有一條主光纜和一條備用光纜。事實證明,我們與資料中心的容錯通訊通道在井中的某一點很容易受到攻擊。因此,我們失去了部分基礎設施。我們得出結論並採取了一系列行動,包括在相鄰井中安裝額外的光學元件。
在資料中心,有一些通訊提供者的存在點,我們透過 BGP 向這些提供者廣播我們的前綴。對於每個網路方向,選擇最佳的度量,這允許為不同的客戶端提供最佳的連接品質。如果透過一個提供者的通訊發生故障,我們將透過可用的提供者重建路由。
如果某個提供者失敗,我們會自動切換到下一個。如果其中一個資料中心發生故障,我們會在第二個資料中心擁有服務的鏡像副本,該副本將承擔全部負載。

物理基礎設施的彈性
我們使用什麼來實現應用程式級容錯
我們的服務基於許多開源元件建構。
ExaBGP 是一種使用基於 BGP 的動態路由協定實現多種功能的服務。我們積極使用它來宣傳我們列入白名單的 IP 位址,使用者透過這些位址存取 API。
HAProxy的 是一個高負載平衡器,可讓您在 OSI 模型的不同層級配置非常靈活的流量平衡規則。我們用它來平衡所有服務:資料庫、訊息代理程式、API 服務、Web 服務、我們的內部專案 - 一切都在 HAProxy 後面。
API應用 — 用 python 編寫的 Web 應用程序,使用者用它來管理他的基礎設施和服務。
工人申請 (以下簡稱worker) - 在OpenStack服務中,這是一個基礎設施守護進程,可讓您向基礎設施廣播API指令。例如,磁碟建立發生在worker中,建立請求發生在應用程式API中。
標準 OpenStack 應用架構
大多數為 OpenStack 開發的服務都嘗試遵循單一範例。一個服務通常由兩個部分組成:API和workers(後端執行器)。一般來說,API 是 python 中的 WSGI 應用程序,它可以作為獨立進程(守護進程)啟動,也可以使用現成的 Nginx 或 Apache Web 伺服器啟動。 API 處理使用者請求並將進一步的指令傳遞給工作應用程式以供執行。傳輸使用訊息代理(通常是 RabbitMQ)進行,其他的支援很差。當訊息到達代理時,它們將由工作人員處理,並在必要時返回回應。
此範例涉及孤立的常見故障點:RabbitMQ 和資料庫。但 RabbitMQ 在一個服務內是隔離的,理論上,每個服務都可以是單獨的。因此,在 MCS,我們盡可能地分離這些服務;對於每個單獨的項目,我們建立一個單獨的資料庫,一個單獨的 RabbitMQ。這種方法很好,因為如果某些脆弱點發生事故,並不是整個服務崩潰,而只是部分服務崩潰。
工作應用程式的數量是無限的,因此 API 可以輕鬆地在平衡器後面水平擴展,以提高效能和容錯能力。
當 API 和工作執行緒之間發生複雜的順序操作時,某些服務需要在服務內進行協調。在這種情況下,使用單一協調中心,即 Redis、Memcache、etcd 等叢集系統,它允許一個工作人員告訴另一個工作人員該任務已指派給他(「請不要接受」)。我們使用etcd。通常,工作人員主動與資料庫進行通信,從那裡寫入和讀取資訊。我們使用 mariadb 作為資料庫,它位於多主叢集中。
這個經典的單一服務以 OpenStack 普遍接受的方式組織。它可以被認為是一個封閉的系統,其擴展和容錯的方法是非常明顯的。例如,為了API容錯,在它們前面放一個平衡器就足夠了。擴大工人規模是透過增加工人數量來實現的。
整個方案的弱點是RabbitMQ和MariaDB。他們的架構值得單獨寫一篇文章,我想專注於 API 容錯。

Openstack 應用架構。雲端平台的均衡與容錯
使用 ExaBGP 使 HAProxy 平衡器具有容錯能力
為了使我們的 API 可擴展、快速且具有容錯能力,我們在它們前面放置了一個負載平衡器。我們選擇HAProxy。在我看來,它具有我們任務所需的所有必要特徵:在多個 OSI 層級進行平衡、管理介面、靈活性和可擴展性、大量平衡方法、對會話表的支援。
首先需要解決的問題是平衡器本身的容錯問題。簡單地安裝平衡器也會產生故障點:平衡器損壞並且服務崩潰。為了防止這種情況發生,我們將 HAProxy 與 ExaBGP 結合使用。
ExaBGP 可讓您實作一種檢查服務狀態的機制。我們使用此機制來檢查 HAProxy 的功能,並在出現問題時從 BGP 停用 HAProxy 服務。
ExaBGP+HAProxy方案
- 我們在三台伺服器上安裝必要的軟體 ExaBGP 和 HAProxy。
- 我們在每台伺服器上建立一個環回介面。
- 在所有三台伺服器上,我們為該介面分配相同的白色 IP 位址。
- 白色 IP 位址透過 ExaBGP 發佈到網際網路。
容錯是透過從所有三台伺服器通告相同的 IP 位址來實現的。從網路的角度來看,可以從三個不同的下一跳存取相同位址。路由器看到三個相同的路由,根據自己的度量選擇其中最高優先權(這通常是相同的選項),並且流量僅流向其中一台伺服器。
當HAProxy運作出現問題或伺服器故障時,ExaBGP停止通告路由,流量平滑切換到另一台伺服器。
這樣,我們就實現了平衡器的容錯能力。

HAProxy 平衡器的容錯能力
事實證明方案並不完美:我們學會如何保留 HAProxy,但沒有學會如何在服務內分配負載。因此,我們稍微擴展了這個方案:我們繼續在幾個白色 IP 位址之間進行平衡。
基於DNS+BGP的均衡
我們的 HAProxy 的負載平衡問題仍未解決。然而,它可以很簡單地解決,就像我們在這裡所做的那樣。
為了平衡三台伺服器,您將需要 3 個白色 IP 位址和良好的舊 DNS。這些位址中的每一個都在每個 HAProxy 的環回介面上確定並公佈到 Internet。
在 OpenStack 中,為了管理資源,使用服務目錄,它指定特定服務的端點 API。在此目錄中,我們註冊了一個網域名稱 - public.infra.mail.ru,該網域透過三個不同的 IP 位址透過 DNS 進行解析。結果,我們透過 DNS 獲得了三個位址之間的負載分配。
但由於在宣佈白色 IP 位址時我們不控制伺服器選擇優先級,因此這還不是平衡的。通常,只會根據 IP 位址資歷選擇一台伺服器,另外兩台伺服器將處於空閒狀態,因為 BGP 中沒有指定度量。
我們開始透過 ExaBGP 使用不同的指標發送路由。每個平衡器都會通告所有三個白色 IP 位址,但其中一個(該平衡器的主要位址)使用最小度量進行通告。因此,當所有三個平衡器都在運行時,對第一個IP 位址的呼叫將轉到第一個平衡器,對第二個IP 位址的呼叫將轉到第二個平衡器,對第三個IP 位址的呼叫將轉到第三個。
當其中一個平衡器掉落時會發生什麼?如果任何平衡器發生故障,其主位址仍會從其他兩個平衡器中通告,並且流量會在它們之間重新分配。因此,我們透過 DNS 一次為使用者提供多個 IP 位址。透過 DNS 和不同的指標進行平衡,我們可以在所有三個平衡器之間均勻分配負載。同時我們也不會失去容錯能力。

基於DNS+BGP平衡HAProxy
ExaBGP 與 HAProxy 之間的交互
因此,我們透過停止通告路由來實現容錯,以防伺服器離開。但 HAProxy 可能會因伺服器故障以外的其他原因而關閉:管理錯誤、服務內故障。在這些情況下,我們也希望從負載下移除損壞的平衡器,並且我們需要不同的機制。
因此,擴展先前的方案,我們實現了ExaBGP和HAProxy之間的心跳。這是ExaBGP和HAProxy之間互動的軟體實現,ExaBGP使用自訂腳本來檢查應用程式的狀態。
為此,您需要在 ExaBGP 設定中設定運行狀況檢查器,該檢查器可以檢查 HAProxy 的狀態。在我們的範例中,我們在 HAProxy 中配置了運行狀況後端,並從 ExaBGP 端使用簡單的 GET 請求進行檢查。如果公告停止發生,那麼 HAProxy 很可能無法正常運作,並且無需對其進行公告。

HAProxy 健康檢查
HAProxy Peers:會話同步
接下來要做的就是同步會話。當使用分散式平衡器時,很難組織有關客戶端會話的資訊的儲存。但由於 Peers 功能(能夠在不同 HAProxy 進程之間傳輸會話表的能力),HAProxy 是少數可以做到這一點的平衡器之一。
有不同的平衡方法:簡單的方法,例如 ,並擴展,當客戶端的會話被記住時,並且每次他最終都像以前一樣位於同一台伺服器上。我們想實作第二個選項。
HAProxy 使用黏表來保存該機制的客戶端會話。它們保存客戶端的原始IP位址、選定的目標位址(後端)和一些服務資訊。通常,棒表用於儲存來源 IP + 目標 IP 對,這對於在切換到另一個平衡器時無法傳輸使用者會話上下文的應用程式(例如,在 RoundRobin 平衡模式下)特別有用。
如果教導棒表在不同的 HAProxy 流程之間移動(在這些流程之間取得平衡),我們的平衡器將能夠使用一個棒表池。如果其中一個平衡器發生故障,這將使得無縫切換客戶端網路成為可能;客戶端會話的工作將在先前選擇的相同後端上繼續。
為了正確操作,必須解決建立會話的平衡器的來源 IP 位址問題。在我們的例子中,這是環回介面上的動態位址。
同伴的正確工作只有在一定條件下才能實現。也就是說,TCP 逾時必須足夠大或切換必須足夠快,以便 TCP 會話沒有時間終止。然而,它允許無縫切換。
在 IaaS 中,我們有一個使用相同技術建構的服務。這 ,這就是所謂的奧克塔維亞。它基於兩個 HAProxy 進程,最初包括對等點的支援。他們在這項服務中證明了自己的出色。
此圖示意性地顯示了三個 HAProxy 實例之間對等表的移動,提出瞭如何配置的配置:

HAProxy Peers(會話同步)
如果實施相同的方案,則必須仔細測試其操作。事實上,它並不是 100% 的時間都以同樣的方式運作。但至少當您需要記住客戶端的來源 IP 時,您不會遺失黏表。
限制同一客戶端同時發出的請求數量
任何公開可用的服務(包括我們的 API)都可能受到大量請求的影響。原因可能完全不同,從使用者錯誤到有針對性的攻擊。我們定期受到 IP 位址的 DDoS 攻擊。客戶經常在腳本中犯錯,從而為我們帶來小型 DDoS 攻擊。
無論如何,必須提供額外的保護。顯而易見的解決方案是限制 API 請求的數量,而不是浪費 CPU 時間來處理惡意請求。
為了實現此類限制,我們使用基於 HAProxy 組織的速率限制,並使用相同的棒表。設定限制非常簡單,您可以透過對 API 的請求數量來限制使用者。演算法會記住發出請求的來源 IP,並限制一個使用者同時發出的請求數量。當然,我們計算了每個服務的平均 API 負載配置文件,並將限制設為該值的 ≈ 10 倍。我們將繼續密切關注事態發展並隨時掌握動態。
這在實踐中是什麼樣子的?我們的客戶一直在使用我們的自動縮放 API。他們早上創建大約兩到三百台虛擬機,晚上刪除它們。對於 OpenStack 來說,建立虛擬機器以及 PaaS 服務至少需要 1000 個 API 請求,因為服務之間的互動也是透過 API 進行的。
這樣的任務轉移會造成相當大的負荷。我們評估了這個負載,收集了每日峰值,將其增加了十倍,這成為了我們的速率限制。我們密切關注脈搏。我們經常看到機器人和掃描器試圖查看我們是否有任何可以運行的 CGA 腳本,我們正在積極地削減它們。
如何在使用者不注意的情況下更新程式碼庫
我們還在程式碼部署流程層級實作容錯。推出期間可能會出現故障,但可以最大限度地減少它們對服務可用性的影響。
我們不斷更新我們的服務,並且必須確保程式碼庫的更新不會影響使用者。我們設法使用 HAProxy 的管理功能以及在我們的服務中實現 Graceful Shutdown 來解決這個問題。
為了解決這個問題,需要確保平衡器的控制和服務的「正確」關閉:
- 對於 HAProxy,控制是透過 stats 檔案執行的,該檔案本質上是一個套接字,並在 HAProxy 配置中定義。您可以透過 stdio 向其發送命令。但我們主要的設定控制工具是ansible,因此它內建了一個用於管理HAProxy的模組。我們積極使用它。
- 我們的大多數 API 和引擎服務都支援優雅關閉技術:關閉時,它們會等待當前任務完成,無論是 http 請求還是某些服務任務。同樣的事情也發生在工人身上。它知道它正在執行的所有任務,並在成功完成所有任務後結束。
由於這兩點,我們部署的安全演算法如下所示。
- 開發人員組裝一個新的程式碼包(對我們來說這是 RPM),在開發環境中測試它,在階段中測試它,並將其保留在階段儲存庫中。
- 開發人員使用最詳細的「工件」描述來設定部署任務:新套件的版本、新功能的描述以及有關部署的其他詳細資訊(如果需要)。
- 系統管理員開始更新。啟動 Ansible playbook,它依序執行以下操作:
- 從階段儲存庫中取得套件並使用它來更新產品儲存庫中的套件的版本。
- 編譯更新服務的後端清單。
- 關閉 HAProxy 中第一個要更新的服務並等待其程序完成執行。由於正常關閉,我們有信心所有目前的客戶端請求都將成功完成。
- API 和工作線程完全停止並且 HAProxy 關閉後,程式碼將更新。
- Ansible 運作服務。
- 對於每項服務,都會拉出某些“句柄”,這些“句柄”對許多預先定義的關鍵測試執行單元測試。對新程式碼進行基本檢查。
- 如果上一步沒有發現錯誤,則啟動後端。
- 讓我們繼續討論下一個後端。
- 所有後端更新後,啟動功能測試。如果它們遺失,那麼開發人員會查看他創建的任何新功能。
這樣就完成部署了。

服務更新週期
如果我們沒有一條規則,這個計劃就行不通。我們在戰鬥中支援新舊版本。事先在軟體開發階段就規定,即使服務資料庫有變化,也不會破壞先前的程式碼。因此,程式碼庫逐漸更新。
結論
分享一下我自己對容錯WEB架構的看法,我想再次指出它的重點:
- 物理容錯;
- 網路容錯(平衡器、BGP);
- 使用和開發的軟體的容錯能力。
大家穩定正常運作!
來源: www.habr.com
