本文作者:kiloson
近些年隨着微服務、kubernetes 等技術的發展,越來越多的廠商將單體架構的項目進行微服務化。但是隨着原有項目的不斷拆分,微服務的數量越來越多,部署的頻率也越來越高,傳統手工運維的劣勢越發明顯,效率低、部署質量沒有保證。在雲原生時代,是否有一種更加高效、穩定的部署方式,可以幫助我們改進部署和管理流程呢?
隨着我們對運維方法的調研,我們發現 GitOps 能夠很好的解決這些問題。GitOps 是一種符合 DevOps 思想的運維方式,GitOps 以 Git 倉庫作為唯一的事實來源,儲存聲明式配置,並通過自動化工具實現環境和應用的自動化管理。Git 實現了版本控制、回滾、多人協作;聲明式配置保證了配置的可讀性和事務性;自動化部署消除了人為錯誤,調高了部署效率和準確性,同時也保證了多環境的一致性。所以 GitOps + 聲明式配置 能夠很好的解決傳統運維的痛點,提高部署效率,保證部署質量。
主機部署的缺陷
在傳統的雲主機部署模式下,通過工單創建運維請求,運維人員接收到工單後,通過 Ansible 等運維工具手動進行運維操作。這種方式在實際操作過程中遇到了許多問題,比如由於 Ansible 基於 SSH 下發文件,所以需要給每台機器配置 SSH;因為機器底層的異構,導致運維需要修改配置文件;或是因為腳本執行順序錯誤,導致需要重新執行整個部署流程;手工操作,導致部署效率低,容易出錯,無法保證部署質量。
總的來説,雲主機時代運維存在以下缺陷:
- 環境不一致:需要step-by-step的編寫腳本,設想目標環境中的各種情況,編寫腳本時需要考慮各種情況,比如機器是否已經部署過,機器是否已經配置過 SSH,機器是否已經安裝過依賴等等。並且腳本運行在不同環境中可能會有不同的結果。
- 無事務保證:安裝腳本不能被打斷,如果中途遇到問題,服務可能處於不可用的中間狀態。
- 協作困難:需要另行編寫文檔描述運維流程,如果多人同時維護一個腳本,協作往往非常困難。
- 回滾困難:部署流程難以回滾,如果部署過程中出現問題,需要手動執行逆向操作。
- 權限管控與審核:通常運維需要目標主機的 root 權限,難以限制運維人員的權限,同時也難以對整個運維動作進行審核。
雲原生時代部署特點
雲原生的代表技術包括容器、服務網格、微服務、不可變基礎設施和聲明式API。雲原生時代,微服務架構應用成為了主流。微服務架構的特點是將應用拆分成多個服務,每個服務都有自己的數據庫和配置文件。每個微服務都是獨立部署的,這大大提高了部署的頻率,帶來了新的挑戰:
- 部署頻繁:微服務應用被拆分成了多個服務,每個服務都需要獨立部署,部署頻率大大提高,需要更高的部署效率
- 多副本:微服務通過擴容副本的方式來提高可用性,通常需要部署多副本,有時甚至需要部署數百個副本
- 多環境:通常需要部署多個環境,比如開發環境、測試環境、預發環境和生產環境
為滿足以上需求,我們需要一種全新的部署方式
- 其應該有較高的自動化水平,能夠減少人工參與,減少出錯,提高部署效率
- 應該有良好的版本控制,方便回滾
- 應該保證多環境一致,快速在多個環境中拉起相同的應用,方便測試和驗證
- 應該能夠保證事務,避免部署過程中出錯,導致服務不可用
- 便於多人協作,提高部署效率
什麼是GitOps
如果我們需要自己實現一種滿足需求的部署方式,我們需要自己實現一個版本管理系統,這需要很大的工作量。但是事實上市面上已經存在一個十分優秀的版本管理系統,那就是 Git。
能不能直接基於 Git 進行部署呢?我們順着這個思路繼續調研基於 Git 的部署方式,最終發現了 GitOps,GitOps 能夠很好的滿足以上需求。
那麼究竟什麼是 GitOps 呢?
GitOps 的關鍵是使用 Git 倉庫儲存聲明式配置,通過自動化工具將 Git 倉庫中的配置應用到目標環境中。Git 倉庫滿足了對於版本管理、回滾、多人協作的需求,聲明式配置滿足了對於事務性、一致性的需求,而自動化工具提高了部署的自動化水平。所以 GitOps 能夠很好的滿足雲原生時代的部署需求,是一種優秀的部署方式。
Git倉庫
Git 倉庫所有開發者都很熟悉,它是一個分佈式的版本控制系統,可以方便的進行版本管理和回滾。在 GitOps 中,Git 倉庫作為唯一的事實來源,儲存所有的配置信息。
使用 Git 倉庫儲存配置,可以方便的進行版本管理和回滾,並且天然支持多人協作,同時修改配置文件。並且通過 Pull Request 提交修改,可以基於 Code Review 保證修改的正確性和質量。
聲明式配置
聲明式配置使用配置文件直接描述系統的期望狀態,使用者不需要考慮執行流程和目標環境的差異,易於編寫、理解、代碼 review 和進行版本管理。並且聲明式配置天然具備冪等性,可以重複應用而不會導致系統狀態發生變化。具備事務性,要麼全部應用成功,要麼什麼都不做。以 Kubernetes 資源配置文件為例,使用者只需要指定 CPU 和 Memory 的大小,而不需要關心底層執行細節和環境差異,保證了各個環境中部署一致。
apiVersion: apps/v1
kind: Deployment
metadata:
name: example-deployment
spec:
replicas: 3
selector:
matchLabels:
app: example-app
template:
metadata:
labels:
app: example-app
spec:
containers:
- name: example-container
image: example-image
resources:
limits:
cpu: 1
memory: 512Mi
requests:
cpu: 500m
memory: 256Mi
自動化工具
GitOps 中的自動化工具負責將 Git 倉庫中的配置應用到目標環境中。自動化工具可以是Gitlab CI、 Github Action 這類流水線工具,也可以是 Argo CD 這類專門用於 GitOps 的工具,自動化工具可以根據 Git 倉庫中的配置,自動化的完成部署、回滾、監控、告警等工作。
以 ArgoCD 為例,用户只需要創建 Git 倉庫和 ArgoCD Application,ArgoCD 就會自動的將 Git 倉庫中的配置應用到目標環境中。並且 ArgoCD 會實時監聽 Git 倉庫的變化,一旦 Git 倉庫中的配置發生變化,ArgoCD 也可以進行自動同步。
自動化工具能夠提高部署自動化水平、效率,減少人工參與,減少出錯,提高部署效率。滿足了我們對高自動化水平的需求。
GitOps 實踐
經過上面的描述,相信大家已經對 GitOps 有了一個初步的認識。下面,我們將通過雲音樂內部的實踐,展示 GitOps 在生產環境中的應用與優勢。
在雲音樂全面推進容器化的過程中,需要安裝部署許多的管控面組件,比如Grafana、Argo Rollout等,並且需要在多個環境中安裝相同的應用。出於對 GitOps 理念的認同,我們設計了設計了一套基於 Gitlab CI 流水線的 GitOps 部署體系。
管控面運維
在雲音樂的容器化過程中,我們使用了許多開源的管控面組件,比如 Prometheus、Grafana、Argo Rollout 等。我們有許多 kubernetes 集羣,很多 kubernetes 集羣中都需要安裝相同的組件,通過手工安裝費時費力,而且很難保證所有環境的配置一致。並且,在運維過程中,可能會出現多人修改同一個應用配置的情況,如何避免修改衝突和覆蓋?Kubernetes 十分強大,但同時其也非常複雜,有海量的配置項可以修改,如何保證配置的正確性?
為了解決以上問題,我們設計了一套基於 GitOps 的自動化運維流程。每個線上組件都會有兩到三個對應的倉庫,分別是:代碼倉庫、配置倉庫、Helm Chart 倉庫。其中,代碼倉庫存放組件的源代碼,如果是開源組件,則直接使用開源的 release,沒有對應倉庫;Helm Chart 倉庫存放組件的 Helm Chart,配置倉庫通過 Helm Dependency 引用 Helm Chart 倉庫中的 Chart,並存放了 values.yaml 文件,用於配置組件的參數。
將通用配置抽出來,放到同一個 Helm Cart 中,並在部署倉庫中引用該 Chart,可以有效避免因多環境導致的配置不一致問題。
當需要修改配置時,開發者只需要修改配置倉庫中的 values.yaml 文件,並向原倉庫提交 MR,這時會觸發流水線,驗證修改是否正確。通過驗證後,開發者需要請求團隊中的其他人幫忙 review。通過檢查後,將 MR 合併到 master 分支,這時會觸發流水線,運行修改之後的配置,使用 Helm Upgrade 命令將組件更新到環境中。發佈完成後,如果開發者本次更新升級有任何問題,可以通過運行上一次的部署流水線,將組件回滾到上一次的版本。
為了強制執行以上過程,通常會回收開發者對於 master 分支的更新權限。開發者只能通過向原倉庫提交 MR 的方式,來配置修改。這樣,就可以保證配置和環境的一致性。為了避免開發者沒有合入權限,我們開發了一個 review 機器人,當開發者提交 MR 時,機器人會在評論區進行評論,要求其他人 review 並投票。當該 MR 獲得足夠的票數(通常是兩票)後,機器人會自動合入 MR。
需要注意的是,這裏選擇了使用一個倉庫對應一個環境,但是實際上,也可以使用一個倉庫對應多個環境,只需要在倉庫中創建多個分支,每個分支對應一個環境,然後在流水線中,根據分支名稱,選擇對應的環境。為什麼選擇倉庫對應環境的方式而不是分支對應環境的形式呢?主要是因為倉庫對應環境的形式在權限管控方面比較有優勢,因為開發者可能需要不同環境擁有不同的權限,如果選擇分支對應環境,那麼就需要在部署流程中對不同環境的權限進行管控,這樣很麻煩。
並且通過引入測試流水線和 Reviewer 降低了出錯的概率,通過重新運行部署流水線完成了快速回滾,通過 commit 記錄每次部署的操作者,通過自動化部署減少了人工介入,解決了絕大部分傳統部署過程中的問題。
Horizon 應用部署
以上實踐,部署少量的管控面組件還是比較方便的,但當部署的應用數量增多時,就會變得比較麻煩。因為每個應用都需要創建對應的倉庫和創建流水線,這樣就會導致倉庫、流水線數量過多,維護成本過高。
為了優化雲音樂大量應用的部署流程,我們開發了 Horizon CD 平台,Horizon 基於 GitOps、ArgoCD 部署應用。通過 Horizon,開發者只需要在 Horizon 平台上創建應用,配置應用的參數,就可以完成應用的部署,並且也可以享受到 GitOps 帶來的好處。
開發者通過填寫表單即可在 Horizon 上創建應用,表單中包含了應用的基本信息和部署信息,例如應用名稱、應用描述、鏡像地址、副本數、部署環境等。Horizon 會為應用創建對應的 GitOps 倉庫,並將用户輸入以及其它創建應用必要的信息一併寫入到 GitOps 倉庫中。GitOps 倉庫中的每個分支對應不同的環境,方便管理多環境。
用户可以根據以上創建的應用,創建對應的應用實例,應用實例對應 Kubernetes 中的一系列相關資源。Horizon 會為該應用實例創建 GitOps 倉庫和 ArgoCD Application。 該 GitOps 倉庫有兩個 branch —— master 和 gitops。master 和 gitops 分支都存放了應用的配置。用户修改應用配置後,Horizon 會將修改記錄到 gitops 分支中。用户發佈應用時,Horizon 會將 gitops 分支合併到 master 分支中,並觸發 ArgoCD 同步,將 GitOps 倉庫中的配置應用到 Kubernetes 中。這樣,就完成了一次部署。
如果有人手動修改了 kubernetes 中相關資源或者修改了 GitOps 倉庫但並未執行同步,ArgoCD 會感知到 master 分支配置與 kubernetes 中資源配置不一致,會將該應用標記為 OutOfSync,在 Horizon 上,用户也可以觀察到該應用狀態不正常,方便用户及時發現問題,並與 Horizon 管理員聯繫,及時排查解決。
Horizon 依賴於 GitOps 倉庫實現回滾,Horizon 的每次發佈會在 master 分支生成一條 commit 記錄,當用户需要回滾應用實例時,只需要找到當時的部署記錄,即一條 Pipelinerun 記錄,代表了一次流水線運行。Horizon 通過該 Pipelinerun 記錄找到對應的 commit 記錄,然後將該記錄之後的所有 commit 記錄revert,最後觸發 ArgoCD 的同步,這樣就完成了一次回滾。
GitOps 倉庫
Horizon 通過拓展 Helm Chart,設計了一套 Template 系統。Template 包含三個部分,Helm Chart,JsonSchema 和 ReactJsonSchemaForm。Horizon 會通過 ReactJsonSchemaForm 渲染表單,獲取用户輸入,並使用 JsonSchema 驗證用户輸入,確認無誤後,記錄到 GitOps 倉庫的相關文件中。GitOps 倉庫是一個 Helm Chart 倉庫,部署時,ArgoCD 通過 Helm 渲染 Manifest,並將 Manifest 應用到 Kubernetes 中。Horizon 管理員可以通過自定義 Template,實現部署各種類型的應用,非常靈活。
以下為 GitOps 倉庫結構
Chart.yaml 文件是 Helm Chart 的標準,通過 dependency 字段,引用預先定義的 Horizon Template。
apiVersion: v2
name: demo
version: 1.0.0
dependencies:
- name: deployment
version: v0.0.1-ec06d596
repository: https://horizon-harbor-core.horizon.svc.cluster.local/chartrepo/horizon-template
application.yaml 包含了用户通過 ReactJsonSchemaForm 表單填寫的數據。
deployment:
app:
envs:
- name: test
value: test
spec:
replicas: 1
resource: x-small
pipeline-output.yaml 包含了 CI 階段的輸出,因為在 Horizon 中 CI 也是可以自定義的,所以該文件的內容也是不固定的。默認的 CI 腳本輸出如下:
deployment:
image: library/demo:v1
git:
branch: master
commitID: 28992d8f35a6ef38d59181080b3728df9540d8d6
url: https://github.com/horizoncd/springboot-source-demo.git
| 參數 | 描述 |
|---|---|
| .Values.image | CI 階段構建 image 的全路徑 |
| .Values.git.{ref} | 源代碼倉庫的引用類型,可以是 branch、tag、commit |
| .Values.git.commitID | 構建代碼的 commit ID |
| .Values.git.url | 源代碼的引用鏈接 |
pipeline.yaml 包含了 CI 階段的配置信息,Horizon 管理員可以通過自定義 CI 以支持更多的構建類型
pipeline:
buildType: dockerfile
dockerfile:
path: ./Dockerfile
| 參數 | 描述 |
|---|---|
| .Values.buildType | 該應用的構建類型,默認是“dockerfile” |
| .Values.dockerfile.path | dockerfile 相對於源代碼倉庫的路徑 |
| .Values.dockerfile.content | dockerfile 的內容 |
sre.yaml 包含了一些管理員配置,比如ingress、默認的超售比等等。使用 sre.yaml 文件修改管理員配置,既可以做到關注點分離,又保證了 GitOps 應用修改的體驗統一。比如可以通過配置nodeAffinity,將應用部署到特定的節點上。SRE在修改sre.yaml後,也需要提交 PR 到 GitOps 倉庫,並通過 review 機器人完成多人審核,合入到發佈分支,最後在 Horizon 上執行發佈,即可完成變更。
deployment:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: cloudnative/demo
operator: In
values:
- "true"
system目錄下的文件記錄了部署的元信息
env.yaml 記錄了部署環境相關的信息
| 參數 | 描述 |
|---|---|
| .Values.env.environment | 環境名 |
| .Values.env.region | Kubernete 名 |
| .Values.env.namespace | Namespace |
| .Values.env.baseRegistry | image 倉庫的地址 |
| .Values.env.ingressDomain | ingress 域名 |
deployment:
env:
environment: local
region: local
namespace: local-1
baseRegistry: horizon-harbor-core.horizon.svc.cluster.local
ingressDomain: cloudnative.com
horizon.yaml 包含了該應用在 Horizon 中的信息
| 參數 | 描述 |
|---|---|
| .Values.horizon.application | 應用名 |
| .Values.horizon.clusterID | 集羣ID(這裏的集羣指應用實例,應用為配置集合) |
| .Values.horizon.cluster | 集羣 |
| .Values.horizon.template.name | 模板名 |
| .Values.horizon.template.release | 模板版本 |
| .Values.horizon.priority | 優先級 |
restart.yaml Horizon 通過修改該文件內容,重啓所有 Pod
| 參數 | 含義 |
|---|---|
| .Values.restartTime | 重啓時間 |
deployment:
restartTime: "2023-01-06 18:28:49"
結語
通過以上部署模式,我們可以很方便的管理數十個環境上百管控面管控面組件的部署,而且每個環境的配置都是獨立的,修改對應的配置倉庫不會影響其他環境的部署。同時,基於 Horizon 平台,我們實現了部署的自動化,用户發佈應用時,只需要填寫表單,即可完成應用發佈,無需運維人員介入,運維效率提升 10 倍以上。Horizon 平台如今每天的發佈數量已經達到了 1000+,這是傳統運維模式難以企及的。在達成高效發佈的同時,也保證了發佈的質量,每次發佈都有對應的流水線記錄,可以方便的回滾到任意版本。Horizon 將開發、運維、測試等多個團隊的工作流程串聯起來,實現了 DevOps 的理念。
GitOps 在雲音樂的實踐中,表現出了非常好的效果,但同時在實踐中我們也發現了一些問題:
- 修改不便:設想,如果我們有多個環境,每個環境都對應一個配置倉庫,那麼一旦需要修改一個統一的值,那麼需要修改所有倉庫。
- 密碼管理:Git 倉庫中數據都是明文顯示,並且 Git 倉庫會記住所有的歷史修改,所以放在 Git 倉庫中的明文信息應該加密。雖然社區裏面開發了一些類似於 git-secret 的工具,但使用起來還是不太方便。這裏需要注意的是,密碼管理指將密鑰放置於 Git 倉庫中,和 Gitlab、Github secret並不一致。將密碼放在 secret 中,就失去了 Git 倉庫提供的 版本管理、回滾、審計等能力。
- 標準不統一:對於回滾的實現,到底是修改配置,reset 到對應版本;還是通過運行 CI,重新部署到環境中?對於不同環境的相同應用部署,到底是選擇多個倉庫,還是一個倉庫多個環境?這些都沒有統一的標準,需要根據自身情況選擇。
所以 GitOps 並不是銀彈,使用者任需要基於自身情況判斷,選擇最適合自己的方案。 但是 GitOps 作為隨着雲原生出生的 DevOps 方法,還在快速發展中,相信以上提到的問題,以後都會逐漸被解決。我們也會持續關注、嘗試 GitOps 領域的最新技術和解決方案。如果你對 Horizon 或者 GitOps,可以加入我們,和我們一起討論。
本文發佈自網易雲音樂技術團隊,文章未經授權禁止任何形式的轉載。我們常年招收各類技術崗位,如果你準備換工作,又恰好喜歡雲音樂,那就加入我們 grp.music-fe(at)corp.netease.com!