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」。在沒有使用過相關功能的狀況下,點進去應該會是基本的介紹。
這邊點選「Create an environment」就可以建立一個新的環境了。而實際上,這邊要填的資料也不多,基本上就是名稱、描述、網址而已;除非有 Kubernetes 才要指定 agent。
實際上,網址的部分也只是在網頁介面上便使用者開啟網站而已,這邊不填 URL 也可以透過 CI/CD 腳本來設定,都沒有的話也只是會沒有開啟網站的按鈕而已。
而實際上 GitLab 並不會主動連線到對應環境的系統;如果要針對伺服器做實際上的操作,都是要定義 CI/CD 的腳本、透過 GitLab Runner 來操作的。
如果已經有環境、也已經有執行過的話,介面則會類似下面的樣子:
最上方會有根據環境的狀態分成「Active」和「Stopped」兩個分類。
每一個環境會顯示環境的名稱、最後一次的部署來源與狀態;最後的動作則會因為 CI/CD 腳本設計的不同而不同。
如果有設定環境的網址(前面建立環境的時候、或是在 CI/CD 內設定)的話,會有一個「Open」的按鈕、可以快速地開啟網頁來開啟部署用的系統。
而如果還有設定其他環境相關的工作的話,也都可以在這個介面來執行。
如果有設定好停止的功能的話,那按下停止的圖示(紅色的按鈕)就可以把環境停下來、並進行環境關閉後的清理;在環境被關閉後、這個環境就會從「Active」跳到「Stopped」。
如果在列表中點選環境的名稱的話,則會開啟環境的部署紀錄:
這邊就會顯示過去部署的狀況、同時也可以在這邊進行退回到特定版本的動作(當然,也是要看腳本怎麼寫的)。
最後,有環境的專案在專案首頁也可以看到目前啟用的環境的數量,點選這邊也可以快速地進入環境的列表。
網頁介面的部分主要就是這樣了?
CI/CD 腳本:基本
這部分一樣是要針對專案設計 .gitlab-ci.yml 這個檔案(官方文件)。
而實際上,job 的寫法也沒有太多的變化,主要就是要加上 environment 這個標籤的設定(官方文件);最簡單的狀況,就是下面的形式:
deploy:
environment: production
stage: deploy
script:
- echo "deploy test"
也就是只要指定現在這個工作是對應到「production」這個名字的環境就可以了。
而這樣的 CI/CD 腳本執行後,GitLab 網站就會出現「production」這個環境的資訊和部署狀態了。(就算前面沒有手動在網站建立環境也會出現)
由於這邊沒有設定網址,所以 Actions 的部分就沒有開啟網站的按鈕了。
而紅色的停止按鈕按下去,就是把環境的狀態設定成關閉了。
當然,實際使用通常不會這麼單純,所以 environment 還有很多東西可以調整;最常見的一種寫法,是變成:
deploy: environment:
name: production
url: http://www.example.com stage: deploy
script:
- echo "deploy test"
這邊基本上就是加入 url、代表這個環境的網址。
如此一來,在部署完成後、GitLab 的環境介面上就會有一個「Open」的按鈕,方便開啟網站做測試、確認了。
當然啦,這邊因為沒有實際的網站,所以是打不開網頁的。
最基本的腳本大概就是這樣?當然啦,這邊 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 總共有五種值:start、stop、prepare、verify、access(文件),如果像 deploy 這樣沒有指定的話,就會是預設的 start、代表要啟動一個環境;至於其他三個則算是輔助用的、不會影響到 GitLab 上環境的狀態,就等之後再介紹了。
這樣的腳本在 pipeline 裡面會有兩個階段,stop 這個階段不會自動執行、需要手動觸發。
而在 GitLab 的環境介面中,在右側的 Actions 這邊則會多出一個執行的箭頭、點選下拉選單後,會看到剛剛新加入的「stop-env」這個工作。
這樣修改完了之後,只要按下紅色的停止按鈕,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 下方,就會顯示這個環境多久之後會自動關閉了。
這樣的設定在不是固定開著的測試環境上相當好用。因為通常測試也都只是在部署後的一小段時間,如果忘記關掉的時候除了會持續占用資源外、更可能會造成額外的資安風險;這樣設定自動關閉的話,就可以在時間到了之後就自動關閉、避免衍生的問題了。
不過這邊可能要注意的,是在執行新的 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)來設定:
在不使用環境的架構的狀況下,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)的變數的。不過,如果是在新增的時候,有選擇環境的名稱的話,就會被視為是只有該環境能看到的環境變數。
所以在有指定環境名稱的狀況下,是可以針對不同的環境、使用同一個名字的變數的。像下圖就是一個例子:
這邊有兩個 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-production 和 deploy-staging 這兩個工作因為都是繼承這個範本,所以腳本就不需要重新寫了,只需要設定個別的環境設定就可以了~
透過這樣的寫法,不管之後有多少個環境、實際上部署用的腳本都會集中在 .deploy 這個範本、所以要修改的地方也只有一個,不但更好維護、也更不容易出現改一個地方卻忘了另一個地方、導致最後腳本不一樣的問題。
同樣的寫法也可以套用在停止環境的工作上,這邊的範本是 .stop,實際的工作則是 stop-production 和 stop-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:
然後再確定主線是受保護的分支(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 連到伺服器進行操作了。












