學習目標
- 用
environment記錄每次部署,在「Operate → Environments」看得到歷史與網址。 - 為每個分支開一個 review 環境,並用
on_stop收掉。 - 用
resource_group避免兩條 pipeline 同時部署同一個環境。 - 正式環境用手動按鈕上線。
觀念
build ─▶ review/<分支> (每個分支一個,1 天後自動停止,Stop 按鈕執行 stop-review)
└▶ staging (只有 main,自動)
└▶ production(只有 main,手動按下)| 設定 | 說明 |
|---|---|
environment:name | 環境名稱,可以用變數;review/xxx 的斜線會在介面上分成資料夾 |
environment:url | 環境的網址,介面上會出現「Open」按鈕 |
environment:on_stop | 停止環境時要執行的 job,那個 job 要寫 action: stop |
environment:auto_stop_in | 多久後自動停止 |
environment:deployment_tier | production、staging、testing、development、other |
resource_group | 同名的 job 一次只跑一個,其他的排隊 |
回復
環境頁面每一筆部署都有「Rollback」按鈕,它會重新執行那一次的部署 job。所以部署 job 必須只依賴自己的產物或映像標籤,重跑舊的 job 就等於部署舊版。Lv.10 用 commit 編號當映像標籤,就是為了這件事。
受保護環境
正式專案建議在「Settings → CI/CD → Protected environments」把 production 設成受保護,只有指定的人能按部署,也可以要求核准。本關為了讓任何人都能練習,沒有設定。
為什麼 production 設成 allow_failure: true
寫在 rules 裡的 when: manual 預設會讓 pipeline 停在「blocked」,直到有人按下。真實專案通常就要這樣。教學專案改成不擋,Lv.1~Lv.9 才能一起顯示通過;按下去一樣會部署。
動手做
- push 到 main 後打開「Operate → Environments」,看到
review/main、staging、production。 - 按
staging的「Open」,網頁顯示staging。 - 在 pipeline 按下
production的播放鍵,環境頁面多一筆部署。 - 在
review/main按「Stop」,會執行stop-review。
常見錯誤
on_stop的 job 和部署 job 的rules不一致:停止按鈕會不見。- 停止 job 沒寫
GIT_STRATEGY: none:分支刪掉後停止會失敗。 - 環境名稱
review/main帶斜線,拿去sed "s/…/$CI_ENVIRONMENT_NAME/"會出現bad option in substitution expression:分隔符號改用|(本關第一次實跑就踩到)。 - 部署 job 用
latest標籤:Rollback 也只會拿到最新版,等於不能回復。
下一關
Lv.10 把真正的網站建成映像、簽章,經 GitOps 部署到 k8s。
範例檔案
資料夾 lv09-environments/ 的全部檔案。.gitlab-ci.yml 可以直接複製到自己的專案根目錄執行。
lv09-environments/.gitlab-ci.yml
lv09-environments/.gitlab-ci.yml
# Lv.9 Environments:讓每次部署都有紀錄、看得到網址、可以停止與回復
# 這一關的「部署」是把網站放成 job 產物,在 GitLab 上直接打開;Lv.10 才真的部署到 k8s。
stages:
- build
- review
- staging
- production
default:
image: python:3.13-alpine
build:
stage: build
script:
- python3 lv09-environments/build.py
artifacts:
paths: [lv09-environments/public/]
expire_in: 1 week
.deploy:
needs: [build]
script:
- echo "部署 $CI_COMMIT_SHORT_SHA 到 $CI_ENVIRONMENT_NAME(等級:$CI_ENVIRONMENT_TIER)"
- sed -i "s|__ENV__|$CI_ENVIRONMENT_NAME|" lv09-environments/public/index.html # 環境名稱有斜線(review/main),分隔符號改用 |
artifacts:
paths: [lv09-environments/public/]
expire_in: 1 week
# 每個分支一個 review 環境,網址指到這個 job 的產物
review:
stage: review
extends: .deploy
environment:
name: review/$CI_COMMIT_REF_SLUG
url: $CI_PROJECT_URL/-/jobs/$CI_JOB_ID/artifacts/file/lv09-environments/public/index.html
on_stop: stop-review # 環境頁面的「Stop」按鈕會執行這個 job
auto_stop_in: 1 day # 一天後自動停止
resource_group: review-$CI_COMMIT_REF_SLUG
stop-review:
stage: review
needs: [review]
image: alpine:3.22
when: manual # job 層的 manual 預設不擋 pipeline
variables:
GIT_STRATEGY: none # 分支可能已刪除,停止時不要 checkout
script:
- echo "移除 review/$CI_COMMIT_REF_SLUG"
environment:
name: review/$CI_COMMIT_REF_SLUG
action: stop
staging:
stage: staging
extends: .deploy
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
environment:
name: staging
deployment_tier: staging
url: $CI_PROJECT_URL/-/jobs/$CI_JOB_ID/artifacts/file/lv09-environments/public/index.html
resource_group: staging # 同一時間只有一個部署,避免兩條 pipeline 互相覆蓋
production:
stage: production
extends: .deploy
needs: [build, staging]
rules:
- if: $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH
when: manual
# 正式環境通常要擋住 pipeline 等人按(allow_failure: false)。
# 教學 pipeline 設成不擋,這樣 Lv.1~Lv.9 能一起顯示通過;按下去一樣會部署。
allow_failure: true
environment:
name: production
deployment_tier: production
url: $CI_PROJECT_URL/-/jobs/$CI_JOB_ID/artifacts/file/lv09-environments/public/index.html
resource_group: productionlv09-environments/build.py
lv09-environments/build.py
"""Lv.9 建置:產生一頁顯示「部署到哪個環境」的網站,__ENV__ 由部署 job 換掉。"""
import os
from pathlib import Path
out = Path(__file__).parent / 'public'
out.mkdir(exist_ok=True)
sha = os.environ.get('CI_COMMIT_SHORT_SHA', 'local')
(out / 'index.html').write_text(f'''<!doctype html>
<html lang="zh-Hant"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Lv.9 環境示範</title>
<style>body{{font-family:system-ui,sans-serif;margin:2rem;background:#F2EEE4;color:#1D2137}}
@media (prefers-color-scheme:dark){{body{{background:#070918;color:#CBD6E5}}}}</style></head>
<body><h1>Lv.9 Environments</h1><p>這一版({sha})部署在:<strong>__ENV__</strong></p></body></html>
''', encoding='utf-8')
print('已產生', out / 'index.html')