GitLab CI/CD 的「環境」管理架構

| | 0 Comments| 13:17|
Categories:

GitLab 在滿久以前,就有一個「環境」(environment、文件)的架構,可以在 CI/CD 的流程中、對部署的目標做關聯,讓開發者可以在 GitLab 的專案頁面上針對目標環境做一些管理;根據官方的說法,它可以提供的好處是:

  • 讓部署流程保持一致且可重複執行
  • 追蹤哪些程式碼部署到了哪裡
  • 當發生問題時回復到先前版本
  • 保護敏感環境,避免未經授權的變更
  • 依據各環境控管部署變數,以維持安全邊界
  • 監控環境健康狀況,並在出現異常時收到警示

Heresy 也算是滿久以前就知道有這個東西,不過由於以前使用 GitLab CI/CD 主要都是在編譯 C++ 的本機程式,並沒有碰到網站的部署,所以就一直沒理他了。

而最近有碰到想要透過 CI/CD 來進行網站部署的需求,所以就來稍微研究一下了~


概念

GitLab 提供的 environment 功能,在概念上是把一個部署的目標定義為一個環境來做關聯;以簡單的網站來說,就是網站伺服器了。

而透過 GitLab CI/CD 的功能,開發者可以設定多個環境來進行部署、管理;一般來說,應該會有測試、正式環境(staging、production)這樣的分類,有的時候可能還會有更多。

使用 GitLab 提供的環境架構,在個人來看有幾個好處:

  • 可以針對不同環境設定變數、方便腳本撰寫與開發
    • 在 GitLab 網站設定 CI/CD 變數的時候,可以針對每個環境的主機名稱、密碼用同一個變數名稱來設定不同的值、這樣腳本就可以不用特別去分開寫了
  • GitLab 的網頁上會顯示環境的狀態、部署歷程
    • 可以透過網頁簡單地還原到特定版本
    • 可以透過網頁來停止一個環境(包含清理資源)
    • 可以設定過一段時間自動停止環境、避免測試環境忘記關閉
  • 部署、停止、資源清理都可以自動化、透過網頁控制,不需要連線到人工伺服器
    • 可以在一定的程度上避免人工操作的錯誤
  • 付費版應該還有事件管理和自動退版(Auto Rollback)的機制

由於它可以提供相對完整的部署歷程,所以如果有資安相關稽核的需要的話,算是滿實用。

另外,GitLab 也支援臨時性的動態環境、Review App 的架構,方便進行測試;但是相對地,也是要自己的環境能不能這樣玩了。


GitLab 的網頁介面

首先,GitLab 的 environment 入口是在專案的「Operate」、「Environments」。在沒有使用過相關功能的狀況下,點進去應該會是基本的介紹。

image host

這邊點選「Create an environment」就可以建立一個新的環境了。而實際上,這邊要填的資料也不多,基本上就是名稱、描述、網址而已;除非有 Kubernetes 才要指定 agent。

image host

實際上,網址的部分也只是在網頁介面上便使用者開啟網站而已,這邊不填 URL 也可以透過 CI/CD 腳本來設定,都沒有的話也只是會沒有開啟網站的按鈕而已。

而實際上 GitLab 並不會主動連線到對應環境的系統;如果要針對伺服器做實際上的操作,都是要定義 CI/CD 的腳本、透過 GitLab Runner 來操作的。

如果已經有環境、也已經有執行過的話,介面則會類似下面的樣子:

image host

最上方會有根據環境的狀態分成「Active」和「Stopped」兩個分類。

每一個環境會顯示環境的名稱、最後一次的部署來源與狀態;最後的動作則會因為 CI/CD 腳本設計的不同而不同。

如果有設定環境的網址(前面建立環境的時候、或是在 CI/CD 內設定)的話,會有一個「Open」的按鈕、可以快速地開啟網頁來開啟部署用的系統。

而如果還有設定其他環境相關的工作的話,也都可以在這個介面來執行。

image host

如果有設定好停止的功能的話,那按下停止的圖示(紅色的按鈕)就可以把環境停下來、並進行環境關閉後的清理;在環境被關閉後、這個環境就會從「Active」跳到「Stopped」。

如果在列表中點選環境的名稱的話,則會開啟環境的部署紀錄:

image host

這邊就會顯示過去部署的狀況、同時也可以在這邊進行退回到特定版本的動作(當然,也是要看腳本怎麼寫的)。

最後,有環境的專案在專案首頁也可以看到目前啟用的環境的數量,點選這邊也可以快速地進入環境的列表。

image host

網頁介面的部分主要就是這樣了?


CI/CD 腳本:基本

這部分一樣是要針對專案設計 .gitlab-ci.yml 這個檔案(官方文件)。

而實際上,job 的寫法也沒有太多的變化,主要就是要加上 environment 這個標籤的設定(官方文件);最簡單的狀況,就是下面的形式:

deploy:
     environment: production
     stage: deploy
     script:
     - echo "deploy test"

也就是只要指定現在這個工作是對應到「production」這個名字的環境就可以了。

而這樣的 CI/CD 腳本執行後,GitLab 網站就會出現「production」這個環境的資訊和部署狀態了。(就算前面沒有手動在網站建立環境也會出現)

image host

由於這邊沒有設定網址,所以 Actions 的部分就沒有開啟網站的按鈕了。

而紅色的停止按鈕按下去,就是把環境的狀態設定成關閉了。


當然,實際使用通常不會這麼單純,所以 environment 還有很多東西可以調整;最常見的一種寫法,是變成:

deploy:
    environment:
        name: production
        url: http://www.example.com
    stage: deploy
    script:
    - echo "deploy test"

這邊基本上就是加入 url、代表這個環境的網址。

如此一來,在部署完成後、GitLab 的環境介面上就會有一個「Open」的按鈕,方便開啟網站做測試、確認了。

image host

當然啦,這邊因為沒有實際的網站,所以是打不開網頁的。


最基本的腳本大概就是這樣?當然啦,這邊 deploy 這個工作的腳本只是測試用的、沒有實際的功能;不過如果已經有既有的部署腳本、然後只是想套用 GitLab 的環境的話,這樣就算完成了。

到這邊會有的功能,基本上就是自動部署、GitLab 網頁上的部署紀錄、以及還原到特定版本的介面了。

而這邊雖然也會有一個停止的按鈕,可以把 GitLab 網頁上的環境設定成停止狀態,但是由於 CI/CD 腳本並沒有說明停止的時候該做什麼,所以其實這邊就只是單純修改 GitLab 網頁上的狀態、服務得自己手動關閉了。


CI/CD 腳本:停止環境

而如果想要透過 GitLab 的環境的機制來關閉服務的話,就是要在 CI/CD 的腳本裡面,定義一個停止用的工作了。

這邊的腳本大致上可以寫成:

stages:
- deploy
- stop
 
deploy:
    environment:
        name: production
        url: http://www.example.com
        on_stop: stop-env
    stage: deploy
    script:
    - echo "deploy test" stop-env:
    environment:
        name: production
        action: stop
    stage: stop
    script:
    - echo "stop test"
    when: manual

這邊主要就是:

  • 加入一個「stop-env」的工作,來停止環境
    • 這個工作要設定成手動、或是加上其他條件,總之不要讓他自動執行
  • 在本來的「deploy」這個工作的環境定義裡面,加入「on_stop」,設定這個環境要停止的時候要執行哪個工作。

stop-env 這邊的環境設定中有加入「action: stop」,這是代表他是用來停止 production 這個環境的工作。

這邊的 action 總共有五種值:startstopprepareverifyaccess文件),如果像 deploy 這樣沒有指定的話,就會是預設的 start、代表要啟動一個環境;至於其他三個則算是輔助用的、不會影響到 GitLab 上環境的狀態,就等之後再介紹了。

這樣的腳本在 pipeline 裡面會有兩個階段,stop 這個階段不會自動執行、需要手動觸發。

image host

而在 GitLab 的環境介面中,在右側的 Actions 這邊則會多出一個執行的箭頭、點選下拉選單後,會看到剛剛新加入的「stop-env」這個工作。

image host

這樣修改完了之後,只要按下紅色的停止按鈕,GitLab 就會跳出一個對話框、確認是否真的要停止這個環境。確認之後,他就會去執行「stop-env」這個工作、把環境關閉了。

當然,這邊 stop-env 實際上只有測試用的,功能也只有輸出一個字串而已。不過如果在這邊加入去把服務關閉的腳本的話,就可以透過 GitLab 網頁來關閉服務了。


另外,如果這個環境只是測試用的、不是固定要開著的,也可以在部署的工作的環境設定中,加上「auto_stop_in」的設定,讓 GitLab 在時間到了之後、自動去執行對應的停止工作(例如這邊的 stop-env)來關閉這個環境。例如:

deploy:
    environment:
        name: production
        url: http://www.example.com
        on_stop: stop-env
        auto_stop_in: 1 day
    stage: deploy
    script:
    - echo "deploy test"

這樣部署完成後,在 GitLab 的環境頁面的 Actions 下方,就會顯示這個環境多久之後會自動關閉了。

image host

這樣的設定在不是固定開著的測試環境上相當好用。因為通常測試也都只是在部署後的一小段時間,如果忘記關掉的時候除了會持續占用資源外、更可能會造成額外的資安風險;這樣設定自動關閉的話,就可以在時間到了之後就自動關閉、避免衍生的問題了。


不過這邊可能要注意的,是在執行新的 pipeline 的部署工作的時候,並不會保證目前已經完成部署的 pipeline 的停止工作會被執行;假設在環境停止前就去部署下一個版本的話,那前一個部署對應的停止工作實際上是不會被執行的。

所以如果有很多清理的工作是靠停止的工作在做的話,那可能就是變成要養成習慣,要在部署新版本前、手動去執行停止工作了。(這點設計個人是覺得有點微妙)

另外,如果是在 Merge Request 裡面的部署有定義停止動作的話,似乎是會在 MR 被接受、被關閉的時候、去執行停止的工作,這個可能也是要注意的。


針對多個環境設定變數

使用 GitLab 的環境架構,另一個好處是在有多個環境的時候,可以針對不同的環境個別設定同名的 CI/CD 變數、在不改變 CI/CD 腳本的變數名稱的情況下、簡化腳本的差異性。

GitLab 管理 CI/CD 的變數的介面是在 settings、CI/CD、Variables 裡面的「CI/CD Variables」。

假設現在有 production 和 staging 兩個環境的情況下,如果不針對 environment 來設定 CI/CD 變數的話,兩台主機的名字可能會需要透過不同的變數名稱(這邊是 Key)來設定:

image host

在不使用環境的架構的狀況下,CI/CD 腳本可能會寫成下面的樣子:

stages:
- deploy
- stop
 
deploy-production:
    stage: deploy
    script:
    - echo "deploy to $PRODUCTION_HOST" deploy-staging:
    stage: deploy
    script:
    - echo "deploy to $STAGING_HOST" stop-production:
    stage: stop
    script:
    - echo "stop $PRODUCTION_HOST"
    when: manual stop-staging:
    stage: stop
    script:
    - echo "stop $STAGING_HOST"
    when: manual

這邊是因為只是測試,所以腳本的內容算簡單,這樣個別寫感覺沒什麼關係。

但是實際上,真正要部署的腳本,除了主機名稱外,還會需要有很多其他的資訊(例如 SSH Key、路徑等等)、腳本也會很複雜;這個時候如果還是針對不同的環境個別寫腳本的話,就很容易出現不同的環境的腳本修改的不一致的狀況、也很不容易維護。


這邊比較好的做法,其實是針對 production 和 staging 這兩個環境,設定同樣名稱的變數、來做腳本的簡化。

在新增 CI/CD 變數的時候,基本上是不能有同樣名稱(Key)的變數的。不過,如果是在新增的時候,有選擇環境的名稱的話,就會被視為是只有該環境能看到的環境變數。

image host

所以在有指定環境名稱的狀況下,是可以針對不同的環境、使用同一個名字的變數的。像下圖就是一個例子:

image host

這邊有兩個 key 是「HOST_NAME」的變數,一個是給 production 用的、另一個是給 staging 用的,裡面可以放不同的值。

這樣一來,由於變數名稱都一樣,所以在使用 GitLab 環境的架構的時候,腳本就可以抽出來變成共用的範本、然後透過 extends 來繼承了(官方文件):

stages:
- deploy
- stop
 
.deploy:
    stage: deploy
    script:
    - echo "deploy to $HOST_NAME" .stop:
    stage: stop
    script:
    - echo "stop $HOST_NAME"
    when: manual deploy-production:
    extends: [.deploy]
    environment:
        name: production
        url: "http://$HOST_NAME"
        on_stop: stop-production deploy-staging:
    extends: [.deploy]
    environment:
        name: staging
        url: "http://$HOST_NAME"
        on_stop: stop-staging
        auto_stop_in: 10 min stop-production:
    environment:
        name: production
        action: stop
    extends: [.stop] stop-staging:
    environment:
        name: staging
        action: stop
    extends: [.stop]

可以看到,這邊把部署的腳本都寫在 .deploy 這個範本裡面,而 deploy-productiondeploy-staging 這兩個工作因為都是繼承這個範本,所以腳本就不需要重新寫了,只需要設定個別的環境設定就可以了~

透過這樣的寫法,不管之後有多少個環境、實際上部署用的腳本都會集中在  .deploy 這個範本、所以要修改的地方也只有一個,不但更好維護、也更不容易出現改一個地方卻忘了另一個地方、導致最後腳本不一樣的問題。

同樣的寫法也可以套用在停止環境的工作上,這邊的範本是 .stop,實際的工作則是 stop-productionstop-staging


使用保護變數來保護部署環境

在這樣多個部署環境的狀況下,接下來通常會想要把主要的 production 環境保護好、只部署經過確認、完整測試的版本。

所以,通常我們會限制 production 環境部署相關的工作只會在主線上出現:

deploy-production:
    extends: [.deploy]
    environment:
        name: production
        url: "http://$HOST_NAME"
        on_stop: stop-production
    rules:
    - if: $CI_COMMIT_BRANCH == "main"

當然還有很多不同的規則的設定方向、也有可能是只有在特定的 release tag 才能跑,這點基本上就是看自己的需求了。

由於主線通常會設定成只有管理者可以推送、合併,所以可以在一定的程度上確保只有穩定的程式會被部署到 production 環境。


不過光是這樣設定的話,如果其他開發者把條件改掉的話,那其實還是可以在其他分支執行了;為了避免這樣的狀況發生,可以透過把 CI/CD 變數設定成保護來做到。

在加入 CI/CD 變數的時候,還有一個「Protect variable」的 flag 可以設定。在啟用這個選項後,這個變數將會變成只有受保護的分支(protected branch)與受保護的標籤(protected tag)才能讀取到。(官方文件

假設這邊是僅允許主線(main)上的 pipeline 可以部署到 production 環境的話,那就可以把 production 環境的所有 CI/CD 變數都設定成 protected:

image host

然後再確定主線是受保護的分支(settings、Repository、Branch rules)(預設應該是),這樣就可以了。

這樣在主線執行的時候,會可以正確讀取到 production 環境的 HOST_NAME、沒有問題;但是在其他分支要讀取 production 環境的 HOST_NAME 的話,就會因為讀不到變數而出問題。

所以透過這種方法,就可以避免正式環境的變數在其他分支被讀取、也可以避免正式環境被其他分支部署、修改。

而同時,在 staging 環境由於 HOST_NAME 沒有設定成 protected,所以在所有分支都可以正確地讀取並執行、不會受到影響。


動態環境與 Review Apps

前面的環境設定基本上是針對固定在那邊的環境在說明。

而實際上,GitLab 的環境也可以作為動態環境(官方文件)、搭配 Review Apps 的概念(官方文件)來使用。

GitLab 官方文件建議的方法,是把環境的名稱和 URL 都動態產生,例如:

review_app:
  stage: deploy
  script:
    - echo "Deploy to review app environment"
    # Add your deployment commands here   environment:
    name: review/$CI_COMMIT_REF_SLUG
    url: https://$CI_COMMIT_REF_SLUG.example.com
  rules:
    - if: $CI_COMMIT_BRANCH && $CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH

這樣設計的目的主要是針對每個分支或是 MR 建立個別的環境來做管理,所以環境名稱以及 url 都是根據其他的 CI 變數來生成的。

不過這邊 Heresy 沒有認真研究,也沒測試過,所以就點到為止了。


這篇大概就這樣了。

實際上這邊能講的東西還有不少,像是他也支援 Kubernetes(官方文件),只是 Heresy 就完全不熟了。

此外,這邊的 CI/CD 腳本實際上也都沒有真正部署的功能,這部分就等下一篇再來簡單整理一下怎麼在 runner 透過 SSH 連到伺服器進行操作了。

Leave a Reply

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *