將應用程式部署到 VM、Nomad 和 Kubernetes

大家好! 我的名字是帕維爾·阿加列茨基。 我在開發 Lamoda 交付系統的團隊中擔任團隊領導。 2018 年,我在 HighLoad++ 會議上發表了演講,今天我想展示我的報告的文字記錄。

我的主題致力於我們公司在不同環境中部署系統和服務的經驗。 從史前時代開始,我們將所有系統部署到普通的虛擬伺服器中,最後從Nomad逐漸過渡到部署在Kubernetes。 我會告訴你我們為什麼這樣做以及我們在這個過程中遇到了什麼問題。

將應用程式部署到虛擬機

首先,3年前,公司所有的系統和服務都部署在常規虛擬伺服器上。 從技術上講,它的組織方式是使用自動組裝工具(使用 jenkins)儲存和組裝我們系統的所有程式碼。 使用 Ansible,它從我們的版本控制系統推廣到虛擬伺服器。 而且,我們公司的每個系統都至少部署在兩台伺服器上:一台在機頭,一台在機尾。 這兩個系統在所有設定、電源、配置等方面都完全相同。 它們之間唯一的區別是 head 接收用戶流量,而 tail 不會接收用戶流量。

為什麼要這樣做?

當我們部署應用程式的新版本時,我們希望確保無縫部署,也就是說,不會對用戶造成明顯的後果。 這是因為使用 Ansible 的下一個編譯版本已推出尾部。 在那裡,參與部署的人員可以檢查並確保一切正常:所有指標、部分和應用程式都正常運作; 啟動必要的腳本。 只有在他們確信一切正常後,交通才會被切換。 它開始前往之前處於尾部的伺服器。 之前的頭部仍然沒有用戶流量,但仍然保留著我們應用程式的先前版本。

所以對於用戶來說這是無縫的。 因為切換是瞬時的,因為它只是切換平衡器。 您只需將平衡器切換回即可輕鬆回滾到先前的版本。 我們還可以在應用程式收到用戶流量之前驗證是否能夠投入生產,這非常方便。

我們在這一切中看到了哪些優勢?

  1. 首先,夠了 它就是有效的。 每個人都明白這樣的部署方案是如何運作的,因為大多數人都曾經部署過常規的虛擬伺服器。
  2. 這就夠了 可靠地,由於部署技術簡單,經過數千家公司的測試。 數以百萬計的伺服器都是這樣部署的。 打破東西很難。
  3. 最後我們可以得到 原子部署。 使用者同時進行部署,在舊版本和新版本之間沒有明顯的切換階段。

但我們也看到了這一切的一些缺點:

  1. 除了生產環境、開發環境之外,還有其他環境。 例如,品質保證和預生產。 當時我們有很多伺服器,大概有60個服務。 為此,有必要 對於每項服務,維護其最新版本 虛擬機。 此外,如果您想更新程式庫或安裝新的依賴項,則需要在所有環境中執行此操作。 您還需要將部署應用程式的下一個新版本的時間與 devops 執行必要的環境設定的時間同步。 在這種情況下,很容易陷入這樣一種情況,即我們的環境在所有環境中都會有所不同。 例如,在 QA 環境中會有一些版本的庫,而在生產環境中會有不同的庫,這會導致問題。
  2. 更新依賴項困難 你的申請。 這不取決於你,而是取決於其他團隊。 即,來自維護伺服器的 DevOps 團隊。 您必須給他們適當的任務並描述您想要做什麼。
  3. 當時,我們還想將我們擁有的大型整體劃分為單獨的小型服務,因為我們知道它們會越來越多。 當時我們已經有100多個了,每一個新的服務,都需要單獨建立一個新的虛擬機,也需要維護和部署。 另外,你不需要一輛車,而是至少兩輛車。 除此之外還有 QA 環境。 這會導致問題並使您建置和運行新系統變得更加困難。 複雜、昂貴且漫長的過程。

因此,我們決定從部署常規虛擬機器轉向在 Docker 容器中部署應用程式會更方便。 如果您有 docker,則需要一個可以在叢集中運行應用程式的系統,因為您不能只建立一個容器。 通常您想要追蹤有多少貨櫃被提升,以便它們自動提升。 因此,我們需要選擇一個控制系統。

我們考慮了很久我們可以選擇哪一個。 事實是,當時常規虛擬伺服器上的部署堆疊有些過時,因為它們沒有最新版本的作業系統。 在某個時候,甚至出現了 FreeBSD,但支援起來並不是很方便。 我們知道我們需要盡快遷移到 docker。 我們的開發人員根據不同解決方案的現有經驗,選擇了像 Nomad 這樣的系統。

切換到游牧者

Nomad 是 HashiCorp 的產品。 他們也因其其他解決方案而聞名:

將應用程式部署到 VM、Nomad 和 Kubernetes

“領事” 是一個服務發現工具。

“地形” - 一個用於管理伺服器的系統,允許您透過配置來配置它們,即所謂的基礎設施即程式碼。

“流浪漢” 允許您透過特定的設定檔在本機或雲端部署虛擬機器。

當時的 Nomad 似乎是一個相當簡單的解決方案,可以快速切換而無需更改整個基礎設施。 此外,它非常容易學習。 這就是我們選擇它作為容器過濾系統的原因。

將系統部署到 Nomad 需要什麼?

  1. 首先你需要 碼頭工人形象 你的申請。 您需要建置它並將其放置在 docker 映像存儲庫中。 在我們的例子中,這是一個工件 - 一個允許您將不同類型的各種工件推入其中的系統。 它可以儲存檔案、docker 映像、composer PHP 套件、NPM 套件等。
  2. 還需要 設定檔,這將告訴 Nomad 您要部署什麼、在哪裡以及要部署多少數量。

當我們談論Nomad時,它使用HCL語言作為其資訊文件格式,它代表 HashiCorp 配置語言。 這是 Yaml 的超集,可讓您用 Nomad 術語描述您的服務。

將應用程式部署到 VM、Nomad 和 Kubernetes

它允許您指定要部署多少個容器,以及在部署期間從哪些映像將各種參數傳遞給它們。 因此,您將此文件提供給 Nomad,它會根據該文件將容器啟動到生產環境中。

在我們的例子中,我們意識到簡單地為每個服務編寫完全相同的 HCL 檔案並不是很方便,因為有很多服務,有時您想要更新它們。 有時一項服務不是部署在一個實例中,而是部署在多個不同的實例中。 例如,我們生產中的系統之一有超過 100 個生產實例。 它們從相同的映像運行,但配置設定和配置檔案不同。

因此,我們決定將所有設定檔儲存在一個公共儲存庫中以方便部署。 這樣它們就可見了:它們很容易維護,我們可以看到我們擁有什麼系統。 如有必要,更新或更改某些內容也很容易。 新增系統也不難 - 您只需在新目錄中建立一個設定檔即可。 其中包含以下檔案:service.hcl,其中包含我們服務的描述,以及一些允許配置在生產中部署的服務的環境檔案。

將應用程式部署到 VM、Nomad 和 Kubernetes

然而,我們的一些系統在生產中部署時不是一個副本,而是同時部署多個副本。 因此,我們決定不以純粹的形式儲存配置,而是以模板化的形式儲存配置,這對我們來說會很方便。 我們選擇了 金賈2。 在這種格式中,我們儲存服務本身的配置及其所需的環境文件。

此外,我們還在儲存庫中放置了所有專案通用的部署腳本,它允許您啟動服務並將其部署到生產、所需的環境、所需的目標。 在我們將 HCL 配置轉換為模板的情況下,之前是常規 Nomad 配置的 HCL 檔案在這種情況下開始看起來有點不同。

將應用程式部署到 VM、Nomad 和 Kubernetes

也就是說,我們用從 env 檔案或其他來源取得的插入變數取代了一些配置位置變數。 此外,我們有機會動態收集HCL文件,也就是說,我們不僅可以使用普通的變數插入。 由於 jinja 支援循環和條件,因此您也可以在那裡建立設定文件,這些文件會根據您部署應用程式的特定位置而變更。

例如,您想要將服務部署到預生產和生產。 假設在預生產中您不想執行 cron 腳本,而只想查看單獨網域上的服務以確保其正常運作。 對於部署該服務的任何人來說,該過程看起來都非常簡單和透明。 您所需要做的就是執行deploy.sh 文件,指定要部署哪個服務以及部署到哪個目標。 例如,您想要將某個系統部署到俄羅斯、白俄羅斯或哈薩克。 為此,只需更改其中一個參數,您將獲得正確的配置。

當 Nomad 服務已經部署到您的叢集時,它看起來像這樣。

將應用程式部署到 VM、Nomad 和 Kubernetes

首先,您需要某種外部平衡器,它將接收所有用戶流量。 它將與 Consul 一起工作,並從中找出與特定網域相對應的特定服務所在的位置、節點、IP 位址。 Consul 中的服務來自 Nomad 本身。 由於這些是同一家公司的產品,因此彼此之間有很大的相關性。 可以說,Nomad 開箱即用,可以在 Consul 中註冊其中啟動的所有服務。

一旦您的前端負載平衡器知道將流量傳送到哪個服務,它就會將其轉送到與您的應用程式相符的適當容器或多個容器。 當然,安全的考量也是必要的。 儘管所有服務都在容器中的相同虛擬機器上運行,但這通常需要阻止任何服務對任何其他服務的自由存取。 我們透過細分實現了這一點。 每個服務都在自己的虛擬網路中啟動,在該虛擬網路上規定了路由規則以及允許/拒絕存取其他系統和服務的規則。 它們可能位於該星團內部,也可能位於該星團外部。 例如,如果您想要阻止某個服務連接到特定資料庫,可以透過網路層級分段來完成。 也就是說,即使是錯誤的,您也不會意外地從測試環境連接到生產資料庫。

就人力資源而言,轉型花了我們多少?

整個公司向 Nomad 的過渡大約需要 5-6 個月的時間。 我們在逐個服務的基礎上移動,但速度相當快。 每個團隊都必須為服務創建自己的容器。

我們採用了這樣的做法,每個團隊獨立負責自己系統的docker映像。 DevOps提供了部署所需的通用基礎設施,即對叢集本身的支援、對CI系統的支援等等。 當時,我們有60多個系統遷移到Nomad,總計約2個容器。

DevOps 負責與部署和伺服器相關的一切的通用基礎架構。 每個開發團隊負責為其特定係統實現容器,因為該團隊知道特定容器中通常需要什麼。

放棄 Nomad 的原因

透過切換到使用 Nomad 和 docker 等進行部署,我們獲得了哪些優勢?

  1. 我們 提供同等條件 適用於所有環境。 在開發、QA 環境、預生產、生產中,使用相同的容器鏡像,具有相同的依賴關係。 因此,您幾乎不可能最終在生產中得到的結果不是您之前在本地或測試環境中測試的結果。
  2. 我們也發現,這樣就夠了 輕鬆新增服務。 從部署的角度來看,任何新系統的啟動都非常簡單。 只需轉到儲存配置的儲存庫,為您的系統新增另一個配置,就可以了。 您可以將系統部署到生產環境,而無需開發人員進行任何額外的工作。
  3. 所有 設定檔 在一個公共儲存庫中 原來正在審核中。 當我們使用虛擬伺服器部署系統時,我們使用 Ansible,其中配置位於同一儲存庫中。 然而,對於大多數開發人員來說,這有點難以使用。 這裡,您需要新增的部署服務的配置和程式碼量已經變得小得多。 另外,開發人員很容易修復或更改它。 例如,在過渡到 Nomad 新版本的情況下,他們可以獲得並批量更新位於同一位置的所有操作檔案。

但我們也遇到了幾個缺點:

原來我們 無法實現無縫部署 以游牧者為例。 當在不同條件下推出容器時,它可能正在運行,Nomad 將其視為準備接收流量的容器。 這發生在其中的應用程式有機會啟動之前。 因此,系統開始在短時間內產生 500 個錯誤,因為流量開始流向尚未準備好接受它的容器。

我們遇到了一些 由沼澤地。 最重要的錯誤是,如果您有許多系統和容器,Nomad 不能很好地處理大型叢集。 當你想要取出Nomad叢集中包含的一台伺服器進行維護時,很有可能叢集會感覺不太好並且會崩潰。 例如,某些容器可能會下降而不上升 - 如果您的所有生產系統都位於由 Nomad 管理的叢集中,這將使您付出很大的代價。

所以我們決定考慮下一步該去哪裡。 那時,我們更清楚自己想要實現的目標。 即:我們想要可靠性,比Nomad提供的功能多一點,以及更成熟、更穩定的系統。

在這方面,我們選擇了 Kubernetes 作為最受歡迎的叢集啟動平台。 特別是考慮到我們集裝箱的尺寸和數量足夠大。 出於這樣的目的,Kubernetes 似乎是我們可以考慮的最合適的系統。

過渡到 Kubernetes

我將向您介紹一些 Kubernetes 的基本概念以及它們與 Nomad 的不同之處。

將應用程式部署到 VM、Nomad 和 Kubernetes

首先,Kubernetes中最基本的概念是pod的概念。 是一組總是一起運作的一個或多個容器。 而且它們總是嚴格地在一台虛擬機器上工作。 它們可以透過不同連接埠上的 IP 127.0.0.1 相互存取。

假設您有一個由 nginx 和 php-fpm(經典方案)組成的 PHP 應用程式。 最有可能的是,您希望始終將 nginx 和 php-fpm 容器保持在一起。 Kubernetes 讓您可以透過將它們描述為一個通用 Pod 來實現這一目標。 這正是 Nomad 無法做到的。

第二個概念是 部署。 事實上,pod 本身是一個短暫的東西;它會啟動並消失。 你要先殺掉所有以前的容器,然後立即推出新版本,還是想逐步推出它們?這就是部署概念負責的過程。 它描述瞭如何部署 Pod、數量以及如何更新它們。

第三個概念是 服務。 你的服務實際上就是你的系統,它接收一些流量,然後將其轉發到與你的服務對應的一個或多個 Pod。 也就是說,它允許您說具有此類名稱的此類服務的所有傳入流量都必須傳送到這些特定的 Pod。 同時它為您提供流量平衡。 也就是說,您可以啟動應用程式的兩個 Pod,並且所有傳入流量將在與該服務相關的 Pod 之間均勻平衡。

第四個基本概念是 入口。 這是一個運行在 Kubernetes 叢集上的服務。 它充當接管所有請求的外部負載平衡器。 使用 Kubernetes API,Ingress 可以確定這些請求應發送到何處。 而且,他這樣做非常靈活。 您可以說,對此主機和此類 URL 的所有請求都會傳送到此服務。 這些到達該主機和另一個 URL 的請求將被傳送到另一個服務。

從開發應用程式的人的角度來看,最酷的事情是您能夠自己管理這一切。 透過設定 Ingress 配置,您可以將所有到達某個 API 的流量傳送到用 Go 等語言編寫的單獨容器。 但是,這些流量,來自同一個網域,但不同的 URL,應該發送到用 PHP 編寫的容器,其中有很多邏輯,但速度不是很快。

如果我們將所有這些概念與Nomad進行比較,我們可以說前三個概念都是服務。 Nomad 本身並不存在最後一個概念。 我們使用了一個外部平衡器:它可以是 haproxy、nginx、nginx+ 等。 對於立方體,您不需要單獨引入這個附加概念。 然而,如果你看看 Ingress 的內部,它要么是 nginx、haproxy,要么是 traefik,但有點內建在 Kubernetes 中。

事實上,我描述的所有概念都是 Kubernetes 叢集中存在的資源。 為了在多維資料集中描述它們,使用了 yaml 格式,在 Nomad 的情況下,它比 HCL 檔案更具可讀性和熟悉性。 但在結構上,它們描述的是相同的東西,例如 pod。 他們說 - 我想在那裡部署這樣那樣的吊艙,帶有這樣那樣的圖像,以這樣那樣的數量。

將應用程式部署到 VM、Nomad 和 Kubernetes

此外,我們意識到我們不想手動建立每個單獨的資源:部署、服務、Ingress 等。 相反,我們希望在部署過程中用 Kubernetes 來描述我們的每個系統,這樣我們就不必以正確的順序手動重新建立所有必要的資源依賴項。 Helm 被選為允許我們做到這一點的系統。

Helm 中的基本概念

頭盔是 套件管理器 對於 Kubernetes。 它與程式語言中的套件管理器的工作方式非常相似。 它們允許您以以下形式儲存由部署 nginx、部署 php-fpm、Ingress 配置、configmaps(這是一個允許您為系統設定 env 和其他參數的實體)組成的服務:稱為圖表。 同時,頭盔 運行在 Kubernetes 之上。 也就是說,這不是某種孤立的系統,而只是立方體內啟動的另一項服務。 您可以透過控制台命令透過其 API 與其進行互動。 它的便利性和美妙之處在於,即使 helm 損壞或您將其從叢集中刪除,您的服務也不會消失,因為 helm 本質上只用於啟動系統。 Kubernetes 本身負責服務的效能和狀態。

我們也意識到 模板化之前我們被迫自己將 jinja 引入到我們的配置中,這是 helm 的主要功能之一。 你為系統創建的所有配置都以模板的形式儲存在 helm 中,有點類似於 jinja,但實際上,使用 Go 語言的模板,helm 是用 Go 語言編寫的,就像 Kubernetes 一樣。

Helm 為我們增加了更多概念。

圖表 - 這是對您的服務的描述。 在其他套件管理器中,它被稱為套件、捆綁包或類似的名稱。 這裡稱為圖表。

價值觀 是您想要用來從模板建立配置的變數。

發行。 每次使用 helm 部署的服務都會收到版本的增量版本。 Helm 會記住上一個版本、先前的版本等的服務配置。 因此,如果需要回滾,只需執行 helm 回呼命令,將其指向先前的發行版本即可。 即使您的儲存庫中的對應配置在回滾時不可用,helm 仍然會記住它是什麼,並將您的系統回滾到上一個版本中的狀態。

當我們使用 helm 時,Kubernetes 的常規配置也會變成模板,在其中可以使用變數、函數和應用條件語句。 這樣您就可以根據環境收集您的服務配置。

將應用程式部署到 VM、Nomad 和 Kubernetes

在實務中,我們決定採取與 Nomad 稍有不同的做法。 如果在 Nomad 中,部署我們的服務所需的部署配置和 n 變數都儲存在一個儲存庫中,那麼我們決定將它們劃分為兩個單獨的儲存庫。 「deploy」儲存庫僅儲存部署所需的 n 個變量,「helm」儲存庫儲存配置或圖表。

將應用程式部署到 VM、Nomad 和 Kubernetes

這為我們帶來了什麼?

儘管事實上我們不在設定檔本身中儲存任何真正敏感的資料。 例如,資料庫的密碼。 它們作為秘密儲存在 Kubernetes 中,但儘管如此,我們仍然不希望讓每個人都可以存取某些內容。 因此,對「deploy」儲存庫的存取受到更多限制,而「helm」儲存庫僅包含服務的描述。 因此,它可以被更廣泛的人安全地訪問。

由於我們不僅有生產環境,還有其他環境,由於這種分離,我們可以重複使用 Helm Charts,不僅將服務部署到生產環境,還可以部署到 QA 環境等。 甚至可以使用以下方式在本地部署它們 迷你酷 - 這是本地運行 Kubernetes 的東西。

在每個儲存庫中,我們為每個服務劃分了不同的目錄。 也就是說,每個目錄內都有與對應圖表相關的模板,並描述啟動系統所需部署的資源。 我們在「deploy」儲存庫中只留下了 envs。 在這個例子中,我們沒有使用 jinja 進行模板化,因為 helm 本身提供了開箱即用的模板化 - 這是它的主要功能之一。

我們留下了一個部署腳本——deploy.sh,它簡化並標準化了使用 helm 進行部署的啟動。 因此,對於任何想要部署的人來說,部署介面看起來與透過 Nomad 部署時完全相同。 相同的deploy.sh、您的服務的名稱以及您要部署它的位置。 這會導致 helm 在內部啟動。 反過來,它從模板中收集配置,將必要的值檔案插入其中,然後部署它們,將它們啟動到 Kubernetes 中。

發現

Kubernetes 服務似乎比 Nomad 更複雜。

將應用程式部署到 VM、Nomad 和 Kubernetes

這裡傳出流量到達 Ingress。 這只是前端控制器,它接管所有請求,並隨後將它們發送到請求資料對應的服務。 它根據配置來確定它們,這些配置是 helm 中應用程式描述的一部分以及開發人員自己設定的。 該服務將請求傳送到其 pod(即特定容器),以平衡屬於該服務的所有容器之間的傳入流量。 當然,我們不應該忘記,我們不應該偏離網路層級的安全性。 因此,在 Kubernetes 叢集中,分段是基於標記的。 所有服務都有特定的標籤,這些標籤與服務對叢集內部或外部的某些外部/內部資源的存取權限相關聯。

當我們進行過渡時,我們看到 Kubernetes 擁有我們之前使用過的 Nomad 的所有功能,並且還添加了許多新東西。 它可以透過插件進行擴展,實際上可以透過自訂資源類型進行擴展。 也就是說,您不僅有機會使用 Kubernetes 附帶的開箱即用的東西,而且還有機會創建自己的資源和服務來讀取您的資源。 這為您提供了擴充系統的更多選項,而無需重新安裝 Kubernetes,也無需進行修改。

Prometheus 就是此類使用的一個範例,它在我們的 Kubernetes 叢集內運作。 為了讓它開始從特定服務收集指標,我們需要在服務描述中添加額外類型的資源,即所謂的服務監視器。 Prometheus 由於在 Kubernetes 中啟動時可以讀取自訂資源類型,因此會自動開始從新系統收集指標。 相當方便。

我們對 Kubernetes 進行的首次部署是在 2018 年 3000 月。 在此期間我們從未遇到任何問題。 它工作相當穩定,沒有明顯的錯誤。 此外,我們還可以進一步擴展它。 今天我們已經擁有了足夠的功能,而且我們非常喜歡 Kubernetes 的發展速度。 目前,Kubernetes 中有超過 XNUMX 個容器。 叢集佔用多個Node。 同時,它耐用、穩定、可控性強。

來源: www.habr.com

添加評論