如果你跟我一樣有能力在開發完成後手動進行打包上傳等部署工作,但是卻對於CI/CD 跑 Pipeline 要吃的 YAML 語法感到頭痛,這篇文章可以解決你大多數疑惑
以 GitLab 為例細看
這邊提供一份簡單的 YAML 檔案做為參考
stages:
- deploy
deploy:
stage: deploy
image: ubuntu:22.04
only:
- main
before_script:
- apt-get update -qq && apt-get install -y -qq rsync curl openssh-client
# 安裝 Hugo Extended
- HUGO_VERSION="0.147.0"
- curl -sL "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz" | tar -xz -C /usr/local/bin hugo
# 設定 SSH
- mkdir -p ~/.ssh
- |
if [ -f "$VULTR_SSH_KEY" ]; then
cp "$VULTR_SSH_KEY" ~/.ssh/id_rsa
else
echo "$VULTR_SSH_KEY" | tr -d '\r' > ~/.ssh/id_rsa
fi
- echo "" >> ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- ssh-keygen -l -f ~/.ssh/id_rsa # 診斷:顯示金鑰指紋,確認金鑰格式是否正確
- ssh-keyscan -H $VULTR_HOST >> ~/.ssh/known_hosts
script:
# 建置
- hugo --minify
# 部署到 Vultr(只傳差異檔案)
- rsync -avz --delete ./public/ $VULTR_USER@$VULTR_HOST:$VULTR_ROOT/
# Ping IndexNow(Bing、Yandex 等)
- curl -s -o /dev/null -w "%{http_code}" "https://api.indexnow.org/indexnow?url=https://your-website.com/sitemap.xml&key=$INDEXNOW_KEY"
- curl -s -o /dev/null -w "%{http_code}" "https://www.bing.com/indexnow?url=https://your-website.com/sitemap.xml&key=$INDEXNOW_KEY"以 GitLab 部署 Hugo 為例,當我 push 後是如何透過 pipeline 部署的
- gitlab runner 依據 .gitlab-ci.yml 定義的 image 下載映像檔,並啟動容器(Container)建立乾淨且獨立的建置環境。
- 接著會在裡面執行 before_script 進行部署前的環境初始化。
2.1. 安裝必要的系統套件(如 apt 依賴或 hugo)。這一步通常很花時間,最佳做法是直接採用預裝 Hugo 的官方或自製映像檔(Custom Docker Image)以大幅縮短建置時間。
2.2. 將私鑰(SSH Private Key)寫入容器的
~/.ssh/id_rsa中,以供後續遠端驗證。(公鑰需自行先放到正式機中) 2.3. 並利用ssh-keyscan將目標主機的公鑰加入known_hosts,防止 rsync 執行時因互動式確認提示(Prompt)而導致 Pipeline 而卡住 - 在
script階段執行hugo命令,產生(Generate)靜態網頁檔案。 - 將打包後
public/目錄rsync到正式主機 - … 部署後的連動 ex. 更新 indexNow、清除快取等…
💡每個 Stage 都是乾淨的
每一個 Stage 都會重新起一個 Container 得到乾淨的環境,因此前一次做過的東西想留下必須透過 Artifacts
例如:在 Stage 1 將產出放在 public/ 目錄並設為 Artifacts,那麼在 Stage 2 啟動時會將 public/ 自動下載回來(請注意是花時間下載)
💡如果前面 Stage 產生的 Artifacts 太多可以利用 dependencies 限制下載範圍
stage: deploy
dependencies:
- build_job # 這樣它就只會下載 build_job 的東西,不會下載 Test 階段的東西💡如果後面 Stage 不需要,可以不下載嗎?
可以!只要給 dependencies 一個空陣列即可
job_不需要檔案:
stage: notify
dependencies: [] # 明確告訴 GitLab:這一個 Job 什麼都不要下載
script:
- echo "我不需檔案也能執行"💡Artifacts 預設會在 GitLab Server 上留存一段時間(大約30天)
這指的是 GitLab 的網頁介面與儲存空間。 用途: 當你打開 GitLab 網頁,進到該次 Pipeline 的畫面時,你會看到一個「Download Artifacts」的按鈕。這讓你可以手動下載那次打包出來的 public/ 壓縮檔,用來檢查內容。
⚠️注意: Artifacts 不會影響下一次 Pipline 的環境
GitLab 的 YAML 結構
一般人大腦的思考方式,在程式設計中叫做「由上而下」(Top-Down) 的樹狀結構,例如
graph TD
subgraph "大腦直覺:樹狀結構"
A[Stages] --> B[Build]
A --> C[Deploy]
B --> B1[Job_打包網頁]
B --> B2[Job_同步主機]
end# ❌ 這不是 GitLab 的語法,但這是你大腦直覺的樹狀邏輯:
stages:
build:
- job_打包網頁:
script: hugo
- job_壓縮圖片:
script: optipng
deploy:
- job_同步主機:
script: rsync但 GitLab 是採用了「扁平化標籤(Tagging)邏輯」,他把所有 Job 攤平在最外層,在讓 Job 自己去認領 Stage。
graph TD
subgraph "GitLab 邏輯:扁平化標籤"
D[job_打包網頁] -- Tag: Build --> E[Build Stage]
F[job_壓縮圖片] -- Tag: Build --> E
G[job_同步主機] -- Tag: Deploy --> H[Deploy Stage]
endstages:
- build
- deploy
job_打包網頁:
stage: build
script:
...
job_壓縮圖片:
stage: build
script:
...
job_同步主機:
stage: deploy
script:
...為何 GitLab 要這樣設計?
GitLab 這樣設計,是為了解決「樹狀結構」在複雜自動化時的致命缺點:
- 為了支援超高自由度的「條件執行」 如果採用樹狀結構,當你想讓某個 Job 在特定條件下才執行(例如:只有 master 分支才跑部署),你的語法會變得極度臃腫,容易搞錯縮排。現在的設計可以讓每個 Job 變成獨立的「積木」,自己決定自己的命運:
- 方便「繼承」與「重複使用」(Extends) 當專案很大時,你可能會寫一個「範本 Job」。如果是平鋪的結構,其他 Job 可以直接繼承它,並改掛到不同的 Stage 去:
.bash_template: # 範本
before_script:
- echo "初始化環境"
job_測試環境:
extends: .bash_template
stage: test # 掛到測試 stage
job_正式環境:
extends: .bash_template
stage: deploy # 掛到部署 stage這個觀念一通,以下這三件事你就全懂了
- 為什麼可以「跨關卡打臉」?(DAG 異步執行)
job_部署網頁:
stage: deploy
needs: ["job_打包網頁"] # 標籤連線:只要打包好,我就要直接去 deploy,不管中間的 Test 關卡跑完了沒!如果用樹狀結構,這根本做不到,因為 Deploy 必須死等整個 Test 樹狀分枝結束。但用標籤,個體之間可以直接通訊。
- 為什麼可以用環境變數隨便過濾?(Rules 觸發) 因為每個 Job 都是獨立個體,它們可以像在百貨公司抽獎一樣,看自己身上的標籤符不符合條件:
job_測試:
stage: test
rules:
- if: '$CI_COMMIT_BRANCH == "main"' # 只有 main 分支時,這個個體才活過來核心組件快速對照表
| 關鍵字 | 核心功能 | 一句話理解 |
|---|---|---|
artifacts | 檔案傳遞 | 把這關產出的東西「打包」傳給下一關 |
dependencies | 下載控制 | 決定這關要從哪關「拿」東西過來 |
needs | DAG 異步 | 不用等整關跑完,前置 Job 好了就衝 |
extends | 繼承範本 | 減少重複程式碼的「積木範本」 |
rules | 條件觸發 | 像抽獎一樣,符合條件的 Job 才會執行 |
延伸:Kubernetes / Terraform 也是這套邏輯 未來如果去碰雲端、微服務(Kubernetes),你會發現它也是這樣:你不會把一個容器「寫在」某台伺服器下面。你只會起一個容器,給它貼上標籤 app: hugo-web,然後起一個流量分配器(Service),也貼上標籤去撈 app: hugo-web。它們就自己對上了。
結語
掌握了「扁平化標籤」與「獨立積木」的邏輯後,GitLab CI/CD 的 YAML 就不再是令人頭痛的語法拼圖。這套設計哲學不僅賦予了 Pipeline 極高的靈活性,也與現代雲原生架構(Cloud Native)的設計理念不謀而合。希望這篇文章能幫助你克服對自動化部署的恐懼,開始建構屬於你自己的高效開發工作流。
