GitOps多環(huán)境部署問題及解決方案

大型組織應(yīng)用GitOps難免會遇到在多環(huán)境中部署的問題,本文分析了應(yīng)用環(huán)境分支策略會遇到到問題,介紹了應(yīng)用文件夾策略解決這些問題的方案。原文:Stop Using Branches for Deploying to Different GitOps Environments[1], How to Model Your Gitops Environments and Promote Releases between Them[2]

在關(guān)于GitOps問題的指南中,我們簡要解釋了(參見第3和第4點)當(dāng)前GitOps工具在支持不同環(huán)境部署以及多集群配置建模時的問題。

“如何將發(fā)布部署到下一個環(huán)境?”的問題在希望采用GitOps的組織中越來越受到重視[4],并且有幾種可能的答案。但在這篇文章中,我們將重點討論在這一過程中不應(yīng)該做什么。

我們不應(yīng)該使用Git分支來建模不同的環(huán)境。如果保存配置的Git存儲庫(在Kubernetes的例子中是manifests/templates)有名為“預(yù)發(fā)”、“QA”、“生產(chǎn)”等分支,那就掉進(jìn)了陷阱。

重要的事情說三遍:
使用Git分支來建模不同的環(huán)境是一種反模式,不要這樣做!
使用Git分支來建模不同的環(huán)境是一種反模式,不要這樣做!
使用Git分支來建模不同的環(huán)境是一種反模式,不要這樣做!

我們將從以下幾點探討為什么這個實踐是反模式:

  1. 在部署環(huán)境中使用不同的Git分支是過去的遺留問題。
  2. 不同分支之間的pull request和合并是有問題的。
  3. 人們傾向于包含特定于環(huán)境的代碼并創(chuàng)建不同的配置。
  4. 一旦環(huán)境數(shù)量增多,環(huán)境的維護(hù)就會變得難以控制。
  5. 每個環(huán)境的分支模型違背了現(xiàn)有的Kubernetes生態(tài)系統(tǒng)。

在不同環(huán)境中采用分支應(yīng)該只應(yīng)用于遺留應(yīng)用程序。

當(dāng)問到為什么選擇Git分支來建模不同的環(huán)境時,回答幾乎總是“我們一直都是這樣做的”,“感覺很自然”,“這是開發(fā)人員知道的”等等。

這沒有錯,大多數(shù)人都熟悉在不同環(huán)境中使用分支。這一實踐是由古老的Git-Flow模型[3]大力推廣的。但自從引入這種模式以來,情況發(fā)生了很大的變化,甚至最初的作者也從宏觀角度發(fā)出了嚴(yán)重警告,建議人們不要在不了解后果的情況下采用這種模式。

事實上,Git-flow模型……

  • 專注于應(yīng)用程序源代碼,而不是環(huán)境配置(更不用說Kubernetes manifest了)。
  • 如果需要在生產(chǎn)環(huán)境中支持多個應(yīng)用版本,這一模型很合適,通常沒有這種場景,但也時有發(fā)生。

因為本文是關(guān)于GitOps環(huán)境而不是應(yīng)用程序源代碼的,因此不打算在這里過多討論Git-flow及其缺點,總而言之,如果需要為不同的環(huán)境支持不同的特性,那么應(yīng)該遵循基于主干的開發(fā)[5]并使用特性標(biāo)志[6]。

在GitOps上下文中,應(yīng)用程序源代碼和配置也應(yīng)該在不同的Git存儲庫中(一個存儲庫只有應(yīng)用程序代碼,一個存儲庫有Kubernetes manifests/templates)。這意味著應(yīng)用程序源代碼分支不應(yīng)該影響環(huán)境存儲庫中的分支。

當(dāng)我們在項目中采用GitOps時,應(yīng)用程序開發(fā)人員可以為源代碼選擇想要的任何分支策略(甚至使用Git-flow),但是環(huán)境配置Git存儲庫(包含所有Kubernetes manifests/templates)不應(yīng)該遵循每個環(huán)境一個分支的模型。

部署升級絕不是簡單的Git合并

既然我們已經(jīng)了解了在部署中使用按環(huán)境區(qū)分分支的方法的歷史,就可以討論其缺點了。

這種方法的主要優(yōu)點是“部署升級是一個簡單的git合并”。理論上,如果想要將一個版本從QA環(huán)境升級部署到預(yù)發(fā)環(huán)境,只需將QA分支合并到預(yù)發(fā)分支即可。當(dāng)我們準(zhǔn)備好生產(chǎn)環(huán)境時,再次將預(yù)發(fā)分支合并到生產(chǎn)分支,就可以確定來自預(yù)發(fā)的所有變更已經(jīng)部署到了生產(chǎn)環(huán)境中。

想知道生產(chǎn)環(huán)境和預(yù)發(fā)環(huán)境之間有什么不同嗎?只需要在兩個分支之間做一個標(biāo)準(zhǔn)的git diff[7]就可以了。想要將配置變更從預(yù)發(fā)環(huán)境反向移植到QA環(huán)境?從預(yù)發(fā)分支到QA分支的一個簡單的Git合并就可以做到這一點。

如果想對部署升級施加額外的限制,可以使用Pull Requests。一方面任何人都可以觸發(fā)從QA到預(yù)發(fā)的合并,另一方面如果想在生產(chǎn)分支中合入一些東西,可以觸發(fā)Pull Request并要求所有利益相關(guān)者手動批準(zhǔn)。

這在理論上聽起來很棒,一些瑣碎的場景實際上可以像這樣工作。但在實踐中,情況并非如此。通過Git合并來升級一個版本可能會遇到合并沖突、引入不想要的變更,甚至觸發(fā)錯誤的變更順序。

下面我們以Kubernetes部署為例看一個簡單的例子,當(dāng)前部署位于預(yù)發(fā)分支中:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: example-deployment
spec:
  replicas: 15
  template:
    metadata:
      labels:
        app: my-app
    spec:
      containers:
      - name: backend
        image: my-app:2.2
        ports:
        - containerPort: 80

QA團(tuán)隊已經(jīng)通知我們說版本2.3(位于QA分支中)看起來已經(jīng)準(zhǔn)備好了,可以轉(zhuǎn)移到交付階段。我們將QA分支合并到預(yù)發(fā)分支,部署應(yīng)用程序,并認(rèn)為一切都很好。

但我們不知道,由于某些資源限制,有人將QA分支中的副本數(shù)量更改為2。使用Git合并,不僅將2.3部署到了預(yù)發(fā)環(huán)境,而且還將副本改成了2個(而不是15個),這可能并不是我們想要的。

你可能會說,在合并之前查看副本個數(shù)很容易,但請記住,在實際場景中,有大量的應(yīng)用程序,其中有大量的manifests被模板化(通過Helm或Kustomize)。因此,理解想要帶來什么變化,留下什么變化并不是一件小事情。

即使我們確實發(fā)現(xiàn)了不應(yīng)該被合并的變更,也需要使用git cherry-pick[8]或其他非標(biāo)準(zhǔn)方法手動選擇“好的”部分,這與最初的“簡單的”git合并相去甚遠(yuǎn)。

但是,即使我們知道了所有可以合并的變更,也會出現(xiàn)合并的順序與提交的順序不同的情況。例如,QA環(huán)境上有以下4個更改。

  1. 更新了應(yīng)用ingress[9]的主機(jī)名。
  2. 版本2.5被部署到QA環(huán)境,所有QA人員開始測試。
  3. 在2.5版本中發(fā)現(xiàn)了一個問題,并修復(fù)了Kubernetes的configmap。
  4. 資源限制[10]進(jìn)行了微調(diào),并提交到QA分支。

然后我們決定ingress設(shè)置和資源限制應(yīng)該部署到下一個環(huán)境(預(yù)發(fā)),但是QA團(tuán)隊還沒有完成2.5版本的測試。

如果我們盲目的將QA分支合并到預(yù)發(fā)分支,就將同時合并所有4個變更,包括2.5的升級。

為了解決這個問題,需要再次使用git cherry-pick或其他手動方法。

在更復(fù)雜的情況下,提交之間存在依賴關(guān)系,因此即使是cherry-pick也幫不上忙。

在上面的示例中,版本1.24必須部署到生產(chǎn)環(huán)境。問題是其中一個提交(hotfix)包含了大量的變更,而其中某些變更又依賴于另一個提交(ingress配置變更),而后者本身無法部署到生產(chǎn)環(huán)境(因為只適用于預(yù)發(fā)環(huán)境)。因此,即使是精心挑選,也不可能只將所需的變更從準(zhǔn)備階段引入到生產(chǎn)階段。

最終的結(jié)果是,部署升級絕不是簡單的Git合并。大多數(shù)組織還擁有大量應(yīng)用,這些應(yīng)用位于大量集群中,由大量manifests組成,手動選擇變更將是一場失敗的戰(zhàn)斗。

特定于環(huán)境的變更更容易造成配置漂移

理論上,配置漂移不應(yīng)該成為Git合并的問題。如果在預(yù)發(fā)環(huán)境中進(jìn)行了變更,然后將該分支合并到生產(chǎn)環(huán)境,那么所有變更都應(yīng)該遷移到新環(huán)境中。

然而在實踐中,事情是不一樣的,因為大多數(shù)組織只向一個方向合并,團(tuán)隊成員很容易改變上游環(huán)境,而從不將這些改變遷移到下游環(huán)境。

在QA、預(yù)發(fā)和生產(chǎn)三個環(huán)境的經(jīng)典例子中,Git合并的方向只有一個。人們將QA分支合并到預(yù)發(fā),將預(yù)發(fā)分支合并到生產(chǎn),這意味著變化只會向上流動。

QA -> 預(yù)發(fā)(Staging) -> 生產(chǎn)(Production).

典型場景是,在生產(chǎn)環(huán)境中需要對配置進(jìn)行快速變更(一個hotfix),然后有人部署了該修復(fù)程序。在Kubernetes的情況下,這個修補(bǔ)程序可以是任何東西,比如對現(xiàn)有manifest的更改,甚至是一個全新的manifest。

現(xiàn)在生產(chǎn)環(huán)境有了一個與預(yù)發(fā)完全不同的配置。下次一個版本從臨時版本升級到生產(chǎn)版本時,Git只會通知我們將從臨時版本升級到生產(chǎn)版本。生產(chǎn)上的臨時變更永遠(yuǎn)不會出現(xiàn)在Pull Request中的任何地方。

因為現(xiàn)在生產(chǎn)中有一個沒有文檔化的變更,這意味著所有后續(xù)部署都可能失敗,而這個變更永遠(yuǎn)不會被任何后續(xù)升級檢測到。

理論上,我們可以反向遷移這些變更,并周期性的將所有提交從生產(chǎn)階段合并到交付階段(以及交付階段合并到QA階段)。實際上,由于前面提到的原因,這種情況從未發(fā)生過。

可以想象,如果有很多環(huán)境,就會進(jìn)一步放大這個問題。

總而言之,通過Git合并來部署發(fā)布版本并不能解決配置漂移問題,而且實際上團(tuán)隊會試圖做出一些不按順序合并的特殊變更,因此會使問題更加嚴(yán)重。

在大量環(huán)境中管理不同的Git分支是一場注定失敗的戰(zhàn)斗

在前面的所有示例中,我只使用了3個環(huán)境(QA環(huán)境->預(yù)發(fā)環(huán)境->生產(chǎn)環(huán)境)來說明基于分支的環(huán)境部署的缺點。

根據(jù)組織的大小,也許有更多的環(huán)境,如果考慮地理位置等其他因素,那么環(huán)境的數(shù)量就會迅速增加。

我們以某個公司為例,它有5個工作環(huán)境:

  1. 負(fù)載測試
  2. 集成測試
  3. QA
  4. 預(yù)發(fā)
  5. 生產(chǎn)

我們假設(shè)最后3個環(huán)境也部署在歐洲、美國和亞洲,而前2個環(huán)境也有GPU和非GPU變體,這意味著該公司共有13個環(huán)境,而這只是針對單個應(yīng)用的。

如果使用基于分支的方法:

  • 在任何時候都需要有13個長期Git分支。
  • 需要13個pull requests才能跨所有環(huán)境部署一個變更。
  • 有一個二維的部署升級矩陣,縱向5步,橫向2-3步。
  • 錯誤合并、配置漂移和特別變更的可能性在所有環(huán)境組合中都有可能出現(xiàn)。

在這個示例組織的上下文中,所有以前的問題現(xiàn)在都更加普遍了。

branch-per-environment模型與Helm/Kustomize背道而馳

描述應(yīng)用程序的兩個最流行的Kubernetes工具是Helm和Kustomize,我們看看這兩種工具如何對不同環(huán)境進(jìn)行建模。

對于Helm,需要創(chuàng)建一個通用chart,該chart本身接受values.yaml形式的參數(shù),如果希望擁有不同的環(huán)境,則需要多個values文件[11]

對于Kustomize,需要創(chuàng)建一個“base”配置,然后每個環(huán)境被建模為一個overlay,有自己的文件夾:

在這兩種情況下,不同的環(huán)境使用不同的文件夾/文件進(jìn)行建模。Helm和Kustomize對Git分支、Git merge或Pull Requests一無所知,只使用普通文件。

再重復(fù)一遍:Helm和Kustomize在不同的環(huán)境下使用普通文件,而不是Git分支。這是一個很好的提示,說明如何使用這兩種工具建模不同的Kubernetes配置。

如果引入Git分支,不僅會引入額外的復(fù)雜性,還會違背自己的工具。

在GitOps環(huán)境中部署發(fā)布的推薦方法

建模不同的Kubernetes環(huán)境,并在環(huán)境之間部署發(fā)布,對于所有采用GitOps的團(tuán)隊來說都是非常普遍的問題。盡管非常流行的方法是在每個環(huán)境中使用Git分支,并假設(shè)每次部署都是一個“簡單的”Git合并,但在本文中已經(jīng)看到,這是一個反模式。


下面我們將介紹一種更好的方法來為不同的環(huán)境建模,從而在不同的Kubernetes集群上部署發(fā)布,之前的介紹(關(guān)于Helm/Kustomize)應(yīng)該已經(jīng)給了你一點關(guān)于這種方案的提示。

下面我會解釋如何在同一個Git分支上使用不同的文件夾對GitOps環(huán)境進(jìn)行建模,以及如何通過簡單的文件復(fù)制操作來處理環(huán)境升級(簡單的和復(fù)雜的)。

GitOps環(huán)境部署升級

首先了解應(yīng)用程序

在創(chuàng)建文件夾結(jié)構(gòu)之前,需要先做一些研究,了解應(yīng)用程序的“設(shè)置”。盡管一些人以通用的方式討論應(yīng)用程序配置,但實際上并不是所有的配置設(shè)置都同樣重要。

在Kubernetes應(yīng)用的上下文中,我們有以下幾類“環(huán)境配置”:

  1. 容器tag形式的應(yīng)用版本。這可能是Kubernetes manifest中最重要的設(shè)置(就環(huán)境升級而言)。根據(jù)不同的用例,只需更改容器鏡像版本即可。不過有可能源代碼中的新變更也需要更改部署環(huán)境。
  2. 應(yīng)用相關(guān)的Kubernetes特定配置。包括應(yīng)用的副本和其他Kubernetes相關(guān)信息,如資源限制、運行狀況檢查、持久卷、親和性規(guī)則等。
  3. 基本靜態(tài)業(yè)務(wù)配置。這是一組與Kubernetes無關(guān)的設(shè)置,但與應(yīng)用業(yè)務(wù)有關(guān)??赡苁峭獠縰rl、內(nèi)部隊列大小、UI默認(rèn)值、身份驗證配置文件等。所謂“基本靜態(tài)”,我指的是為每個環(huán)境定義一次的設(shè)置,然后永遠(yuǎn)不會更改。例如,我們總是希望生產(chǎn)環(huán)境使用production.paypal.com,而非生產(chǎn)環(huán)境使用staging.paypal.com。在不同的環(huán)境中,這是一個我們永遠(yuǎn)不希望遷移的設(shè)置。
  4. 非靜態(tài)業(yè)務(wù)配置。和上一點一樣,但包含了希望在不同環(huán)境之間遷移的設(shè)置,可以是全球VAT設(shè)置、推薦引擎參數(shù)、可用的比特率編碼,以及任何其他特定于業(yè)務(wù)的配置。

必須了解所有不同的設(shè)置是什么,更重要的是,哪些屬于第4類,因為這些是我們希望隨應(yīng)用程序版本一起推廣的設(shè)置。

這樣就可以覆蓋所有可能的部署場景:

  1. 應(yīng)用在QA中從版本1.34升級到1.35,這是一個簡單的源代碼變更,因此只需要在QA環(huán)境中更改容器鏡像屬性。
  2. 應(yīng)用在預(yù)發(fā)環(huán)境中從版本3.23升級到3.24,這不是一個簡單的源代碼變更,不但需要更新容器鏡像屬性,而且從QA環(huán)境帶來了新的設(shè)置“recommender.batch_size”。

我看到很多團(tuán)隊不理解不同配置參數(shù)之間的區(qū)別,而只使用一個配置文件(或機(jī)制)來設(shè)置不同域的值(即運行時和應(yīng)用業(yè)務(wù)配置)。

有了配置列表以及所屬區(qū)域之后,就可以創(chuàng)建環(huán)境結(jié)構(gòu)并優(yōu)化需要經(jīng)常變更并且需要在不同環(huán)境之間遷移的文件復(fù)制操作。

5個GitOps環(huán)境及其變更示例

我們來看一個實際的例子。

我們將對之前提到的環(huán)境進(jìn)行建模,該公司有5個不同的環(huán)境:

  1. 負(fù)載測試
  2. 集成測試
  3. QA
  4. 預(yù)發(fā)
  5. 生產(chǎn)

我們假設(shè)最后兩個環(huán)境也部署在歐洲、美國和亞洲,而前兩個環(huán)境也有GPU和非GPU變體,這意味著該公司共有11個環(huán)境。

可以在 https://github.com/kostis-codefresh/gitops-environment-promotion 找到建議的文件夾結(jié)構(gòu),所有環(huán)境都是同一分支中的不同文件夾,對于不同的環(huán)境沒有分支。如果想知道在一個環(huán)境中部署了什么,只需查看repo的主分支中的envs/。

在解釋結(jié)構(gòu)之前,有一些免責(zé)聲明:

免責(zé)聲明1: 寫這篇文章花了我很長時間,因為不確定應(yīng)該討論Kustomize[12]、Helm[13]還是普通的manifests。我選擇了Kustomize,因為它更簡單(在文章的最后我也提到了Helm)。但是請注意,示例repo中的Kustomize模板只是為了演示目的。本文不是Kustomize教程。在實際應(yīng)用中,你可能有Configmap生成器[14]、定制補(bǔ)丁[15],并采用和這里展示的完全不同的“組件”結(jié)構(gòu)。如果你不熟悉Kustomize,請先花些時間理解它的功能,然后再回來。

免責(zé)聲明2: 我用于部署的應(yīng)用[16]完全只是為了演示,它的配置由于簡潔和簡單的原因而忽略了幾個最佳實踐。例如,某些部署缺少運行狀況檢查[17],所有部署都缺少資源限制[18]。同樣,本文不會討論如何創(chuàng)建Kubernetes部署,你應(yīng)該已經(jīng)知道正確的部署manifests是什么樣子的。如果想了解更多關(guān)于生產(chǎn)級最佳實踐的信息,請參閱另一篇文章 https://codefresh.io/kubernetes-tutorial/kubernetes-antipatterns-1/

拋開免責(zé)聲明不說,下面是存儲庫的結(jié)構(gòu):

GitOps目錄結(jié)構(gòu)

base目錄保存對所有環(huán)境通用的配置,不會經(jīng)常改變。如果同時對多個環(huán)境進(jìn)行更改,最好使用“variants”文件夾。

variants文件夾(或者叫mixins、components)保存不同環(huán)境之間的共同特征。在研究上一節(jié)討論的應(yīng)用程序之后,可以自行定義你認(rèn)為的環(huán)境之間的“共同之處”。

在示例應(yīng)用中,我們?yōu)樗衟rod和非prod環(huán)境以及地區(qū)提供了variants。下面是一個適用于所有生產(chǎn)環(huán)境的prod variant[19]示例。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-deployment
spec:
  template:
    spec:
      containers:
      - name: webserver-simple
        env:
        - name: ENV_TYPE
          value: "production"
        - name: PAYPAL_URL
          value: "production.paypal.com"   
        - name: DB_USER
          value: "prod_username"
        - name: DB_PASSWORD
          value: "prod_password"                     
        livenessProbe:
          httpGet:
            path: /health
            port: 8080

在上面的示例中,我們確保所有生產(chǎn)環(huán)境都使用了生產(chǎn)DB憑證、生產(chǎn)支付網(wǎng)關(guān)和活動探針(這是一個精心設(shè)計的示例,請參閱本節(jié)開頭的免責(zé)聲明2)。這些設(shè)置屬于我們不希望在不同環(huán)境之間遷移的配置集,我們假設(shè)在整個應(yīng)用生命周期中這些都是靜態(tài)的。

準(zhǔn)備好base和variants之后,可以用這些屬性的組合來定義每個最終環(huán)境。

下面是一個ASIA環(huán)境的示例[20]:

apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization

namespace: staging
namePrefix: staging-asia-

resources:
- ../../base

components:
  - ../../variants/non-prod
  - ../../variants/asia

patchesStrategicMerge:
- deployment.yml
- version.yml
- replicas.yml
- settings.yml

首先定義一些公共屬性,從base環(huán)境、非prod環(huán)境和asia的所有環(huán)境中繼承所有配置。

這里的關(guān)鍵點是我們應(yīng)用的補(bǔ)丁。version.yml[21]和replicas.yml[22]是自解釋的,只定義自己的鏡像和副本,其他什么都沒有。

version.yml文件(這是環(huán)境間最重要的東西)只定義了應(yīng)用的鏡像,其他什么都沒有。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-deployment
spec:
  template:
    spec:
      containers:
      - name: webserver-simple
        image: docker.io/kostiscodefresh/simple-env-app:2.0

我們希望在不同環(huán)境之間部署的每個版本的相關(guān)設(shè)置也在settings.yml[23]中定義。

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: simple-deployment
spec:
  template:
    spec:
      containers:
      - name: webserver-simple
        env:
        - name: UI_THEME
          value: "dark"
        - name: CACHE_SIZE
          value: "1024kb"
        - name: PAGE_LIMIT
          value: "25"
        - name: SORTING
          value: "ascending"    
        - name: N_BUCKETS
          value: "42"         

請隨意查看整個存儲庫[16],以理解所有kustomizations的構(gòu)造方式。

通過GitOps執(zhí)行初始部署

要將應(yīng)用程序部署到相關(guān)的環(huán)境中,只需將GitOps控制器指向相應(yīng)的“env”文件夾,kustomize將創(chuàng)建完整的settings和values層次結(jié)構(gòu)。

下面是在Staging/Asia中運行的示例應(yīng)用程序[16]

GitOps應(yīng)用示例

可以在命令行上使用Kustomize預(yù)覽將為每個環(huán)境部署的內(nèi)容,例如:

kustomize build envs/staging-asia
kustomize build envs/qa
kustomize build envs/integration-gpu

當(dāng)然,也可以將上述命令的輸出通過管道輸出到kubectl來部署每個環(huán)境,但在GitOps的上下文中,應(yīng)該始終讓GitOps控制器部署環(huán)境,避免手動kubectl操作。

比較兩個環(huán)境的配置

對于軟件團(tuán)隊來說,一個非常普遍的需求是理解兩個環(huán)境之間的不同之處。我看到一些團(tuán)隊有這樣的誤解,他們認(rèn)為只有使用分支才能很容易的發(fā)現(xiàn)不同環(huán)境之間的差異。

這與事實相去甚遠(yuǎn)。通過比較文件和文件夾,可以很容易的使用成熟的文件diff工具來查找環(huán)境之間的不同之處。

最簡單的方法是只區(qū)分對應(yīng)用程序至關(guān)重要的設(shè)置。

vimdiff envs/integration-gpu/settings.yml envs/integration-non-gpu/settings.yml
GitOps settings diff

在kustomize的幫助下,還可以比較任意數(shù)量的環(huán)境,獲取整體的概念:

kustomize build envs/qa/> /tmp/qa.yml
kustomize build envs/staging-us/ > /tmp/staging-us.yml
kustomize build envs/prod-us/ > /tmp/prod-us.yml
vimdiff /tmp/staging-us.yml /tmp/qa.yml /tmp/prod-us.yml
GitOps環(huán)境diff

個人認(rèn)為這種方式和在環(huán)境分支之間執(zhí)行“git diff”沒有什么差別。

如何在GitOps環(huán)境中進(jìn)行部署升級

現(xiàn)在文件結(jié)構(gòu)已經(jīng)很清楚了,終于可以回答這個古老的問題:“我如何用GitOps部署發(fā)布”?

讓我們看看下面一些部署場景。如果你有關(guān)注文件結(jié)構(gòu),應(yīng)該已經(jīng)了解到所有部署升級都可以解析為簡單的文件復(fù)制操作。

場景: 在美國將應(yīng)用版本從QA升級到預(yù)發(fā)環(huán)境:

  1. cp envs/qa/version.yml envs/staging-us/version.yml
  2. commit/push變更

場景: 從GPU集成測試到GPU負(fù)載測試,再到QA的應(yīng)用版本升級。這是一個兩步的過程:

  1. cp envs/integration-gpu/version.yml envs/load-gpu/version.yml
  2. commit/push變更
  3. cp envs/load-gpu/version.yml envs/qa/version.yml
  4. commit/push變更

場景: 通過額外配置,將應(yīng)用程序從prod-eu升級到prod-us。這里我們還復(fù)制了settings文件。

  1. cp envs/prod-eu/version.yml envs/prod-us/version.yml
  2. cp envs/prod-eu/settings.yml envs/prod-us/settings.yml
  3. commit/push變更

場景: 確保QA擁有與staging-asia相同的副本數(shù)量

  1. cp envs/staging-asia/replicas.yml envs/qa/replicas.yml
  2. commit/push變更

場景: 從QA到移植所有配置到集成測試(非gpu版本)

  1. cp envs/qa/settings.yml envs/integration-non-gpu/settings.yml
  2. commit/push變更

場景: 一次性對所有非prod環(huán)境進(jìn)行全局更改(但請參閱下一節(jié),以了解關(guān)于此操作的一些討論)

  1. 在variants/non-prod/non-prod.yml中做出變更
  2. commit/push變更

場景: 向所有美國環(huán)境(包括生產(chǎn)環(huán)境和預(yù)發(fā)環(huán)境)添加新的配置文件。

  1. 在variants/us文件夾中添加新的manifest
  2. 修改variants/us/kustomization.yml引入新的manifest
  3. commit/push變更

一般來說,所有的部署升級只是復(fù)制操作。與branch-per-environment方法不同,現(xiàn)在可以自由的將任何東西從任何環(huán)境推廣到其他環(huán)境,不必?fù)?dān)心進(jìn)行錯誤的變更。特別是當(dāng)涉及到反向移植配置時,environment-per-folder確實很出色,因為可以簡單地“向上”或“向后”移動配置,甚至可以在不相關(guān)的環(huán)境之間移動配置。

注意,我使用cp操作只是為了演示。在實際的應(yīng)用程序中,此操作將由CI系統(tǒng)或其他編排工具自動執(zhí)行。根據(jù)環(huán)境的不同,你可能想先創(chuàng)建一個Pull Request,而不是直接在主分支中編輯文件夾。

一次對多個環(huán)境進(jìn)行更改

首先,我們需要定義“多重”環(huán)境的確切含義,假設(shè)以下兩種情況。

  1. 同時更改同一“級別”上的多個環(huán)境。例如,想要同時變更“prod-us”、“prod-eu”和“prod-asia”。
  2. 同時更改不在同一級別上的多個環(huán)境。例如,想同時更改“integration”和“staging-eu”。

第一種情況是有效場景,我們將在下面討論。但是,我認(rèn)為第二個場景是反模式,擁有不同環(huán)境的關(guān)鍵在于能夠以一種漸進(jìn)的方式發(fā)布內(nèi)容,并推動從一個環(huán)境到下一個環(huán)境的變化。因此,如果你發(fā)現(xiàn)自己在不同的環(huán)境中部署了相同的變化,問問自己是否真的需要這樣做以及為什么。

對于部署單個更改到多個“類似”環(huán)境的有效場景,有兩種策略:

  1. 如果你確定更改是絕對“安全的”,并且希望立即應(yīng)用到所有環(huán)境,那么可以在適當(dāng)?shù)膙ariant(或各自的文件夾)中進(jìn)行更改。例如,如果你在variants/non-prod文件夾中提交/推送一個更改,那么所有非生產(chǎn)環(huán)境都會同時應(yīng)用這個更改。我個人反對這種方法,因為有些更改在理論上看起來是“安全的”,但在實踐中可能會有問題。
  2. 更可取的方法是將更改應(yīng)用于每個單獨的文件夾,然后將其移動到“父”variant(當(dāng)它在所有環(huán)境中都存在時)。

讓我們舉個例子。我們想做一個影響所有EU環(huán)境的改變(例如GDPR功能[24])。簡單的方法是將配置更改直接提交/推送到variants/eu文件夾。這確實會影響到所有的EU環(huán)境(prod-eu和staging-eu)。但是,這有一點風(fēng)險,因為如果部署失敗,就會導(dǎo)致生產(chǎn)環(huán)境崩潰。

建議采用如下方法:

  1. 首先在envs/staging-eu中做出變更
  2. 然后對envs/prod-eu做同樣的修改
  3. 最后,從兩個環(huán)境中刪除更改,并將其添加到variants/eu中(通過一個commit/push操作)。
GitOps漸進(jìn)升級

你可以從漸進(jìn)的數(shù)據(jù)庫重構(gòu)[25]中認(rèn)識到這種模式。最后的提交是“過渡性的”,不會以任何方式影響任何環(huán)境。Kustomize將在這兩種情況下創(chuàng)建完全相同的定義,GitOps控制器應(yīng)該不會發(fā)現(xiàn)任何差異。

這種方法的優(yōu)點是,當(dāng)我們在環(huán)境中移動更改時,可以輕松回滾/恢復(fù)更改。缺點是需要增加工作(和提交)將更改推廣到所有環(huán)境,但是我相信這些工作的好處大于風(fēng)險。

如果采用這種方法,意味著永遠(yuǎn)不會直接對base文件夾應(yīng)用新的更改。如果希望對所有環(huán)境進(jìn)行更改,則首先將更改應(yīng)用于單個環(huán)境和/或variants,然后將其反向移植到base文件夾,同時將其從所有下游文件夾中刪除。

“environment-per-folder”方法的優(yōu)點

既然我們已經(jīng)分析了“environment-per-folder”方法的所有內(nèi)部工作原理,現(xiàn)在就該解釋為什么它比“branch-per-environment”方法更好了。如果你已經(jīng)看過前面的部分,那么應(yīng)該已經(jīng)理解了“environment-per-folder”方法是如何避免之前分析的所有問題的。

環(huán)境分支最突出的問題是提交的順序,以及從一個環(huán)境合并到另一個環(huán)境時帶來不必要更改的風(fēng)險。使用文件夾方法,這個問題就完全消除了:

  1. 提交順序現(xiàn)在已經(jīng)無關(guān)緊要了。當(dāng)你將一個文件從一個文件夾復(fù)制到下一個文件夾時,不需要關(guān)心它的提交歷史,只需要關(guān)心它的內(nèi)容。
  2. 通過只復(fù)制周圍的文件,只拿需要的東西,而不拿其他東西。當(dāng)你復(fù)制envs/qa/version.yml到env/staging-asia/version.yml中,可以確定只升級了容器鏡像,沒有其他東西。如果其他人在QA環(huán)境中改變了副本,并不會影響升級流程。
  3. 不需要使用git cherry-picks或任何其他高級的git方法來升級版本,只需要復(fù)制文件,并且可以訪問用于文件處理的實用程序的成熟生態(tài)系統(tǒng)。
  4. 可以自由的從任何環(huán)境對上游或下游環(huán)境進(jìn)行任何更改,而不受環(huán)境正確“順序”的任何限制。例如,如果想將設(shè)置從prod-us反向移植到staging-us,可以簡單的將env/prod-us/settings.yml拷貝到env/staging-us/settings.yml,而不用擔(dān)心可能會無意中部署了不相關(guān)的只應(yīng)在生產(chǎn)環(huán)境中應(yīng)用的修補(bǔ)程序。
  5. 可以容易的使用文件diff操作來了解各個環(huán)境之間的不同之處(源環(huán)境和目標(biāo)環(huán)境,反之亦然)

我認(rèn)為這些優(yōu)勢對于任何重要的應(yīng)用程序都是非常重要的,我敢打賭大型組織中總會有幾個“失敗的部署”可以直接或間接歸因于有問題的environment-per-branch模型。

之前我們提到的第二個問題是,將一個分支合并到下一個環(huán)境時,會出現(xiàn)配置漂移。這樣做的原因是,當(dāng)你執(zhí)行“git merge”時,git只會通知你它將帶來的更改,而不會告訴你目標(biāo)分支中已經(jīng)發(fā)生了什么更改。

同樣,文件夾方案完全消除了這個問題。正如前面說的,文件diff操作沒有“方向”的概念,可以從任何環(huán)境向上或向下復(fù)制任何設(shè)置,如果對文件執(zhí)行diff操作,可以看到環(huán)境之間的所有更改,而不管它們的上游/下游位置如何。

關(guān)于環(huán)境分支的最后一點是隨著環(huán)境數(shù)量的增長,分支復(fù)雜性將會線性增加。對于5個環(huán)境,需要在5個分支之間切換更改,而對于20個環(huán)境,需要處理20個分支。在大量的分支之間正確遷移發(fā)布版本是一個繁瑣的過程,在生產(chǎn)環(huán)境中,這是一場災(zāi)難。

使用文件夾方法,分支的數(shù)量不僅是靜態(tài)的,而且只有一個。如果有5個環(huán)境,可以用“主”分支來管理,如果需要更多的環(huán)境,你只需要添加額外的文件夾。如果20個環(huán)境,仍然只需要一個Git分支。當(dāng)只有一個分支時,獲得部署的集中視圖是很簡單的。

在GitOps環(huán)境中使用Helm

如果你不使用Kustomize而是更喜歡Helm,也可以創(chuàng)建一個文件夾層次結(jié)構(gòu),其中包含所有環(huán)境的“通用”設(shè)置,特定的特性/mixins/組件,以及特定于每個環(huán)境的最終文件夾。

下面是文件夾結(jié)構(gòu)的樣子:

chart/
  [...chart files here..]
common/
  values-common.yml
variants/
  prod/
     values-prod.yml
  non-prod/
    Values-non-prod.yml
  [...other variants…]
 envs/
     prod-eu/
           values-env-default.yaml
           values-replicas.yaml
           values-version.yaml
           values-settings.yaml
   [..other environments…]

同樣,你需要花一些時間來檢查應(yīng)用屬性,并決定如何將它們分割成不同的values文件,以獲得最佳的升級速度。

除此之外,在環(huán)境升級方面,大多數(shù)過程都是一樣的。

場景: 在US將應(yīng)用版本從QA提升到預(yù)發(fā)環(huán)境:

  1. cp envs/qa/values-version.yml envs/staging-us/values-version.yml
  2. commit/push變更

場景: 從GPU集成測試到GPU負(fù)載測試,再到QA的應(yīng)用版本升級。這是一個兩步的過程:

  1. cp envs/integration-gpu/values-version.yml envs/load-gpu/values-version.yml
  2. commit/push變更
  3. cp envs/load-gpu/values-version.yml envs/qa/values-version.yml
  4. commit/push變更

場景: 通過額外配置,將應(yīng)用從prod-eu提升到prod-us。這里我們還復(fù)制了settings文件。

  1. cp envs/prod-eu/values-version.yml envs/prod-us/values-version.yml
  2. cp envs/prod-eu/values-settings.yml envs/prod-us/values-settings.yml
  3. commit/push變更

理解Helm(或者你的GitOps代理處理Helm)如何處理多個values文件以及它們相互覆蓋的順序也是非常重要的。

如果希望預(yù)覽某個環(huán)境,可以使用以下命令,而不是“kustomize build”:

helm template chart/ --values common/values-common.yaml --values variants/prod/values-prod.yaml –values envs/prod-eu/values-env-default.yml –values envs/prod-eu/values-replicas.yml –values envs/prod-eu/values-version.yml –values envs/prod-eu/values-settings.yml

可以看到,如果在每個環(huán)境文件夾中都有大量的variants或文件,那么Helm比Kustomize更麻煩一些。

environment-per-git-repo方法

當(dāng)我與大型組織討論文件夾方法時,聽到的第一個反對意見是,人們(尤其是安全團(tuán)隊)不喜歡看到單個Git存儲庫中的單個分支同時包含產(chǎn)品化和非產(chǎn)品化環(huán)境。

這是一個可以理解的反對意見,可以說是文件夾方法相對于“environment-per-branch”范式的唯一弱點。畢竟,在Git存儲庫中保護(hù)各個分支比在單個分支中保護(hù)文件夾要容易得多。

這個問題可以很容易的通過自動化、驗證檢查甚至手工批準(zhǔn)(如果這對你的組織至關(guān)重要的話)來解決。我想再次強(qiáng)調(diào),在文件操作中使用“cp”來升級發(fā)布版本,只是為了演示的目的,并不意味著當(dāng)升級發(fā)生時,需要在交互式終端中手動運行cp。

理想情況下,應(yīng)該有一個自動化系統(tǒng)來復(fù)制文件并commit/push它們,可以是持續(xù)集成(CI)系統(tǒng)或處理軟件生命周期的其他平臺。如果仍然有人自己做出改變,不應(yīng)該直接commit “main”目錄,而是應(yīng)該發(fā)起一個Pull Request,然后通過適當(dāng)?shù)牧鞒?,在合并之前檢查Pull Request。

然而,我意識到有些組織對安全問題特別敏感,當(dāng)涉及到Git保護(hù)時,他們更喜歡完全隔離的方法。對于這些組織,可以使用2個Git存儲庫,一個保存base配置、所有生產(chǎn)variants和所有生產(chǎn)環(huán)境(以及所有與生產(chǎn)相關(guān)的東西),而第二個Git存儲庫保存所有非生產(chǎn)的東西。

這種方法讓升級變得有點困難,因為現(xiàn)在需要在做任何升級之前簽出2個git倉庫。另一方面,它允許安全團(tuán)隊向“生產(chǎn)”Git存儲庫放置額外的安全約束,并且無論部署到多少環(huán)境中,仍然擁有靜態(tài)數(shù)量的Git存儲庫(只有2個)。

個人認(rèn)為這種方法有些過分,至少在我看來,它顯示出開發(fā)和運維缺乏信任。關(guān)于人們是否應(yīng)該直接訪問生產(chǎn)環(huán)境的討論是一個復(fù)雜的問題,可能需要單獨討論。

擁抱文件夾,忘記分支

希望通過這篇文章,可以解決在多環(huán)境中部署的問題,現(xiàn)在你已經(jīng)很好的理解了文件夾方法的好處以及應(yīng)該使用它的原因。

GitOps部署快樂!

References:
[1] Stop Using Branches for Deploying to Different GitOps Environments: https://medium.com/containers-101/stop-using-branches-for-deploying-to-different-gitops-environments-7111d0632402
[2] How to Model Your Gitops Environments and Promote Releases between Them: https://codefresh.io/about-gitops/how-to-model-your-gitops-environments-and-promote-releases-between-them/
[3] Multiple environments(dev, stage, ..,. prod ) example: https://github.com/argoproj/argocd-example-apps/issues/57
[4] A successful git branching model: https://nvie.com/posts/a-successful-git-branching-model/
[5] Trunk based development: https://trunkbaseddevelopment.com/
[6] Feature flags: https://trunkbaseddevelopment.com/feature-flags/
[7] git diff: https://git-scm.com/docs/git-diff
[8] git cherry-pick: https://git-scm.com/docs/git-cherry-pick
[9] Ingress: https://kubernetes.io/docs/concepts/services-networking/ingress/
[10] Manage resources containers: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
[11] Helm deployment evnironments: https://codefresh.io/helm-tutorial/helm-deployment-environments/
[12] Kustomize: https://kustomize.io/
[13] Helm: https://helm.sh/
[14] Configmap generator: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/configmapgenerator/
[15] Patches strategic merge: https://kubectl.docs.kubernetes.io/references/kustomize/kustomization/patchesstrategicmerge/
[16] GitOps promotion source code: https://github.com/kostis-codefresh/gitops-promotion-source-code
[17] Configure liveness readiness startup probes: https://kubernetes.io/docs/tasks/configure-pod-container/configure-liveness-readiness-startup-probes/
[18] Manage resources containers: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/
[19] Sample prod: https://raw.githubusercontent.com/kostis-codefresh/gitops-environment-promotion/main/variants/prod/prod.yml
[20] Sample staging-asia: https://github.com/kostis-codefresh/gitops-environment-promotion/tree/main/envs/staging-asia
[21] Sample version.yml: https://github.com/kostis-codefresh/gitops-environment-promotion/blob/main/envs/staging-asia/version.yml
[22] Sample replicas.yml: https://github.com/kostis-codefresh/gitops-environment-promotion/blob/main/envs/staging-asia/replicas.yml
[23] Sample settings.yml: https://github.com/kostis-codefresh/gitops-environment-promotion/blob/main/envs/staging-asia/settings.yml
[24] GDPR: https://gdpr-info.eu/
[25] Database refactoring: https://databaserefactoring.com/

你好,我是俞凡,在Motorola做過研發(fā),現(xiàn)在在Mavenir做技術(shù)工作,對通信、網(wǎng)絡(luò)、后端架構(gòu)、云原生、DevOps、CICD、區(qū)塊鏈、AI等技術(shù)始終保持著濃厚的興趣,平時喜歡閱讀、思考,相信持續(xù)學(xué)習(xí)、終身成長,歡迎一起交流學(xué)習(xí)。
微信公眾號:DeepNoMind

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容