熵簡技術談 | 私有化部署方案的演進

shangjiankeji發表於2020-12-15

作者資訊:本文出自熵簡後臺團隊,團隊致力於為熵簡科技各產品線構建高可用、易擴充套件、低運維的後臺系統,並逐步構建起統一、靈活的大後臺架構,幫助各研發團隊實現快速、高效的產品開發。

導讀:熵簡科技在近年的金融企業服務的過程當中,隨著業務的高速發展,對於私有化部署方案也經歷了多次升級,基於 DevOps 的思想,如 DRY (Don’t Repeat Yourself)、SSOT (Single Source of Truth)、CaC (Configuration as Code)、IaC (Infrastructure as Code) 等進行了多次改造。
本文主要介紹了熵簡私有化部署方案的演進策略和核心指導思想。整個過程分為三個階段,第一階段:以指令碼為核心的部署方式(docker檔案 + 指令碼 + 環境變數);第二階段:以 Jenkins 為核心的部署方式(docker檔案 + jenkins + 配置檔案);第三階段:以 Jenkins 和 docker harbor 為核心的部署方式(docker harbor + jenkins + 配置檔案)。

一、背景
熵簡科技在近年的金融企業服務的過程當中,隨著業務的高速發展,對於私有化部署方案也做了多次改進,基於 DevOps 的思想,如 DRY (Don’t Repeat Yourself)、SSOT (Single Source of Truth)、CaC (Configuration as Code)、IaC (Infrastructure as Code) 等進行了多次改造。
本文從 DevOps 的基本思想出發,以解決業務發展中各階段的核心問題為基本目標,詳細介紹了熵簡私有化部署方案的演進策略和核心指導思想。
這三個階段依次為:
第一階段:以指令碼為核心的部署方式(docker檔案 + 指令碼 + 環境變數)
第二階段:以 Jenkins 為核心的部署方式(docker檔案 + jenkins + 配置檔案)
第三階段:以 Jenkins 和 docker harbor 為核心的部署方式(docker harbor + jenkins + 配置檔案)

二、第一階段
在我們做第一個私有化部署的系統時,我們的系統架構相對簡單,包含的需要部署的元件不超過10個。所以對於部署方案,我們的考慮是:1. 能支援橫向擴充套件。2. 能支援快速部署啟動一個開發環境或者 debug 環境。3. 能支援快速部署一個新的客戶環境。所以在第一階段,我們的做法是用一個很牛逼的指令碼,一鍵啟動所有元件。其中元件需要相互關聯的部分,都通過變數設定在指令碼中,從而避免引數設定不一致導致的部署問題,例如:
在這裡插入圖片描述
這個方案在我們只有一個產品的時候是非常方便的,操作步驟簡單直接,且維護成本不高,也很靈活。但是隨著公司的產品不斷增多,需要進行復用的元件和工具也越來越多。例如兩個產品的部署過程中,可能都會需要部署 redis、mysql、RabbitMQ,在每個專案中都放一個相同的 start_redis.sh 的指令碼顯然不是一個很好的方案,這樣會導致後續對 redis 的版本升級或者引數調優無法在各個專案中應用。基於 DRY (Don’t Repeat Yourself) 原則,我們開啟了第二階段的部署方案升級。

三、第二階段
第二階段主要是為了解決幾個第一階段的問題:

  • 指令碼更新不及時。內部測試環境在更新部署的時候使用的是 k8s 叢集,於是沒有使用指令碼進行部署。所以常常出現程式碼進行了修改,但是隻更新了 k8s 的服務配置檔案,沒更新私有化部署指令碼的問題。

  • 使用方法和文件更新不及時。當某個元件的部署方式發生變更時,需要對應更新每一個產品的部署文件,常出現一個元件更新了,但是隻更新了一個產品的文件,其他的就忘了。

  • 指令碼複用性差。隨著產品的不斷增多,每個產品都有一部分重複的基礎元件的部署程式碼,這部分程式碼無法在產品間進行復用。

  • 容錯性差。當部署的過程中遇到錯誤時,在幾百行 shell 程式碼中找到問題的難度大。

  • 難以保證冪等性。shell 的編寫對大部分人來說是一個過程性的編寫方式,難以做到每個步驟都是冪等的,在執行指令碼的過程中對執行者要求比較高,需要考慮重複執行指令碼帶來的副作用。針對這些問題,我們也考慮了幾種不同的解決方案:
    3.1備選1: 使用docker編排引擎
    使用 k8s 或者 docker swarm 進行容器的編排,用編排檔案替代部署指令碼。這個方案也是我們在內部測試環境中用的方案,方案的好處很明顯:

  • 可以利用 k8s 的負載均衡完成叢集內部的橫向擴容,而不需要額外的負載均衡。

  • 可以更充分地利用 k8s 的資源排程,最大程度利用宿主主機的資源。

  • 可以使用宣告式的配置方式,宣告最終希望達成的狀態,而由編排引擎來生成、執行相應的操作。同時可以對配置檔案進行版本管理,達到 Configuration as Code 和 Infrastructure as Code 的方式

  • 可以保證在測試環境中的部署方式和私有化部署環境中一致,從而通過在測試過程中強制更新 k8s 保證程式碼和部署流程的一致性。
    除開業界已經認同的這些好處之外,也有一部分實際情況的制約:

  • 目前 k8s 叢集的部署和維護在國內的金融和實體企業仍然是相對空白的,也缺少對該類技術的瞭解。

  • 對於生產環境上使用的 k8s 叢集,機構往往希望可以有專業的商業支援服務,包括但不限於證書的配置除錯、叢集的擴縮容、異構硬體的支援、效能問題的排查、網路問題的排查等。這個支援的成本和其帶來的增益比還是不足以支撐這個方案的。
    3.2備選2: 基於成熟工具的編排部署(Jenkins + ansible)
    為了解決上一個備選方案的制約,我們考慮以一個相對成熟的工具來實現類似的功能。此時我們考慮使用 Jenkins 來進行指令碼的封裝和管理,同時使用 ansible 和統一的配置檔案來實現 k8s 能帶給我們的好處。
    3.2.1群的靈活排程和橫向擴容
    為了實現靈活的叢集內部的橫向擴容,我們使用一個集中式的閘道器進行網路請求的轉發,如下圖:
    在這裡插入圖片描述
    所有元件內部的請求訪問都經過一個 Nginx 閘道器進行轉發,這樣如果我們需要對某個服務進行宿主主機的變更,或者需要增加額外的例項進行橫向擴容,都可以在不更改其他服務配置的前提下進行。
    3.2.2宣告式的配置方式
    為了能達到宣告式的配置,我們將配置檔案簡化成了類似的格式:
    在這裡插入圖片描述
    而我們的啟動指令碼也統一讀取這個配置檔案來知悉他依賴的元件部署在哪,應該通過哪個埠進行訪問。這樣以來,我們便實現了 SSOT (Single Source of Truth),不會出現遷移修改元件後,依賴其的服務無法使用的情況。運維同學在看到這個配置檔案之後,也可以很直觀地看出來每個服務執行在哪、有什麼引數配置。同時我們把啟動指令碼轉換成了一個 Ansible 的指令碼。Ansible 的一個特點便是宣告式、可複用。我們的 Ansible 指令碼從 main.yaml 中讀取服務希望達到的狀態後,會進行所有相關的操作,包括生成配置檔案、拉取最新的 docker、啟動 docker、檢測是否需要初始化資料、檢測服務狀態符合預期等。這樣一來,我們也實現了 Configuration as Code 和 Infrastructure as Code 的部署方式。
    3.2.3介面化的封裝
    如果要遷移至 Ansible 和配置檔案的這個方案,就必須要解決測試環境的問題,我們希望這個部署方案是和開發、測試的部署方法一致的,這樣才能保證部署流程和程式碼的一致性。在這個基礎上,為了簡化研發的使用,我們對Ansible的指令碼和配置檔案用 Jenkins 進行了封裝。
    在這裡插入圖片描述
    在一個 docker 包中包含了:

  • Jenkins 的所有所需外掛和配置,例如每個任務需要呼叫哪個ansible指令碼,每個任務需要觸發哪些依賴的任務

  • 所有的 Ansible 部署指令碼

  • 預設的配置檔案

  • 部署需要用到的工具包,例如 ssh、ansible、openssl 等。
    在這裡插入圖片描述
    一來也大大減少了運維可能出錯的操作步驟,同時對於一套標準系統的部署時間也可以減少到小時級別。甚至內部的測試環境部署可以在30分鐘內完成,為後續的自動化測試打下基礎。
    3.2.4公共元件封裝
    我們最終選擇了方案二(Jenkins + Ansible)作為我們的解決方案,我們把內部所有需要進行私有化部署的系統都遷移到了基於 Jenkins 的部署系統上。
    在這裡插入圖片描述
    對於常用的公共元件包括儲存元件:redis、mysql、oracle、elasticsearch、minio 等,所有產品都可以遵循最佳實踐進行部署複用。而對於部分業務中介軟體,例如文件解析、NLP提取、格式轉換等服務,所有產品也可以快速接入使用。值得一提的是,其他的日誌、指標採集、監控告警、看板等功能,也成了整個私有化部署架構中的共享元件,賦能保證了產品的穩定性和可運維性。
    3.2.5不足之處
    在這套方案中,仍有幾個細節點是待改善的:

  1. 部署工具本身的版本管理。Jenkins 和部署指令碼的內容是會隨著產品的迭代而變化的,例如一個月前的產品使用的是Elasticsearch6.8,目前的產品使用的是Elasticsearch7.6,這時產品的升級過程中,就需要對部署指令碼做一定修改,如果使用了錯誤的部署工具,可能會導致部署的產品出錯。
  2. docker 映象檔案的傳輸和管理。由於在私有化部署的環境中,有許多環境是完全不能接入外網的,所以我們的 docker 是通過 docker save -o xxx.tar 進行匯出,然後將 tar 檔案通過 jenkins 下載、拷貝、並載入到目標伺服器上的。如果能接入外網,就直接從一個 http 伺服器下載並載入映象檔案。
    這個方案的第一個問題是 docker 的版本號不好管理。因為對於 tar 檔案的每個版本都歸檔會造成巨大的儲存資源的壓力,每個版本都儲存下來的話,對於一個整合了 CI/CD 的產品,每天會產生10餘個部署檔案,無疑是一個浪費。
    第二個問題是更新時較慢。每次更新程式碼時,及時只更新一行程式碼,可能也需要下載1G的 docker 檔案,導致在私有化環境中更新速度很慢。

四、第三階段
針對第二階段的一些不足之處,我們也做了更進一步的優化:
1.除了使用 docker 檔案進行打包和傳輸,我們也引入了私有化 docker 映象倉庫進行 docker 包的管理。利用 docker 映象倉庫本身的 TAG 功能進行多版本的歸檔。由於 docker 包的重複檔案層不會重複儲存,所以可以支援對所有產品版本的歸檔儲存。
2. 對於部署工具本身,也同時加入 docker 的映象倉庫中,用 TAG 進行多版本的歸檔管理,每次部署時,要求部署工具和程式碼包的版本作為一個統一整體進行部署。三階段的改動相對於第二階段並不算大,主要工作是:1. 將相關 docker 的打包 CI 任務中,增加推送到 docker 的映象倉庫的配置。2. 在預設配置檔案當中,原本的 image: 配置欄位設定為基於映象倉庫的地址。
3. 在 ansible 的啟動指令碼的 docker_container 模組中,增加 pull 選項的配置,決定是否在重啟的時候拉取最新的映象。這一步是相對 tricky 的,因為我們仍然要相容不能訪問私有化映象倉庫的環境,這些環境中 pull 需要設定為 false,而其他環境需要設定為 true。

五、總結
我們希望在滿足 DRY (Don’t Repeat Yourself)、SSOT (Single Source of Truth)、CaC (Configuration as Code)、IaC (Infrastructure as Code) 的原則基礎上,儘量簡化和自動化部署流程。雖然在實際專案中推廣 k8s 的使用遇到了一些困難,但是我們仍然曲線救國,利用老將 Jenkins 和 Ansible 達到了目的。

相關文章