隨著技術的演進,許多程式語言、框架都開始有了套件相依管理的機制,像 PHP 使用了 Composer,JavaScript、Node.js 由 npm 管理而 Python 則是 pip,這些套件相依管理的機制,讓程式碼們可以更有效的重複利用,甚至讓很多人得以站在巨人的肩膀上,更快速的完成工作。
因應這些套件的相依性,在 GitLab CI 流水線上工作開始時,通常都需要針對語言的相依套件進行安裝,而,這也同時顯現了一個可以討論的議題。以 PHP 的專案開發舉例,當同一個 PHP 專案在流水線上進行各種測試工作如單元測試、整合測試等,這些工作在開始之前都必須進行相依套件的安裝,因為是相同的專案,自然安裝的套件並不會有太大的差異,甚至大多是一樣的套件,那麼,有沒有什麼機會讓這些重複、一樣的套件安裝,可以進行的更快?
在 GitLab CI 裡,有一個參數 cache
快取,即是在讓這些重複安裝的套件,得有一個位置暫時存放,供後續類似的工作可以取用。這邊,先以一個簡單的 PHP Laravel 為例子做設定:
php-test:
tags:
- self-docker
- try
stage: test
image: composer:1
cache:
paths:
- vendor/
script:
- echo -e "section_start:`date +%s`:composer_install\r\e[0KComposer Install"
- composer install --prefer-dist
- echo -e "section_end:`date +%s`:composer_install\r\e[0K"
- echo -e "section_start:`date +%s`:project_env_prepare\r\e[0KProject Env Prepare"
- composer run post-root-package-install
- composer run post-create-project-cmd
- echo -e "section_end:`date +%s`:project_env_prepare\r\e[0K"
- echo -e "section_start:`date +%s`:phpunit_running\r\e[0KPhpUnit Execute"
- php ./vendor/bin/phpunit
- echo -e "section_end:`date +%s`:phpunit_running\r\e[0K"
如上面的這個範例,因為 PHP 專案使用的 Composer 套件管理機制通常會把安裝的相依套件放在 vendor
資料夾上,因此在 cache 參數中,設定需要做快取的路徑為 vendor/
也就是該資料夾下所有的檔案都會暫存起來,供後續使用。另外 paths
子參數,所需要的數值是一個陣列,因此可以設定多個需要快取暫存的路徑。
cache:
paths:
- vendor/
以上面的範例為例,其執行結果,第一次因為快取的內容尚未建立,所以這個工作在執行 composer install
指令的時候花了一分多鐘,其時間主要花在分析相依套件以及下載各個套件。而當第二次同一個工作再次執行後,因為已經有快取內容了 composer install
不用再次分析及下載套件,因此僅花了三秒鐘即完成。
如下圖模擬,第二次取得快取以及再次安裝 composer 的時間。
如下圖,在透過 Pipelines 介面上「Clear Runner Caches」後再次執行相同工作所花費的時間
有些時候快取的內容可能可能僅在同一個分支上或特定的工作上可以使用,當被不需要使用該快取的工作取得時,除了多花時間,也可能造成無法預期的異常狀態。另如果取得該快取的工作,也會產出快取檔案,則已經產出的快取檔案就會被覆寫掉,造成後續真正需要使用快取的工作沒有快取檔案可用。如此,該怎麼解決這問題呢?
在 GitLab CI 的 cache
參數中,提供了一個子參數 key
可以讓使用者透過這個參數,取得有對應 key
的快取,可以想像成為快取取名字,GitLab Runner 工作時只拿特定名字的快取。舉例來說:
cache:
key: "${CI_COMMIT_REF_SLUG}"
paths:
- vendor/
上面的例子,在建立快取時,即會以設定的變數名稱作為快取檔案的 key。讓下次使用的 Runner 可以透過這限定的名字取得快取檔。如下圖,快取的檔名變為 master-2
大部分的套件相關管理工具在安裝或更新完畢之後,都會產出一個明細檔案,內容描述各個相依套件的版本等資訊,其方便下次更新時,工具可以依據該明細檔案直接取用對應的版本;以 PHP composer 來說,其明細檔案即 composer.lock、Node.js npm 為 package.json,因此快取的檔案是否要更新,也可以取決於這些明細檔案是否被更新了,在 GitLab CI 中要怎麼設定才能使用這樣的機制呢?
在 GitLab CI 裡 cache
的子參數 key
提供了 files
子參數供設定判斷是否更新快取與否,以 files
作為 key
後,GitLab CI 在建立快取時,會以設定的 files
產出 SHA 作為 key
值。同樣以最上方的例子來說,可以如下例子進行設定:
cache:
key:
files:
- composer.lock
paths:
- vendor/
如下圖,新的快取 key 名稱改為一個 SHA:
PS. files
的檔案數量上限為 2 個。
衍伸自策略二的案例,當特定檔案更新時才更新快取,在大部分的狀況下算是可以滿足,但有些時候,不同的工作使用的 key:files
檔案一樣,但需要的快取檔案卻不同,就需要再增加一些參數了。如 PHP 專案,在使用 composer
時,在測試的工作上,需要安裝開發工具,但正式釋出的時候,則不需要,但使用的是相同的composer.lock
檔案。
這時候就必須依照不同的工作給予不一樣的快取檔案,那 GitLab CI 可以怎麼設定呢?在 GitLab CI 裡針對 key
提供了另外的參數 prefix
來解決這議題,其可以如底下這個範例進行設定:
cache:
key:
files:
- composer.lock
prefix: ${CI_JOB_NAME}
paths:
- vendor/
如上面這個範例,GitLab 在建立快取時,同樣會依據 composer.lock
建立對應的 SHA Key,但因為有 prefix
設定以 CI 工作名稱作為 prefix
,因此就可以在不同的工作上取得不一樣的快取檔案。如下圖,其快取檔案增加了工作名稱 test-2 前綴 prefix。
小孩才做選擇,大人全都要?在 GitLab CI 提供的快取策略中,有一個有點任性的參數 untracked
其意思是,只要是不在 GIT 控制範圍內的檔案,皆作為快取檔案使用。
cache:
untracked: true
另外,這個策略也可以搭配 paths
使用,如 PHP 專案,設定 vendor
資料夾底下,所有未透過 GIT 進行控制的檔案均作為快取檔案:
cache:
untracked: true
paths:
- vendor/
以預設的快取使用方法通常是,當系統本身取得沒有快取時,則會自行抓取檔案,而後更新快取回快取主機上,這稱為「pull-push」拉下快取後,重新推回去;但有些狀況是我只要拉快取,但不在更新回快取主機上,這情境稱之為「pull」單向的取用。在 GitLab CI 上也是可以這樣設定的,其方法是,增加 policy: pull
如下完整範例:
cache:
key:
files:
- composer.lock
prefix: ${CI_JOB_NAME}
paths:
- vendor/
policy: pull
快取的使用是加快 GitLab Runner 快速完成工作上,很重要的一個元素,如上面第一個範例,有快取與沒快取其結果一個是分鐘級的時間花費,另外一個僅僅幾秒鐘就可以完成,差異相當大,因此當 runner 執行的工作可以分析出可重複使用的內容時,就可以考慮是否要使用快取。
另外,GitLab 也提供將快取檔案置放於公有雲上,目前支援 s3、gcs、azure 等三個平台,其可以設定在 Runner Server 上的 config.toml
,細節的設定可參考官方手冊。
我是墨嗓(陳佑竹),期待這系列的文章能夠讓人有些幫助。