Odnoklassniki 中的一云 - 數據中心級操作系統

Odnoklassniki 中的一云 - 數據中心級操作系統

阿囉哈,人們! 我叫 Oleg Anastasyev,在 Odnoklassniki 平台團隊工作。 除了我之外,Odnoklassniki 還有很多硬體在工作。 我們有四個資料中心,約有 500 個機架,8 多台伺服器。 在某個時刻,我們意識到引入新的管理系統將使我們能夠更有效地加載設備,方便存取管理,自動化計算資源(重新)分配,加快新服務的推出並加快響應速度到大規模事故。

結果是什麼呢?

除了我和一堆硬體之外,還有使用這些硬體的人:直接位於資料中心的工程師; 設定網路軟體的網路人員; 提供基礎設施彈性的管理員或 SRE; 和開發團隊,每個人負責入口網站的部分功能。 他們創建的軟體的工作原理如下:

Odnoklassniki 中的一云 - 數據中心級操作系統

用戶請求均在主門戶的正面收到 www.ok.ru,以及其他方面,例如音樂 API 方面。 為了處理業務邏輯,他們調用應用程式伺服器,應用程式伺服器在處理請求時調用必要的專門微服務 - 單圖(社交關係圖)、用戶快取(用戶設定檔快取)等。

這些服務中的每一個都部署在許多機器上,每個服務都有負責的開發人員負責模組的功能、操作和技術開發。 所有這些服務都在硬體伺服器上運行,直到最近我們才為每台伺服器啟動了一項任務,即它專門用於一項特定任務。

這是為什麼? 這種方法有幾個優點:

  • 鬆了口氣 大眾管理。 假設一項任務需要一些函式庫和一些設定。 然後將伺服器準確地分配到一個特定群組,描述該群組的 cfengine 策略(或已經描述過),並將此配置集中自動推廣到該群組中的所有伺服器。
  • 簡化版 診斷。 假設您查看了中央處理器上增加的負載,並意識到該負載只能由在此硬體處理器上執行的任務產生。 尋找罪魁禍首的搜尋很快就結束了。
  • 簡化版 監控。 如果伺服器出現問題,監視器會報告該問題,並且您可以確切地知道責任所在。

由多個副本組成的服務會分配多個伺服器 - 每個伺服器分配一個伺服器。 那麼服務的運算資源分配就非常簡單:服務擁有的伺服器數量,它可以消耗的最大資源量。 這裡的「簡單」並不是指易於使用,而是指資源分配是手動完成的。

這種方法也允許我們做 專門的鐵配置 對於在此伺服器上執行的任務。 如果任務儲存大量數據,那麼我們使用機箱4盤的38U伺服器。 如果任務是純粹的計算,那麼我們可以購買更便宜的1U伺服器。 這在計算上是高效率的。 除此之外,這種方法使我們可以使用比一個友好的社交網路負載少四倍的機器。

如果我們從最昂貴的東西是伺服器的前提出發,這樣的運算資源使用效率也應該確保經濟效率。 長期以來,硬體是最昂貴的,我們花了很多精力來降低硬體的價格,提出容錯演算法來降低硬體的可靠性要求。 今天我們已經到了伺服器價格不再具有決定性的階段。 如果您不考慮最新的外來事物,那麼機架中伺服器的具體配置並不重要。 現在我們還有一個問題──資料中心內伺服器所佔用的空間的價格,也就是機架內的空間。

意識到這種情況後,我們決定計算一下機架的使用效率。
我們從經濟上合理的伺服器中選擇最強大的伺服器的價格,計算出我們可以在機架中放置多少台這樣的伺服器,根據舊模型“一台伺服器=一個任務”,我們可以在它們上運行多少個任務,以及多少個這樣的伺服器。任務可以利用該設備。 他們數著數著,流下了眼淚。 事實證明,我們的機架使用效率約為11%。 結論很明顯:我們需要提高資料中心的使用效率。 解決方案似乎很明顯:您需要同時在一台伺服器上執行多個任務。 但這就是困難的開始。

大規模配置變得更加複雜 - 現在不可能將任何一組分配給伺服器。 畢竟,現在可以在一台伺服器上啟動不同命令的多個任務。 此外,不同應用程式的配置可能會發生衝突。 診斷也變得更加複雜:如果您發現伺服器上的 CPU 或磁碟消耗增加,您不知道哪個任務導致了問題。

但最主要的是同一台機器上執行的任務之間不存在隔離。 例如,這裡是在同一伺服器上啟動另一個計算應用程式之前和之後伺服器任務的平均回應時間的圖表,與第一個計算應用程式沒有任何關係 - 主任務的回應時間顯著增加。

Odnoklassniki 中的一云 - 數據中心級操作系統

顯然,您需要在容器或虛擬機器中執行任務。 由於我們幾乎所有的任務都在一個作業系統(Linux)下運作或適應它,因此我們不需要支援許多不同的作業系統。 因此,不需要虛擬化;由於額外的開銷,它的效率將低於容器化。

作為直接在伺服器上運行任務的容器實現,Docker 是一個很好的候選者:檔案系統映像很好地解決了配置衝突的問題。 鏡像可以由多個層組成,這一事實使我們能夠顯著減少在基礎設施上部署它們所需的資料量,將公共部分分離到單獨的基礎層中。 然後,基本(也是最龐大的)層將在整個基礎設施中相當快地緩存,並且為了交付許多不同類型的應用程式和版本,只需要傳輸小層。

另外,Docker 中現成的註冊表和映像標記為我們提供了用於版本控制和將程式碼交付到生產環境的現成原語。

Docker 與任何其他類似技術一樣,為我們提供了某種程度的開箱即用的容器隔離。 例如記憶體隔離-每個容器都被賦予了機器記憶體的使用限制,超過這個限制就不會消耗。 您也可以根據 CPU 使用情況隔離容器。 然而,對我們來說,標準隔熱材料還不夠。 但下面有更多內容。

在伺服器上直接運行容器只是問題的一部分。 另一部分與在伺服器上託管容器有關。 您需要了解哪個容器可以放置在哪個伺服器上。 這並不是一件容易的事,因為容器需要盡可能密集地放置在伺服器上,同時又不降低伺服器的速度。 從容錯的角度來看,這種放置也可能很困難。 通常我們希望將相同服務的副本放置在不同的機架甚至資料中心的不同房間中,這樣如果某個機架或房間發生故障,我們不會立即遺失所有服務副本。

當您擁有 8 台伺服器和 8-16 個容器時,手動分發容器不是一個選擇。

此外,我們希望在資源分配方面給予開發人員更多的獨立性,以便他們可以自己在生產中託管其服務,而無需管理員的幫助。 同時,我們希望保持控制,以便一些次要服務不會消耗我們資料中心的所有資源。

顯然,我們需要一個能夠自動執行此操作的控制層。

所以我們得到了一個所有建築師都喜歡的簡單易懂的圖畫:三個正方形。

Odnoklassniki 中的一云 - 數據中心級操作系統

one-cloud masters 是一個負責雲端編排的故障轉移叢集。 開發人員向主伺服器發送清單,其中包含託管服務所需的所有資訊。 基於此,master 會向選定的 minions(設計用於運行容器的機器)發出命令。 Minion有我們的代理,它接收命令,向Docker發出命令,Docker配置Linux核心以啟動相應的容器。 除了執行命令之外,代理還不斷向 master 報告 minion 機器及其上運行的容器的狀態變化。

資源分配

現在讓我們來看看許多 Minion 的更複雜的資源分配問題。

單一雲端中的運算資源是:

  • 特定任務消耗的處理器電量。
  • 任務可用的內存量。
  • 網路流量。 每個 Minion 都有一個頻寬有限的特定網路接口,因此不可能在不考慮它們透過網路傳輸的資料量的情況下分配任務。
  • 磁碟. 此外,顯然,對於這些任務的空間,我們也指派磁碟類型:HDD 或 SSD。 磁碟每秒可以處理有限數量的請求 - IOPS。 因此,對於產生的 IOPS 超過單一磁碟可以處理的任務,我們還指派“主軸”,即必須專門為該任務保留的磁碟裝置。

那麼對於某些服務,例如使用者緩存,我們可以這樣記錄消耗的資源:400 個處理器核心,2,5 TB 內存,雙向 50 Gbit/s 流量,位於 6 個主軸上的 100 TB HDD 空間。 或採用較熟悉的形式,如下所示:

alloc:
    cpu: 400
    mem: 2500
    lan_in: 50g
    lan_out: 50g
    hdd:100x6T

使用者快取服務資源僅消耗生產基礎架構中所有可用資源的一部分。 因此,我想確保突然之間,無論是否由於操作員錯誤,用戶快取消耗的資源不會多於分配給它的資源。 也就是說,我們必須限制資源。 但我們可以將配額與什麼掛鉤呢?

讓我們回到我們大大簡化的組件交互圖,並用更多細節重新繪製它 - 像這樣:

Odnoklassniki 中的一云 - 數據中心級操作系統

什麼吸引了你的眼球:

  • Web 前端和音樂使用同一應用程式伺服器的隔離叢集。
  • 我們可以區分這些叢集所屬的邏輯層:前端、快取、資料儲存和管理階層。
  • 前端是異質的;它是由不同的功能子系統所組成。
  • 快取還可以分散在它們快取資料的子系統中。

我們再重新畫一下圖:

Odnoklassniki 中的一云 - 數據中心級操作系統

呸! 是的,我們看到了層次結構! 這意味著您可以以更大的區塊分配資源:將負責的開發人員分配到該層次結構中與功能子系統(如圖中的“音樂”)相對應的節點,並將配額附加到層次結構的同一級別。 這種層次結構也讓我們能夠更靈活地組織服務,以便於管理。 例如,我們將所有網路(因為這是一個非常大的伺服器分組)分成幾個較小的群組,如圖所示為 group1、group2。

透過刪除多餘的線,我們可以以更扁平的形式編寫圖片的每個節點: group1.web.front, api.music.front, 使用者快取.cache.

這就是我們如何得出「分層隊列」的概念。 它的名稱類似於“group1.web.front”。 為其分配資源配額和使用者權限。 我們將授予 DevOps 人員向佇列發送服務的權限,這樣的員工可以在佇列中啟動某些內容,OpsDev 人員將擁有管理員權限,現在他可以管理佇列,在那裡分配人員,授予這些人權限等。在此佇列上執行的服務將在佇列的配額內執行。 如果佇列的計算配額不足以一次執行所有服務,那麼它們將按順序執行,從而形成佇列本身。

讓我們仔細看看服務。 服務有一個完全限定的名稱,其中始終包含佇列的名稱。 然後前端網路服務將具有名稱 ok-web.group1.web.front。 並且它所訪問的應用程式伺服器服務會被調用 ok-app.group1.web.front。 每個服務都有一個清單,它指定放置在特定機器上的所有必要資訊:該任務消耗多少資源、需要什麼配置、應該有多少個副本、用於處理該服務故障的屬性。 當服務直接放置在機器上後,它的實例就會出現。 它們也被明確命名 - 作為實例編號和服務名稱: 1.ok-web.group1.web.front, 2.ok-web.group1.web.front, …

這非常方便:只需查看正在運行的容器的名稱,我們就可以立即了解很多資訊。

現在讓我們仔細看看這些實例實際執行的內容:任務。

任務隔離類

OK 中的所有任務(可能是所有地方)都可以分成幾組:

  • 短延遲任務 - prod。 對於此類任務和服務,回應延遲(延遲)非常重要,即係統處理每個請求的速度。 任務範例:Web 前端、快取、應用程式伺服器、OLTP 儲存等。
  • 計算問題 - 批次。 在這裡,每個特定請求的處理速度並不重要。 對他們來說,重要的是該任務在某個(長)時間段(吞吐量)內將執行多少次計算。 這些將是 MapReduce、Hadoop、機器學習、統計的任何任務。
  • 後台任務 - 空閒。 對於此類任務,延遲和吞吐量都不是很重要。 這包括各種測試、遷移、重新計算以及將資料從一種格式轉換為另一種格式。 一方面,它們與計算的相似,另一方面,它們完成的速度對我們來說並不重要。

讓我們看看這些任務如何消耗資源,例如中央處理器。

短延遲任務。 這樣的任務將具有類似以下的 CPU 消耗模式:

Odnoklassniki 中的一云 - 數據中心級操作系統

收到用戶的請求進行處理,任務開始使用所有可用的 CPU 內核,對其進行處理,回傳回應,等待下一個請求並停止。 下一個請求到達 - 我們再次選擇了那裡的所有內容,計算了它,然後等待下一個請求。

為了確保此類任務的延遲最小,我們必須最大限度地利用其消耗的資源,並在 minion(將執行該任務的機器)上保留所需數量的核心。 那我們問題的保留公式如下:

alloc: cpu = 4 (max)

如果我們有一台 16 核心的 Minion 機器,那麼正好可以在上面放置 XNUMX 個這樣的任務。 我們特別注意到,此類任務的平均處理器消耗通常非常低 - 這是顯而易見的,因為任務的很大一部分時間等待請求而不執行任何操作。

計算任務。 他們的模式會略有不同:

Odnoklassniki 中的一云 - 數據中心級操作系統

此類任務的平均 CPU 資源消耗相當高。 通常我們希望一個計算任務能夠在一定的時間內完成,因此我們需要預留它所需的最少數量的處理器,以便整個計算在可接受的時間內完成。 其預留公式如下圖所示:

alloc: cpu = [1,*)

“請把它放在一個至少有一個空閒核心的小兵身上,然後只要有多個核心,它就會吞噬一切。”

這裡的使用效率已經比在短延遲任務上好很多了。 但是,如果您將兩種類型的任務組合在一台 Minion 機器上並隨時隨地分配其資源,那麼收益會更大。 當具有短延遲的任務需要處理器時,它立即接收它,並且當不再需要資源時,它們被轉移到計算任務,即像這樣:

Odnoklassniki 中的一云 - 數據中心級操作系統

但是怎麼做呢?

首先,讓我們來看看 prod 及其分配:cpu = 4。我們需要保留四個核心。 在 Docker run 中,這可以透過兩種方式完成:

  • 使用選項 --cpuset=1-4,即將機器上的四個特定核心分配給該任務。
  • 使用 --cpuquota=400_000 --cpuperiod=100_000,為處理器時間分配配額,即表示任務每 100 毫秒即時消耗不超過 400 毫秒的處理器時間。 獲得相同的四個核心。

但這些方法中哪一種是適當的呢?

cpuset看起來相當吸引人。 該任務有四個專用核心,這意味著處理器快取將盡可能有效地工作。 這也有一個缺點:我們必須承擔在機器的卸載核心而不是作業系統上分配計算的任務,這是一項相當重要的任務,特別是如果我們嘗試將批次任務放在這樣的電腦上機器。 測試表明,具有配額的選項更適合這裡:這樣作業系統可以更自由地選擇當前執行任務的核心,並且處理器時間可以更有效地分配。

讓我們來看看如何在 Docker 中根據最小核心數進行預留。 批次任務的配額不再適用,因為不需要限制最大值,只保證最小值就足夠了。 這裡的選項很合適 docker run --cpushares.

我們同意,如果一個批次需要至少一個核心的保證,那麼我們表明 --cpushares=1024,如果至少有兩個核心,那麼我們表明 --cpushares=2048。 只要有足夠的 CPU 份額,就不會以任何方式乾擾處理器時間的分配。 因此,如果 prod 目前未使用其所有四個核心,則批次任務不會受到任何限制,並且它們可以使用額外的處理器時間。 但在處理器短缺的情況下,如果 prod 消耗了全部 1024 個核心並達到配額,則剩餘處理器時間將按 cpushares 比例分配,即在 2048 個空閒核心的情況下,將分配一個分配給具有XNUMX 個cpushares 的任務,其餘兩個將分配給具有XNUMX 個cpushares 的任務。

但僅使用配額和份額還不夠。 我們需要確保在分配處理器時間時,延遲較短的任務優先於批次任務。 如果沒有這樣的優先級,批次任務將在產品需要時佔用所有處理器時間。 Docker 運行中沒有容器優先權選項,但 Linux CPU 調度程序策略會派上用場。 您可以詳細閱讀它們 這裡,在本文的框架內,我們將簡要介紹它們:

  • SCHED_OTHER
    預設情況下,Linux 電腦上的所有正常使用者程序都會接收。
  • SCHED_BATCH
    專為資源密集型流程而設計。 當將任務放置在處理器上時,會引入所謂的激活懲罰:如果該任務目前正被具有 SCHED_OTHER 的任務使用,則該任務不太可能接收處理器資源
  • SCHED_IDLE
    優先權非常低的後台進程,甚至低於nice -19。 我們使用我們的開源庫 一尼奧,以便透過呼叫啟動容器時設定必要的策略

one.nio.os.Proc.sched_setscheduler( pid, Proc.SCHED_IDLE )

但即使您不使用 Java 編程,也可以使用 chrt 命令完成相同的操作:

chrt -i 0 $pid

為了清楚起見,我們將所有隔離等級總結到一張表中:

絕緣級別
分配範例
Docker 運行選項
sched_setscheduler chrt*


中央處理器=4
--cpuquota=400000 --cpuperiod=100000
SCHED_OTHER

批量
CPU = [1, *)
--cpushares=1024
SCHED_BATCH

空閒
中央處理器= [2, *)
--cpushares=2048
SCHED_IDLE

*如果您從容器內部執行 chrt,則可能需要 sys_nice 功能,因為預設情況下 Docker 在啟動容器時會刪除此功能。

但任務不僅消耗處理器,還消耗流量,這對網路任務延遲的影響甚至比處理器資源的錯誤分配還要大。 因此,我們自然希望得到一模一樣的流量圖。 也就是說,當一個 prod 任務向網路發送一些資料包時,我們限制最大速度(公式 分配: lan=[*,500mbps) ),用哪個產品可以做到這一點。 對於批量我們只保證最小吞吐量,但不限制最大吞吐量(公式 分配: lan=[10Mbps,*) )在這種情況下,生產流量應優先於批次任務。
這裡 Docker 沒有任何我們可以使用的原語。 但這對我們有幫助 Linux 流量控制。 在紀律的幫助下我們能夠達到預期的結果 分層公平服務曲線。 在它的幫助下,我們區分了兩類流量:高優先權生產和低優先權批次/空閒。 因此,出站流量的配置如下:

Odnoklassniki 中的一云 - 數據中心級操作系統

這裡 1:0 是 hsfc 規則的「root qdisc」; 1:1 - hsfc子類,總頻寬限制為8 Gbit/s,所有容器的子類都放置在該子類之下; 1:2 - hsfc 子類別對於所有具有「動態」限制的批次和空閒任務都是通用的,這將在下面討論。 其餘的 hsfc 子類是目前運行的 prod 容器的專用類,其限制與其清單相對應 - 450 和 400 Mbit/s。 每個 hsfc 類別都會分配一個 qdisc 佇列 fq 或 fq_codel,取決於 Linux 核心版本,以避免流量突發期間丟包。

通常,tc 規則僅用於對傳出流量進行優先排序。 但我們也希望優先考慮傳入流量 - 畢竟,某些批次任務可以輕鬆選擇整個傳入通道,例如接收大量用於 Map&Reduce 的輸入資料。 為此,我們使用該模組 的ifb,它為每個網路介面建立一個 ifbX 虛擬接口,並將來自該介面的傳入流量重定向到 ifbX 上的傳出流量。 此外,對於 ifbX,所有相同的規則都用於控制傳出流量,其 hsfc 配置將非常相似:

Odnoklassniki 中的一云 - 數據中心級操作系統

在實驗過程中,我們發現,當 1:2 類非優先權批量/空閒流量在 Minion 機器上限制為不超過某個空閒通道時,hsfc 會顯示出最佳結果。 否則,非優先流量對生產任務的延遲影響太大。 miniond 每秒確定目前的可用頻寬量,測量給定 minion 的所有 prod-task 的平均流量消耗 Odnoklassniki 中的一云 - 數據中心級操作系統 並從網路介面頻寬中減去它 Odnoklassniki 中的一云 - 數據中心級操作系統 有很小的餘量,即

Odnoklassniki 中的一云 - 數據中心級操作系統

頻帶是為傳入和傳出流量獨立定義的。 並且根據新值,miniond 重新配置非優先權限制 1:2。

因此,我們實作了所有三個隔離類別:prod、batch 和idle。 這些類別極大地影響任務的效能特徵。 因此,我們決定將此屬性放置在層次結構的頂部,以便在查看層次結構佇列的名稱時,可以立即清楚我們正在處理的內容:

Odnoklassniki 中的一云 - 數據中心級操作系統

我們所有的朋友 捲筒紙 и 音樂 然後將前端放置在 prod 下的層次結構中。 例如,在批次處理下,讓我們將服務放置在 音樂目錄,它定期從上傳到 Odnoklassniki 的一組 mp3 檔案中編譯曲目目錄。 空閒狀態下的服務的一個例子是 音樂變壓器,標準化音樂音量等級。

再次刪除多餘的行後,我們可以透過將任務隔離類別新增至完整服務名稱的末尾來將服務名稱寫得更扁平: web.front.prod, 目錄.音樂.batch, 變壓器.音樂.空閒.

現在,查看服務的名稱,我們不僅了解它執行什麼功能,還了解它的隔離類,這意味著它的關鍵性等。

一切都很棒,但有一個痛苦的事實。 完全隔離在一台機器上運行的任務是不可能的。

我們設法實現的目標:如果批量密集消耗 CPU 資源,那麼內建的 Linux CPU 調度程式就可以很好地完成其工作,並且對 prod 任務幾乎沒有影響。 但如果這個批次任務開始主動與記憶體合作,那麼相互影響就已經出現了。 發生這種情況是因為 prod 任務被「清除」了處理器的記憶體快取 - 結果,快取未命中增加,並且處理器處理 prod 任務的速度變慢。 這樣的批次任務可以使我們典型的 prod 容器的延遲增加 10%。

由於現代網路卡具有內部資料包佇列,隔離流量變得更加困難。 如果來自批次任務的資料包首先到達那裡,那麼它將是第一個透過電纜傳輸的資料包,對此無能為力。

此外,到目前為止,我們只能解決 TCP 流量優先順序的問題:hsfc 方法不適用於 UDP。 而且即使在 TCP 流量的情況下,如果批次任務產生大量流量,這也會使 prod 任務的延遲增加約 10%。

容錯

開發一雲的目標之一是提高 Odnoklassniki 的容錯能力。 因此,接下來我想更詳細地考慮故障和事故可能的情況。 讓我們從一個簡單的場景開始——容器故障。

容器本身可能會以多種方式發生故障。 這可能是清單中的某種實驗、錯誤或錯誤,因此 prod 任務開始消耗比清單中指示的更多的資源。 我們有一個案例:一名開發人員實現了一個複雜的演算法,對其進行了多次修改,自己思考過度,變得如此困惑,最終問題以一種非常不平凡的方式循環。 由於 prod 任務比同一 Minions 上的所有其他任務具有更高的優先級,因此它開始消耗所有可用的處理器資源。 在這種情況下,隔離,或者更確切地說,CPU 時間配額,挽救了局面。 如果為任務分配配額,則該任務不會消耗更多配額。 因此,在同一台機器上運行的批次和其他生產任務沒有註意到任何事情。

第二個可能的問題是容器掉落。 這裡重啟策略拯救了我們,每個人都知道它們,Docker 本身做得很好。 幾乎所有生產任務都有始終重新啟動策略。 有時我們會使用 on_failure 來執行批次任務或偵錯產品容器。

如果整個隨從都不可用,您該怎麼辦?

顯然,在另一台機器上運行容器。 這裡有趣的部分是分配給容器的 IP 位址會發生什麼情況。

我們可以為容器分配與運行這些容器的 Minion 機器相同的 IP 位址。 然後,當容器在另一台機器上啟動時,它的IP位址發生變化,所有客戶端都必須了解容器已經移動,現在他們需要轉到不同的位址,這需要單獨的服務發現服務。

服務發現很方便。 市場上有許多具有不同程度容錯能力的解決方案用於組織服務註冊表。 通常,此類解決方案會實現負載平衡器邏輯,以 KV 儲存等形式儲存附加配置。
但是,我們希望避免實現單獨的註冊表的需要,因為這意味著引入一個由生產中的所有服務使用的關鍵系統。 這意味著這是一個潛在的故障點,您需要選擇或開發一個非常容錯的解決方案,這顯然是非常困難、耗時且昂貴的。

還有一個很大的缺點:為了讓我們的舊基礎設施能夠與新基礎設施一起工作,我們必須重寫所有任務以使用某種服務發現系統。 有很多工作要做,在某些地方,當涉及在作業系統核心層級或直接與硬體一起工作的低階設備時,這幾乎是不可能的。 使用已建立的解決方案模式實現此功能,例如 邊車 在某些地方意味著額外的負載,而在其他地方則意味著操作的複雜性和額外的故障情況。 我們不想讓事情變得複雜,因此我們決定將服務發現的使用作為可選。

在一雲中,IP跟隨容器,也就是每個任務實例都有自己的IP位址。 該位址是「靜態的」:當服務首次傳送到雲端時,它被指派給每個實例。 如果某個服務在其生命週期內擁有不同數量的實例,那麼最終它將被指派與最大實例數一樣多的 IP 位址。

隨後,這些地址不會更改:它們被分配一次,並在生產服務的整個生命週期中繼續存在。 IP 位址在網路中跟隨容器。 如果容器被轉移到另一個minion,那麼位址將跟隨它。

因此,服務名稱到其 IP 位址清單的對應很少會發生變化。 如果您再看一下我們在文章開頭提到的服務實例的名稱(1.ok-web.group1.web.front.prod, 2.ok-web.group1.web.front.prod, …),我們會注意到它們類似於 DNS 中使用的 FQDN。 沒錯,為了將服務執行個體的名稱對應到它們的 IP 位址,我們使用 DNS 協定。 此外,此 DNS 傳回所有容器的所有保留 IP 位址 - 包括運行的和停止的(假設使用了三個副本,並且我們在那裡保留了五個位址 - 所有五個都將返回)。 收到此資訊後的用戶端將嘗試與所有五個副本建立連線 - 從而確定那些正在工作的副本。 這種確定可用性的選項更加可靠;它不涉及 DNS 或服務發現,這意味著在確保這些系統的資訊相關性和容錯性方面不存在需要解決的難題。 而且,在整個入口網站運作所依賴的關鍵服務中,我們根本無法使用DNS,而只需在設定中輸入IP位址即可。

在容器後面實現此類 IP 傳輸可能並不簡單 - 我們將透過以下範例了解它是如何運作的:

Odnoklassniki 中的一云 - 數據中心級操作系統

假設一雲master向minion M1發出運行指令 1.ok-web.group1.web.front.prod 地址為 1.1.1.1。 適用於小兵 ,它將這個位址通告給特殊的伺服器 路由反射器。 後者與網路硬體建立BGP會話,將M1.1.1.1上位址1的路由轉換為該會話。 M1 使用 Linux 在容器內路由資料包。 總共有三個路由反射器伺服器,因為這是一雲基礎設施中非常關鍵的部分 - 沒有它們,一雲中的網路將無法運作。 我們將它們放置在不同的機架中,如果可能的話,放置在資料中心的不同房間中,以減少所有三個同時發生故障的可能性。

現在我們假設一雲Master和M1 Minion之間的連線遺失。 一雲主機現在將假設 M1 完全失敗而採取行動。 也就是說,它會給M2小兵發出發射命令 web.group1.web.front.prod 具有相同的地址 1.1.1.1。 現在,1.1.1.1 的網路上有兩條相互衝突的路由:M1 和 M2。 為了解決此類衝突,我們使用多出口鑑別器,這在 BGP 公告中指定。 這是一個顯示所通告路由的權重的數字。 在衝突的路由中,選擇MED值較小的路由。 一雲Master支援MED作為容器IP位址的組成部分。 第一次,地址寫入了足夠大的MED=1,在這種緊急貨櫃轉運的情況下,master減少了MED,M000已經收到了通告地址000的命令,MED=2 1.1.1.1。在在這種情況下,在M999 上運行的實例將保持在沒有連接的狀態,並且在與主機的連接恢復之前,我們對他的進一步命運沒什麼興趣,此時他將像舊鏡頭一樣被停止。

事故

所有資料中心管理系統總是能夠以可接受的方式處理小故障。 容器溢出幾乎在任何地方都是常態。

讓我們看看如何處理緊急情況,例如資料中心的一個或多個房間發生電源故障。

事故對資料中心管理系統意味著什麼? 首先,這是許多機器的大規模一次性故障,控制系統需要同時遷移很多容器。 但如果災難規模非常大,那麼可能會出現所有任務無法重新分配給其他minion的情況,因為資料中心的資源容量下降到負載的100%以下。

事故常伴隨著控制層的故障。 這種情況的發生可能是由於其設備的故障,但更多的情況是由於事故沒有經過測試,以及控制層本身由於負載增加而掉落。

對於這一切你能做什麼?

大規模遷移意味著基礎設施中發生大量活動、遷移和部署。 每次遷移可能需要一些時間來將容器映像交付和解壓到 Minions、啟動和初始化容器等。因此,最好先啟動較重要的任務,然後再啟動不太重要的任務。

讓我們再看看我們熟悉的服務層次結構,並試著確定我們要先執行哪些任務。

Odnoklassniki 中的一云 - 數據中心級操作系統

當然,這些都是直接參與處理使用者請求的進程,也就是prod。 我們用以下方式表示這一點 放置優先權 — 可以指派給佇列的號碼。 如果隊列具有較高優先級,則其服務被放置在最前面。

在 prod 上,我們分配更高的優先級,0; 批量 - 稍微低一點,100; 空閒時 - 甚至更低,200。優先順序按層次應用。 層次結構中較低的所有任務都將具有相應的優先權。 如果我們希望 prod 內的快取在前端之前啟動,那麼我們將優先權分配給快取 = 0 和前端子佇列 = 1。例如,如果我們希望首先從前端啟動主門戶,並且僅從前端啟動音樂那麼,我們可以為後者分配一個較低的優先權- 10。

下一個問題是缺乏資源。 因此,大量設備、資料中心的整個大廳都出現故障,我們重新啟動瞭如此多的服務,以至於現在沒有足夠的資源供每個人使用。 您需要決定犧牲哪些任務以保持主要關鍵服務的運作。

Odnoklassniki 中的一云 - 數據中心級操作系統

與放置優先順序不同,我們不能不加區別地犧牲所有批次任務;其中一些對於門戶的運作很重要。 因此,我們單獨強調 搶佔優先級 任務。 放置後,如果沒有更多的空閒 Minion,則較高優先順序的任務可以搶佔(即停止)較低優先順序的任務。 在這種情況下,低優先順序的任務可能會保持未放置狀態,即不再有合適的具有足夠可用資源的 Minion。

在我們的層次結構中,透過將空閒優先級指定為200,可以非常簡單地指定搶佔優先級,以便生產任務和批次任務搶佔或停止空閒任務,但不會互相搶佔或停止。就像放置優先等級一樣,我們可以使用我們的層次結構來描述更複雜的規則。 例如,如果我們沒有足夠的資源用於主入口網站,我們將犧牲音樂功能,將對應節點的優先順序設為較低:10。

整個 DC 事故

為什麼整個資料中心會故障? 元素。 是個好帖子 颶風影響了資料中心的工作。 這些元素可以被認為是無家可歸的人,他們曾經燒毀了歧管中的光學器件,並且資料中心與其他站點完全失去了聯繫。 失敗的原因也可能是人為因素:操作員會發出這樣的命令,整個資料中心就會陷落。 這可能是由於一個大錯誤而發生的。 一般來說,資料中心崩潰的情況並不少見。 這種情況每隔幾個月就會在我們身上發生一次。

這就是我們為防止任何人發推文 #alive 所做的事情。

第一個策略是隔離。 每個一雲實例都是隔離的,只能管理一個資料中心的機器。 也就是說,由於錯誤或不正確的操作員命令而導致的雲端損失僅相當於一個資料中心的損失。 我們已為此做好準備:我們有一個冗餘策略,其中應用程式和資料的副本位於所有資料中心。 我們使用容錯資料庫並定期測試故障。
從今天開始,我們有四個資料中心,這意味著四個獨立、完全隔離的一雲實例。

這種方法不僅可以防止物理故障,還可以防止操作員錯誤。

人為因素還能做什麼? 當操作員向雲端發出一些奇怪或潛在危險的命令時,他可能會突然被要求解決一個小問題,看看他的想法如何。 例如,如果這是許多副本的某種大規模停止或只是一個奇怪的命令 - 減少副本數量或更改映像的名稱,而不僅僅是新清單中的版本號。

Odnoklassniki 中的一云 - 數據中心級操作系統

結果

一雲的顯著特徵:

  • 服務和容器的分層和視覺化命名方案,這使您可以非常快速地找出任務是什麼、它與什麼相關、它是如何工作的以及誰負責它。
  • 我們應用我們的 產品與批次結合的技術Minions上的任務,以提高機器共享的效率。 我們使用 CPU 配額、共享、CPU 調度程式策略和 Linux QoS 來取代 cpuset。
  • 同一台機器上運作的容器不可能完全隔離,但它們之間的相互影響保持在20%以內。
  • 將服務組織成層次結構有助於使用以下命令進行自動災難復原 放置和搶佔優先級.

常見問題

為什麼我們不採取現成的解決方案?

  • 不同類別的任務隔離在放置在 Minions 時需要不同的邏輯。 如果簡單的預留資源就可以放置prod任務,那麼就必須放置batch和idle任務,追蹤minion機器上資源的實際使用率。
  • 需要考慮任務消耗的資源,例如:
    • 網路頻寬;
    • 磁碟的類型和“主軸”。
  • 緊急應變時需要指明服務的優先權、資源的命令權限和配額,透過一雲中的分層佇列來解決。
  • 需要對容器進行人工命名,以減少對事故和事件的回應時間
  • 一次性廣泛實施服務發現是不可能的; 需要與硬體主機上託管的任務長期共存 - 這可以透過容器後面的「靜態」IP 位址來解決,因此需要與大型網路基礎設施進行獨特的整合。

所有這些功能都需要對現有解決方案進行重大修改才能適合我們,並且在評估工作量後,我們意識到我們可以以大致相同的勞動力成本開發自己的解決方案。 但是您的解決方案將更容易操作和開發 - 它不包含支援我們不需要的功能的不必要的抽象。

對於那些閱讀最後幾行的人,感謝您的耐心和關注!

來源: www.habr.com

添加評論