本文為系列文章《跟著 GitLab Auto DevOps 學習 CI/CD Pipeline》的第四篇,藉由逐一檢視並解析 GitLab Auto DevOps 背後的 Templates,讓我們一起跟著 GitLab Auto DevOps 學習 GitLab CI/CD Pipeline。
在上一篇文章,我們學習到 workflow:
可以幫助我們控制在哪些條件之下才需要產生 CI/CD Pipeline。這次我們則是要進入第一個 Template - Jobs/Build.gitlab-ci.yml
,看看 Auto DevOps 是如何實現同一個 CI Build 卻能適用於不同的程式語言。
此系列文清單:
- 跟著 GitLab Auto DevOps 學習 CI/CD Pipeline(一)
- 跟著 GitLab Auto DevOps 學習 CI/CD Pipeline(二)
- 跟著 GitLab Auto DevOps 學習 CI/CD Pipeline(三)
- 跟著 GitLab Auto DevOps 學習 CI/CD Pipeline(四)
Jobs/Build.gitlab-ci.yml
首先讓我們直搗黃龍,直接查看 Jobs/Build.gitlab-ci.yml
的內容。
# 下面的版本來自 2022/02/04 的 https://gitlab.com/gitlab-org/gitlab/-/blob/1b56d49344fc468bf3ba537d367fe21ac79b6afa/lib/gitlab/ci/templates/Jobs/Build.gitlab-ci.yml
variables:
AUTO_BUILD_IMAGE_VERSION: 'v1.5.0'
build:
stage: build
image: 'registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:${AUTO_BUILD_IMAGE_VERSION}'
variables:
DOCKER_TLS_CERTDIR: ''
services:
- name: 'docker:20.10.6-dind'
command: ['--tls=false', '--host=tcp://0.0.0.0:2375']
script:
- |
if [[ -z "$CI_COMMIT_TAG" ]]; then
export CI_APPLICATION_REPOSITORY=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG}
export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_SHA}
else
export CI_APPLICATION_REPOSITORY=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE}
export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
fi
- /build/build.sh
artifacts:
reports:
dotenv: gl-auto-build-variables.env
rules:
- if: '$BUILD_DISABLED'
when: never
- if: '$AUTO_DEVOPS_PLATFORM_TARGET == "EC2"'
when: never
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
build_artifact:
stage: build
script:
- printf "To build your project, please create a build_artifact job into your .gitlab-ci.yml file.\nMore information at https://docs.gitlab.com/ee/ci/cloud_deployment\n"
- exit 1
rules:
- if: '$BUILD_DISABLED'
when: never
- if: '$AUTO_DEVOPS_PLATFORM_TARGET == "EC2"'
接著開始一段一段的解釋。
首先官方定義了一個 Variable - AUTO_BUILD_IMAGE_VERSION
宣告要使用哪一個版本的 Container image。官方為了 Build 有特別準備一個特殊的 image,並持續更新維護。關於 Image 的詳細內容,我們留待後面再說明。
variables:
AUTO_BUILD_IMAGE_VERSION: 'v1.5.0'
接著在 CI Job - build:
中,立即指定要使用這個特別的 Image。
build:
# CI Job - build 當然是放在 stage: build
stage: build
# 指定要用特別的 container image 來執行這項 CI Job
image: 'registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:${AUTO_BUILD_IMAGE_VERSION}'
【補充】如上,可以看見這個 Docker image 存放在官方自己的 Registry,這是一個 Public registry,因此你也可以自行透過
docker pull
指令取得這個 Image。
(實際的 CI Job 中,確實會看到有相關的 docker pull 動作。)
接著有兩個重要的設定。
variables:
DOCKER_TLS_CERTDIR: ''
services:
- name: 'docker:20.10.6-dind'
command: ['--tls=false', '--host=tcp://0.0.0.0:2375']
在 Job 中定義的 variables:DOCKER_TLS_CERTDIR
是為了配合 Docker 官方對於 docker:dind
這個 docker image 的更新,透過設置 DOCKER_TLS_CERTDIR: ''
藉此來 Disable TLS,讓 Runner 在 docker in docker 的狀態下,能順利操控 Docker daemon。
【補充】
variables:DOCKER_TLS_CERTDIR
會去覆蓋同名的 Environment variable,所以才能達成 Disable TLS。關於DOCKER_TLS_CERTDIR
的更多資訊可參考 GitLab 官方文章「Update: Changes to GitLab CI/CD and Docker in Docker with Docker 19.03」。
services:
第二個就是 services:
,這是一個特別 GitLab CI Keyword,用途是為 CI Job 提供執行該 Job 所需的額外「資源」。以本文的 Job build:
為例,由於這項 CI Job 會需要執行一些 docker command 像是 docker build
或 docker push
,因此我們要讓 GitLab Runner 在執行這項 CI Job 時,先預備一個 Service 即是 docker:dind
,如此 Runner 在 CI Job 的過程中,才能正常執行這些 docker command。
【補充】
services:
只能運用在以 Container 執行 CI Job 的情境,因為services:
它所做的事情其實就是幫使用者運行另一個 Container,並讓它和執行 CI Job 的 Container 之間建立連結(link)。services:
常見的運用情境包含:
- 透過
docker:dind
令 CI Job 能執行 docker command;- 以
services:
運行臨時的 DB,以便 CI 能自動化測試應用程式與資料庫有關的功能;- 或其他類似的情境,以
services:
運行任何相依的「資源」,以便 CI Job 能順利執行相應的動作。因此要能善用
services:
的前提是,你是否已熟悉 Container 為主的 Workflow 與 Pipeline,了解如何將 Container 彼此連結,如何用 Container 建構出你所需的工作或應用程式運行環境。(更多services:
的內容,請參閱官方文件,或等我有空另外撰文介紹。)
rules: + when: nerver
接著先跳過最重要的 script:
,以及需要較多說明的 artifacts:
,讓我們先看比較容易理解的 rules:
如我們在上一篇文章提過的,rules:
是 GitLab CI 用來做條件判斷的功能,透過它我們可以控制在何種條件之下才需要執行 CI Job,並且 rules:
能與 when:
搭配使用。
因此那整段 rules:
轉換成白話文後,即是下面的意思。
rules:
# 假如變數 $BUILD_DISABLED 為 true,就不執行此 CI Job,
# 藉此讓使用者可以彈性的控制,是否需要讓 Auto DevOps 自動用 Template 的方式幫你 build artifacts。
- if: '$BUILD_DISABLED'
when: never
# 假如變數 $AUTO_DEVOPS_PLATFORM_TARGET 等於 EC2
# 就不要執行此 CI Job。
- if: '$AUTO_DEVOPS_PLATFORM_TARGET == "EC2"'
when: never
# 假如有為此 Project 建立 Tag,或有 Commit 被送到任何 branch,
# 就要執行此 CI Job。
- if: '$CI_COMMIT_TAG || $CI_COMMIT_BRANCH'
第一個 if: '$BUILD_DISABLED'
是為了 Auto DevOps 的彈性而設置的條件,讓使用者可以自主的取消 build。
【補充】
variable:BUILD_DISABLED
到底何時會用到呢?基本上就是如果你打算對 Auto DevOps 做一些客製化的動作時,就可能用到它,例如你不想用 Auto DevOps 內建的 Jobbuild:
,想改換成自己撰寫的 Job 時。在本系列文我們一開始檢閱的檔案Auto-DevOps.gitlab-ci.yml
它也是一個可以被引用的 Template(template: Auto-DevOps.gitlab-ci.yml
),因此你確實可以做到只取代 Jobbuild:
,讓 Pipeline 後續的測試與部署依然按著 Auto DevOps 的規劃繼續執行下去。
第二個 if: '$AUTO_DEVOPS_PLATFORM_TARGET == "EC2"'
則讓我們搭配第二個 CI Job - build_artifact:
一起看。
build_artifact:
stage: build
script:
- printf "To build your project, please create a build_artifact job into your .gitlab-ci.yml file.\nMore information at https://docs.gitlab.com/ee/ci/cloud_deployment\n"
- exit 1
rules:
- if: '$BUILD_DISABLED'
when: never
- if: '$AUTO_DEVOPS_PLATFORM_TARGET == "EC2"'
如上,在第二個 CI Job 的 rules:
也有一組條件是 if: '$AUTO_DEVOPS_PLATFORM_TARGET == "EC2"'
,但這次並沒有 when: never
。也就是當使用者設置了 variable:AUTO_DEVOPS_PLATFORM_TARGET
,在 Stage: build 就只會有 build_artifact:
這項 CI Job。
【補充】
variable:AUTO_DEVOPS_PLATFORM_TARGET
到底是用來做啥的呢?老實說在本文中一點用處的沒有(喂),它只是用來取消 Auto DevOps 預設的 Jobbuild:
。這個 Variable 算是 Auto DevOps Template 的另一條路線(示範),官方示範了可以如何運用 GitLab CI 來自動將你的 Application 直接部署至 AWS 的 EC2。有興趣了解更多細節的朋友,可以先收看官方的示範影片,未來有時間我也會再另外撰文詳細介紹。(謎之音:好啊好啊,全部都說有空有時間啊~)
artifacts:reports:dotenv
接下來,輪到 artifacts:
。
artifacts:
reports:
dotenv: gl-auto-build-variables.env
artifacts:
大家應該都知道,就是 GitLab 的 Job Artifacts 功能,透過設置 artifacts:
可以將指定的檔案儲存至 Job Artifacts 供使用者事後下載,以及讓 Pipeline 能繼續運用同一份 Artifacts 執行後續的 CI Job。
而 artifacts:reports
則是 Job Artifacts 的另一種用法,是專門用來儲存執行 CI Job 所產生的各種 Report,例如有些自動化測試執行完畢後會產生一些 Report,而 artifacts:reports
即是幫助使用者更容易整合這一類 Report 的功能。
【補充】
artifacts:reports
目前也不是所有類型及所有工具產生的 Report 都能接受,在目前 GitLab 版本 14.7,可接受 19 種不同的 Report,但其中有 11 種需要升級至付費版的等級。
至於 artifacts:reports:dotenv
則是一個並非 Report 的 Report,它並不是用來儲存 Report,而是用來儲存 Environment variable。下面用一個其他範例來說明。
# 首先我有一個 CI Job - build
build:
stage: build
script:
## 假的,假裝我真的有 build 了啥
- echo "fake build-job!!!"
## 我用 echo 的方式產生出一個 deploy.env,裡面會定義我後續會用到的 Variable
## 總共會有三個 variable - WEBSITE_URL, ENVIRONMENT, HOST
- echo "WEBSITE_URL=https://chengweichen.com" >> deploy.env
- echo "ENVIRONMENT=production" >> deploy.env
- echo "HOST=my_server" >> deploy.env
## 接著用 artifacts:reports:dotenv 來儲存它
artifacts:
reports:
dotenv: deploy.env
# 在下一個 CI Job - deploy 中,我們就可以直接使用這些 Environment variable
deploy:
stage: deploy
script:
## 如果使用 env 指令來印出所有的 Environment variable,會發現前面定義的 Variable 都有被傳遞過來。
- env
## 既然有傳遞過來,當然可以直接在 command 中使用它們。
- ansible-playbook deploy.yml -e DEPLOY_HOST=$HOST -e DEPLOY_ENVIRONMENT=$ENVIRONMENT -e WEBSITE_URL=$WEBSITE_URL
environment:
name: production
## 傳遞過來的 Environment variable 也可以運用在 environment:
url: $WEBSITE_URL
artifacts:reports:dotenv
的功能就如上範例,官方文件也有其他的示範與說明,由於 Auto DevOps 的 Job build:
會根據狀況產生出後續 CI Job 會使用到的 Environment variable,因此才會在 Job build:
中使用 artifacts:reports:dotenv
。
(在實際的 Job build:
可以看見確實有儲存了 artifacts:reports:dotenv
。)
【補充】基本上我覺得 GitLab 目前將它放在
artifacts:reports:
之下,在語義上有點怪,也許未來會被改掉。
黑魔法 /build/build.sh
最後讓我們進入重頭戲,到底 script:
中 Auto DevOps 用了什麼黑魔法。
script:
- |
if [[ -z "$CI_COMMIT_TAG" ]]; then
export CI_APPLICATION_REPOSITORY=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE/$CI_COMMIT_REF_SLUG}
export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_SHA}
else
export CI_APPLICATION_REPOSITORY=${CI_APPLICATION_REPOSITORY:-$CI_REGISTRY_IMAGE}
export CI_APPLICATION_TAG=${CI_APPLICATION_TAG:-$CI_COMMIT_TAG}
fi
- /build/build.sh
首先,這裡官方再次為我們示範如何在 script:
中直接使用 shell 的 if / else
。如果你只是要做簡單的情境判斷,就如同上面的範例,它只是為了判斷是不是有打 tag,再因此產生不同內容的 Variable,這時直接使用 shell 的 if / else
是比較省事的。但若是要做的判斷及動作較多,建議還是另外撰寫成單一的 shell script,不然 script:
會被你塞爆變得太長反而不易閱讀。
在 script:
的最後執行了黑魔法 /build/build.sh
,這是已經打包在本文一開始提到的 Image - registry.gitlab.com/gitlab-org/cluster-integration/auto-build-image:${AUTO_BUILD_IMAGE_VERSION}
之內的 shell script,而我們必須前往另一個官方 Project 才能找到它的內容。
官方 Image - auto-build-image
一樣有公開 Project,在其中即可找到 /build/build.sh
,但由於內容有一點多,我就不轉貼在文章中,有興趣的朋友可以自行追 Code,下面我就直接以條列的方式簡單說明它會做哪些事情。
- Login GitLab Container Registry。
- Login GitLab Dependency proxy。
- 使用知名的 Image heroku/buildpacks:18,運行 Container 執行真正的 build 動作。(buildpacks 又是另一個黑魔法。)
- 續上,
build.sh
會將必要的參數與設定傳遞給 buildpacks,於是 buildpacks 會自動判斷你的 Application 是何種程式語言,要跑哪一些 build 動作,並將成果打包成 Image。 - 最後該 Image 會被 push 至步驟 1 登入的 GitLab Container Registry 供後續 CI Job 使用。
因此 /build/build.sh
其實就是一個內容有點多的 shell script,官方在裡面幫我們寫好許多判斷式並傳遞 Variabe 給 buildpacks,讓我們可以無腦的使用 Auto DevOps,任由 CI Job 自行完成所有應該在 Job build:
做的事情。
(Login Container Registry,然後開始執行 buildpacks。)
(有偵測出這是 ruby on rails。)
(build 成 image,然後 push 至 Container Registry。)
結語
這次的內容有點多,按慣例回顧我們學到哪些重點:
- GitLab 官方有自行建立一個 Docker image - auto-build-image,它會幫我們代為運用知名的 buildpacks 完成 CI Job
build:
。 - 如果你不想要 Auto DevOps 的 Job
build:
是可以自行替換的。(但本文沒有示範,以後再說嘍~) - GitLab CI Keyword -
services:
。如果你想要搭建以 Container 為主的 CI/CD Pipeline,這會是你絕對要認識的重要功能,它可以幫你以 Container 運行各種 Services,滿足 CI Job 的相依環境需求,例如自動化測試需要一個臨時的 DB,或操控 Docker daemon。 - 在
script:
中,我們可以直接使用 shell 的if / else
做出邏輯判斷。 rules:
可以搭配when: never
,讓 CI Job 在特定條件下不會被執行。- 當 CI Job 會產生出需要傳遞給後續 Job 使用之 Variable 時,可以將它儲存至
.env
檔案,再利用artifacts:reports:dotenv
儲存為 Environment variable,後續的 Job 即可直接使用。
以上就是本篇文章的所有內容,下一篇文章我們會繼續介紹下一個 Template - Jobs/Test.gitlab-ci.yml
。