在現代軟體開發生命週期中,快速且可靠的部署能力是確保產品競爭力的關鍵要素。自動化部署不僅能夠大幅縮短從開發到生產的交付時間,更能透過標準化流程降低人為錯誤,提升系統穩定性。GitLab CI/CD 作為一個功能完整的持續整合與持續部署平台,提供了從程式碼提交到生產環境部署的端對端自動化解決方案。

本文將深入探討如何運用 GitLab CI/CD 建構企業級的自動化部署流程。我們將從基礎的 .gitlab-ci.yml 配置檔案撰寫開始,詳細說明工作定義、階段設定、Docker 映像使用、執行規則與部署指令等核心概念。透過實際案例,我們將示範如何將應用程式部署至 Apache 伺服器,並探討 Docker 容器化技術在自動化部署中的關鍵角色。此外,文章也將涵蓋常見的 Pipeline 問題診斷與排除技巧,包括 YAML 語法錯誤、Runner 基礎設施配置,以及執行環境限制的解決方案。最後,我們將探討基礎設施即程式碼的實務應用,介紹 Terraform、Ansible 等工具的整合方式,並分析 IaC 在現代企業中的應用價值與未來發展趨勢。

自動化部署 Pipeline 設計與實作

自動化部署的核心在於建立一個可靠、可重現的 Pipeline,確保程式碼能夠安全且快速地從開發環境推進至生產環境。GitLab CI/CD 透過 .gitlab-ci.yml 配置檔案定義整個部署流程,包括建置、測試、打包與部署等各個階段。

生產環境部署配置

在設計生產環境的部署流程時,我們需要考慮多個關鍵面向,包括環境隔離、部署驗證、回滾機制等。以下是一個完整的生產環境部署配置範例。

# GitLab CI/CD 生產環境部署完整配置
# 展示從建置到部署的完整自動化流程

# 定義 Pipeline 階段
stages:
  - build          # 建置階段:編譯程式碼、建立產物
  - test           # 測試階段:執行各類測試
  - package        # 打包階段:建立部署包
  - deploy         # 部署階段:部署至目標環境
  - verify         # 驗證階段:部署後驗證

# 全域變數定義
variables:
  # Docker 配置
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  # 應用程式配置
  APP_NAME: "hats-for-cats"
  APP_VERSION: "${CI_COMMIT_SHORT_SHA}"
  # 部署目標配置
  PRODUCTION_HOST: "192.168.0.1"
  PRODUCTION_PATH: "/var/www/html/${APP_NAME}"

# 建置階段:編譯應用程式
build_application:
  stage: build
  image: node:18-alpine
  script:
    # 安裝依賴套件
    - npm ci --prefer-offline
    # 執行建置
    - npm run build
    # 驗證建置產物
    - test -d dist || exit 1
  # 保存建置產物
  artifacts:
    paths:
      - dist/
      - node_modules/
    expire_in: 1 day
  # 快取依賴以加速後續建置
  cache:
    key: ${CI_COMMIT_REF_SLUG}
    paths:
      - node_modules/
      - .npm/
  # 只在程式碼變更時執行
  only:
    changes:
      - src/**/*
      - package.json
      - package-lock.json

# 測試階段:執行單元測試
unit_tests:
  stage: test
  image: node:18-alpine
  script:
    # 執行單元測試
    - npm run test:unit
    # 產生覆蓋率報告
    - npm run test:coverage
  artifacts:
    reports:
      junit: test-results/junit.xml
      coverage_report:
        coverage_format: cobertura
        path: coverage/cobertura-coverage.xml
  coverage: '/Lines\s*:\s*(\d+\.\d+)%/'
  # 依賴建置工作
  needs:
    - build_application

# 打包階段:建立部署包
package_artifact:
  stage: package
  image: alpine:latest
  before_script:
    # 安裝必要工具
    - apk add --no-cache tar gzip
  script:
    # 建立部署包
    - tar -czf ${APP_NAME}-${APP_VERSION}.tar.gz dist/
    # 產生校驗和
    - sha256sum ${APP_NAME}-${APP_VERSION}.tar.gz > ${APP_NAME}-${APP_VERSION}.sha256
  artifacts:
    paths:
      - ${APP_NAME}-${APP_VERSION}.tar.gz
      - ${APP_NAME}-${APP_VERSION}.sha256
    expire_in: 30 days
  needs:
    - build_application
    - unit_tests

# 部署至生產環境
deploy_production:
  stage: deploy
  # 使用包含部署工具的 Docker 映像
  # 此映像已預先配置 SSH 金鑰與必要工具
  image: registry.example.com/deploy-tools:latest
  before_script:
    # 配置 SSH 連線
    # 確保 SSH 金鑰已正確設定於 GitLab CI/CD 變數中
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    # 配置已知主機以避免 SSH 主機驗證提示
    - ssh-keyscan -H ${PRODUCTION_HOST} >> ~/.ssh/known_hosts
    # 驗證 SSH 連線
    - ssh -o ConnectTimeout=10 root@${PRODUCTION_HOST} "echo 'SSH 連線測試成功'"
  script:
    # 建立遠端目標目錄
    - ssh root@${PRODUCTION_HOST} "mkdir -p ${PRODUCTION_PATH}/releases/${APP_VERSION}"
    
    # 上傳部署包至生產伺服器
    # -r: 遞迴複製目錄
    # -p: 保留檔案權限與時間戳記
    # -q: 安靜模式,減少輸出
    - scp -rpq dist/* root@${PRODUCTION_HOST}:${PRODUCTION_PATH}/releases/${APP_VERSION}/
    
    # 建立符號連結指向新版本
    # 使用符號連結可實現零停機部署與快速回滾
    - ssh root@${PRODUCTION_HOST} "ln -sfn ${PRODUCTION_PATH}/releases/${APP_VERSION} ${PRODUCTION_PATH}/current"
    
    # 重新載入 Web 伺服器配置
    # 使用 reload 而非 restart 以避免中斷現有連線
    - ssh root@${PRODUCTION_HOST} "systemctl reload apache2"
    
    # 驗證部署結果
    - ssh root@${PRODUCTION_HOST} "curl -f http://localhost/ || exit 1"
    
  # 部署後清理舊版本
  after_script:
    # 保留最近 5 個版本,刪除較舊的版本以節省空間
    - ssh root@${PRODUCTION_HOST} "cd ${PRODUCTION_PATH}/releases && ls -t | tail -n +6 | xargs -r rm -rf"
  
  # 定義環境資訊
  environment:
    name: production
    url: https://www.example.com
    on_stop: rollback_production
  
  # 只在 production 分支上執行
  rules:
    - if: '$CI_COMMIT_REF_NAME == "production"'
      when: manual
  
  # 依賴打包工作
  needs:
    - package_artifact

# 回滾機制
rollback_production:
  stage: deploy
  image: registry.example.com/deploy-tools:latest
  before_script:
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - ssh-keyscan -H ${PRODUCTION_HOST} >> ~/.ssh/known_hosts
  script:
    # 獲取前一個版本
    - PREVIOUS_VERSION=$(ssh root@${PRODUCTION_HOST} "cd ${PRODUCTION_PATH}/releases && ls -t | sed -n 2p")
    
    # 驗證前一個版本存在
    - |
      if [ -z "$PREVIOUS_VERSION" ]; then
        echo "錯誤: 找不到可回滾的版本"
        exit 1
      fi
    
    # 切換至前一個版本
    - ssh root@${PRODUCTION_HOST} "ln -sfn ${PRODUCTION_PATH}/releases/${PREVIOUS_VERSION} ${PRODUCTION_PATH}/current"
    
    # 重新載入 Web 伺服器
    - ssh root@${PRODUCTION_HOST} "systemctl reload apache2"
    
    # 驗證回滾結果
    - ssh root@${PRODUCTION_HOST} "curl -f http://localhost/ || exit 1"
    
    # 記錄回滾操作
    - echo "已回滾至版本: ${PREVIOUS_VERSION}"
  
  environment:
    name: production
    action: stop
  
  # 手動觸發
  when: manual
  
  # 只在 production 分支上可用
  only:
    - production

# 部署後驗證
verify_deployment:
  stage: verify
  image: curlimages/curl:latest
  script:
    # 健康檢查
    - curl -f https://www.example.com/health || exit 1
    
    # 驗證版本資訊
    - curl https://www.example.com/version | grep ${APP_VERSION} || exit 1
    
    # 執行冒煙測試
    - curl -f https://www.example.com/api/status || exit 1
  
  # 依賴部署工作
  needs:
    - deploy_production
  
  # 允許失敗但會標記為警告
  allow_failure: true

這個完整的部署配置展示了企業級 Pipeline 的設計原則。透過多階段設計,我們將建置、測試、打包與部署明確分離,每個階段都有清晰的職責與產出。使用符號連結實現零停機部署,保留多個版本支援快速回滾,並在部署後執行驗證確保服務正常運作。

Docker 容器化部署策略

Docker 容器化技術為自動化部署提供了一致的執行環境,解決了傳統部署中的環境差異問題。以下是使用 Docker 容器化部署的完整範例。

# Docker 容器化部署完整配置

stages:
  - build
  - test
  - publish
  - deploy

# 全域變數
variables:
  DOCKER_DRIVER: overlay2
  DOCKER_TLS_CERTDIR: "/certs"
  CI_REGISTRY: registry.gitlab.com
  CI_REGISTRY_IMAGE: $CI_REGISTRY/$CI_PROJECT_PATH
  DOCKER_BUILDKIT: 1

# 建置 Docker 映像
build_docker_image:
  stage: build
  image: docker:24-cli
  services:
    - docker:24-dind
  before_script:
    # 等待 Docker daemon 就緒
    - until docker info; do sleep 1; done
    # 登入 Container Registry
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
  script:
    # 建置應用程式映像
    # 使用多階段建置優化映像大小
    - |
      docker build \
        --cache-from $CI_REGISTRY_IMAGE:latest \
        --build-arg BUILDKIT_INLINE_CACHE=1 \
        --build-arg APP_VERSION=$CI_COMMIT_SHORT_SHA \
        --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA \
        --tag $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG \
        --file Dockerfile \
        .
    
    # 掃描映像安全漏洞
    - docker run --rm aquasec/trivy:latest image --exit-code 0 --severity HIGH,CRITICAL $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    
    # 推送映像至 Registry
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG
  
  # 只在主要分支與標籤上執行
  only:
    - main
    - production
    - tags

# 測試容器映像
test_container:
  stage: test
  image: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
  script:
    # 在容器內執行測試
    - npm run test
    # 驗證應用程式可以正常啟動
    - timeout 30 npm start &
    - sleep 10
    - curl -f http://localhost:3000/health || exit 1
  needs:
    - build_docker_image

# 發布映像至生產 Registry
publish_image:
  stage: publish
  image: docker:24-cli
  services:
    - docker:24-dind
  before_script:
    - echo $CI_REGISTRY_PASSWORD | docker login -u $CI_REGISTRY_USER --password-stdin $CI_REGISTRY
  script:
    # 拉取建置階段的映像
    - docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA
    
    # 標記為 latest
    - docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:latest
    
    # 如果是標籤推送,額外標記版本號
    - |
      if [ -n "$CI_COMMIT_TAG" ]; then
        docker tag $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
        docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
      fi
    
    # 推送 latest 標籤
    - docker push $CI_REGISTRY_IMAGE:latest
  
  needs:
    - test_container
  
  only:
    - production
    - tags

# 部署容器至生產環境
deploy_container:
  stage: deploy
  image: bitnami/kubectl:latest
  before_script:
    # 配置 kubectl
    - kubectl config set-cluster production --server=$KUBE_URL --certificate-authority=$KUBE_CA
    - kubectl config set-credentials deployer --token=$KUBE_TOKEN
    - kubectl config set-context production --cluster=production --user=deployer
    - kubectl config use-context production
  script:
    # 更新部署使用新映像
    - kubectl set image deployment/app app=$CI_REGISTRY_IMAGE:$CI_COMMIT_SHA -n production
    
    # 等待部署完成
    - kubectl rollout status deployment/app -n production --timeout=5m
    
    # 驗證 Pod 狀態
    - kubectl get pods -n production -l app=app
  
  environment:
    name: production
    url: https://www.example.com
    kubernetes:
      namespace: production
  
  when: manual
  
  only:
    - production
  
  needs:
    - publish_image

這個容器化部署範例展示了從映像建置、安全掃描、測試到 Kubernetes 部署的完整流程。使用 BuildKit 優化建置速度,整合 Trivy 進行安全掃描,並透過 kubectl 實現自動化的容器編排部署。

@startuml
!define PLANTUML_FORMAT svg
!theme _none_

skinparam dpi auto
skinparam shadowing false
skinparam linetype ortho
skinparam roundcorner 5
skinparam defaultFontName "Microsoft JhengHei UI"
skinparam defaultFontSize 16
skinparam minClassWidth 100

title Docker 容器化部署流程

start
:程式碼提交至 Git;
:觸發 GitLab CI/CD Pipeline;

partition "建置階段" {
  :拉取基礎映像;
  :建置應用程式映像;
  :執行安全掃描;
  
  if (發現嚴重漏洞?) then (是)
    :標記建置失敗;
    stop
  endif
  
  :推送映像至 Registry;
}

partition "測試階段" {
  :啟動測試容器;
  :執行單元測試;
  :執行整合測試;
  
  if (測試通過?) then (否)
    :標記測試失敗;
    stop
  endif
}

partition "發布階段" {
  :標記映像為 latest;
  :推送至生產 Registry;
}

partition "部署階段" {
  :等待手動核准;
  :更新 Kubernetes 部署;
  :執行滾動更新;
  :驗證 Pod 健康狀態;
  
  if (部署成功?) then (是)
    :完成部署;
  else (否)
    :自動回滾至前一版本;
  endif
}

stop

@enduml

這個流程圖清楚展示了 Docker 容器化部署的完整流程,從程式碼提交到生產環境部署的每個關鍵步驟。

Pipeline 問題診斷與解決

在實務應用中,Pipeline 執行過程可能遇到各種問題。有效的問題診斷與解決能力是確保 CI/CD 流程穩定運作的關鍵。

YAML 語法與配置問題

YAML 語法錯誤是 Pipeline 失敗的常見原因。GitLab 提供了 Pipeline 編輯器來協助驗證配置的正確性。

# 常見 YAML 配置錯誤範例與修正

# 錯誤範例 1: 缺少必要的 stage 定義
# 此配置會導致 Pipeline 失敗

# 錯誤的配置:
compile_assets:
  # 缺少 stage 定義
  script:
    - echo "編譯資源檔案"

deploy_app:
  stage: deploy
  script:
    - echo "部署應用程式"

# 問題說明:
# compile_assets 工作缺少 stage 定義
# GitLab 會嘗試將其分配至預設的 test 階段
# 但如果未定義 test 階段,就會導致錯誤

# 正確的配置:
stages:
  - build
  - deploy

compile_assets:
  stage: build
  script:
    - echo "編譯資源檔案"

deploy_app:
  stage: deploy
  script:
    - echo "部署應用程式"

# ---

# 錯誤範例 2: YAML 縮排錯誤
# YAML 對縮排要求非常嚴格

# 錯誤的配置:
build_job:
stage: build  # 缺少正確縮排
  script:
    - npm run build

# 正確的配置:
build_job:
  stage: build  # 正確縮排兩個空格
  script:
    - npm run build

# ---

# 錯誤範例 3: 變數引用錯誤

# 錯誤的配置:
deploy:
  script:
    # 錯誤的變數引用方式
    - echo "部署至 $CI_ENVIRONMENT_NAME"  # 單引號內變數不會被展開

# 正確的配置:
deploy:
  script:
    # 正確的變數引用
    - echo "部署至 ${CI_ENVIRONMENT_NAME}"  # 使用大括號明確界定變數
    - echo "部署至 $CI_ENVIRONMENT_NAME"    # 雙引號內可正常展開

# ---

# 錯誤範例 4: rules 與 only/except 混用

# 錯誤的配置:
deploy:
  stage: deploy
  script:
    - ./deploy.sh
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
  only:
    - main  # rules 與 only 不能同時使用

# 正確的配置 (使用 rules):
deploy:
  stage: deploy
  script:
    - ./deploy.sh
  rules:
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: always

# 或使用 only:
deploy:
  stage: deploy
  script:
    - ./deploy.sh
  only:
    - main

# ---

# 錯誤範例 5: artifacts 路徑設定錯誤

# 錯誤的配置:
build:
  stage: build
  script:
    - mkdir -p build/output
    - echo "產物" > build/output/app.jar
  artifacts:
    paths:
      - output/  # 路徑錯誤,應該是 build/output/

# 正確的配置:
build:
  stage: build
  script:
    - mkdir -p build/output
    - echo "產物" > build/output/app.jar
  artifacts:
    paths:
      - build/output/  # 正確的相對路徑
    expire_in: 1 day

# ---

# 錯誤範例 6: 依賴關係設定錯誤

# 錯誤的配置:
deploy:
  stage: deploy
  script:
    - ./deploy.sh
  needs:
    - build
    - test
  # 但 build 與 test 在不同階段,可能導致時序問題

# 正確的配置:
stages:
  - build
  - test
  - deploy

build:
  stage: build
  script:
    - npm run build

test:
  stage: test
  script:
    - npm run test
  needs:
    - build

deploy:
  stage: deploy
  script:
    - ./deploy.sh
  needs:
    - test  # 只需依賴 test,因為 test 已依賴 build

這些範例展示了常見的 YAML 配置錯誤與正確的修正方式。理解這些常見陷阱能夠幫助開發者快速診斷與修復 Pipeline 問題。

Runner 基礎設施問題

Runner 是執行 CI/CD 工作的基礎設施,其配置與狀態直接影響 Pipeline 的執行。

#!/bin/bash

#############################################
# GitLab Runner 診斷與修復工具
# 版本:1.0.0
# 作者:玄貓(BlackCat)
# 功能:診斷 Runner 常見問題並提供修復建議
#############################################

# 日誌函式
log() {
    local level=$1
    shift
    echo "[$(date +'%Y-%m-%d %H:%M:%S')] [${level}] $*"
}

# 檢查 Runner 服務狀態
check_runner_service() {
    log "INFO" "檢查 GitLab Runner 服務狀態..."
    
    if systemctl is-active --quiet gitlab-runner; then
        log "INFO" "Runner 服務運作正常"
        systemctl status gitlab-runner --no-pager
    else
        log "ERROR" "Runner 服務未運作"
        log "INFO" "嘗試啟動服務..."
        systemctl start gitlab-runner
        
        if systemctl is-active --quiet gitlab-runner; then
            log "INFO" "服務啟動成功"
        else
            log "ERROR" "服務啟動失敗,請檢查日誌"
            journalctl -u gitlab-runner --no-pager -n 50
            return 1
        fi
    fi
}

# 驗證 Runner 註冊狀態
verify_runner_registration() {
    log "INFO" "驗證 Runner 註冊狀態..."
    
    # 執行驗證指令
    gitlab-runner verify
    
    local exit_code=$?
    
    if [ $exit_code -eq 0 ]; then
        log "INFO" "所有 Runner 驗證通過"
    else
        log "WARNING" "部分 Runner 驗證失敗"
        log "INFO" "請檢查 Runner 配置與網路連線"
    fi
    
    # 列出所有 Runner
    log "INFO" "已註冊的 Runner 清單:"
    gitlab-runner list
}

# 檢查 Runner 資源使用情況
check_runner_resources() {
    log "INFO" "檢查 Runner 系統資源..."
    
    # CPU 使用率
    local cpu_usage=$(top -bn1 | grep "Cpu(s)" | awk '{print $2}' | cut -d'%' -f1)
    log "INFO" "CPU 使用率: ${cpu_usage}%"
    
    # 記憶體使用情況
    local mem_info=$(free -h | grep "Mem:")
    log "INFO" "記憶體使用: ${mem_info}"
    
    # 磁碟使用情況
    local disk_usage=$(df -h / | tail -1 | awk '{print $5}')
    log "INFO" "根目錄磁碟使用率: ${disk_usage}"
    
    # 檢查是否有資源瓶頸
    if (( $(echo "$cpu_usage > 80" | bc -l) )); then
        log "WARNING" "CPU 使用率過高,可能影響 Pipeline 執行"
    fi
    
    local mem_used_percent=$(free | grep Mem | awk '{print ($3/$2) * 100.0}')
    if (( $(echo "$mem_used_percent > 80" | bc -l) )); then
        log "WARNING" "記憶體使用率過高,可能影響 Pipeline 執行"
    fi
}

# 檢查 Docker 環境
check_docker_environment() {
    log "INFO" "檢查 Docker 環境..."
    
    if ! command -v docker &> /dev/null; then
        log "WARNING" "Docker 未安裝,無法執行 Docker executor"
        return 1
    fi
    
    # 檢查 Docker 服務狀態
    if ! systemctl is-active --quiet docker; then
        log "ERROR" "Docker 服務未運作"
        return 1
    fi
    
    # 檢查 Docker 版本
    local docker_version=$(docker --version)
    log "INFO" "Docker 版本: ${docker_version}"
    
    # 檢查 Docker 映像
    log "INFO" "本地 Docker 映像:"
    docker images --format "table {{.Repository}}\t{{.Tag}}\t{{.Size}}"
    
    # 檢查 Docker 磁碟使用
    docker system df
}

# 檢查網路連線
check_network_connectivity() {
    log "INFO" "檢查網路連線..."
    
    # 讀取 GitLab URL
    local gitlab_url=$(grep -oP 'url = "\K[^"]+' /etc/gitlab-runner/config.toml | head -1)
    
    if [ -z "$gitlab_url" ]; then
        log "WARNING" "無法從配置檔案讀取 GitLab URL"
        return 1
    fi
    
    log "INFO" "測試連線至: $gitlab_url"
    
    if curl -f -s -o /dev/null "$gitlab_url"; then
        log "INFO" "網路連線正常"
    else
        log "ERROR" "無法連線至 GitLab 伺服器"
        log "INFO" "請檢查網路設定與防火牆規則"
        return 1
    fi
}

# 清理 Runner 快取與暫存資料
cleanup_runner() {
    log "INFO" "清理 Runner 快取與暫存資料..."
    
    # 清理 Docker 未使用的資源
    if command -v docker &> /dev/null; then
        log "INFO" "清理 Docker 未使用的映像與容器..."
        docker system prune -af --volumes
    fi
    
    # 清理 GitLab Runner 快取
    if [ -d "/var/lib/gitlab-runner/cache" ]; then
        log "INFO" "清理 Runner 快取目錄..."
        rm -rf /var/lib/gitlab-runner/cache/*
    fi
    
    log "INFO" "清理完成"
}

# 主要執行流程
main() {
    log "INFO" "========================================="
    log "INFO" "GitLab Runner 診斷工具"
    log "INFO" "========================================="
    
    # 檢查是否以 root 執行
    if [ $EUID -ne 0 ]; then
        log "ERROR" "此工具需要 root 權限執行"
        exit 1
    fi
    
    # 執行各項檢查
    check_runner_service
    verify_runner_registration
    check_runner_resources
    check_docker_environment
    check_network_connectivity
    
    # 詢問是否執行清理
    echo ""
    read -p "是否執行清理作業? (y/N): " -n 1 -r
    echo
    if [[ $REPLY =~ ^[Yy]$ ]]; then
        cleanup_runner
    fi
    
    log "INFO" "========================================="
    log "INFO" "診斷完成"
    log "INFO" "========================================="
}

main "$@"

這個診斷工具能夠全面檢查 Runner 的健康狀態,包括服務狀態、資源使用、Docker 環境與網路連線,並提供清理功能。

Infrastructure as Code 實務應用

基礎設施即程式碼是現代維運的重要實踐,透過程式碼管理基礎設施能夠提升一致性、可重現性與可維護性。

Terraform 基礎設施自動化

Terraform 提供了強大的基礎設施管理能力,能夠以宣告式語法定義雲端資源。

# GitLab CI/CD 整合 Terraform 完整範例

stages:
  - validate
  - plan
  - apply
  - destroy

# Terraform 版本與配置
variables:
  TF_VERSION: "1.6.0"
  TF_ROOT: ${CI_PROJECT_DIR}/terraform
  TF_IN_AUTOMATION: "true"

# Terraform 初始化模板
.terraform_init: &terraform_init
  - cd ${TF_ROOT}
  - terraform version
  - terraform init
      -backend-config="address=${TF_HTTP_ADDRESS}"
      -backend-config="lock_address=${TF_HTTP_LOCK_ADDRESS}"
      -backend-config="unlock_address=${TF_HTTP_UNLOCK_ADDRESS}"
      -backend-config="username=${TF_HTTP_USERNAME}"
      -backend-config="password=${TF_HTTP_PASSWORD}"
      -backend-config="lock_method=POST"
      -backend-config="unlock_method=DELETE"
      -backend-config="retry_wait_min=5"

# 驗證 Terraform 配置
terraform_validate:
  stage: validate
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  before_script:
    - *terraform_init
  script:
    # 格式檢查
    - terraform fmt -check -recursive
    # 配置驗證
    - terraform validate
    # 安全掃描
    - |
      if command -v tfsec &> /dev/null; then
        tfsec ${TF_ROOT}
      fi
  except:
    - schedules

# 產生執行計畫
terraform_plan:
  stage: plan
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  before_script:
    - *terraform_init
  script:
    # 產生執行計畫
    - terraform plan
        -out=tfplan
        -var-file=environments/${CI_ENVIRONMENT_NAME}.tfvars
    # 顯示計畫細節
    - terraform show -json tfplan | jq '.' > tfplan.json
  artifacts:
    paths:
      - ${TF_ROOT}/tfplan
      - ${TF_ROOT}/tfplan.json
    reports:
      terraform: ${TF_ROOT}/tfplan.json
    expire_in: 7 days
  only:
    - main
    - merge_requests

# 應用基礎設施變更
terraform_apply:
  stage: apply
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  before_script:
    - *terraform_init
  script:
    # 應用計畫
    - terraform apply -auto-approve tfplan
    # 輸出資源資訊
    - terraform output -json > outputs.json
  artifacts:
    paths:
      - ${TF_ROOT}/outputs.json
    expire_in: 30 days
  environment:
    name: production
    on_stop: terraform_destroy
  when: manual
  only:
    - main
  dependencies:
    - terraform_plan

# 銷毀基礎設施
terraform_destroy:
  stage: destroy
  image:
    name: hashicorp/terraform:${TF_VERSION}
    entrypoint: [""]
  before_script:
    - *terraform_init
  script:
    - terraform destroy -auto-approve -var-file=environments/${CI_ENVIRONMENT_NAME}.tfvars
  environment:
    name: production
    action: stop
  when: manual
  only:
    - main

這個 Terraform 整合範例展示了完整的基礎設施生命週期管理,從驗證、計畫到應用與銷毀。

Ansible 配置自動化

Ansible 提供了無代理的配置管理能力,適合管理伺服器配置與應用部署。

# GitLab CI/CD 整合 Ansible 完整範例

stages:
  - lint
  - test
  - deploy

# Ansible 配置
variables:
  ANSIBLE_VERSION: "2.15"
  ANSIBLE_CONFIG: ${CI_PROJECT_DIR}/ansible.cfg
  ANSIBLE_INVENTORY: ${CI_PROJECT_DIR}/inventory

# Ansible Lint 檢查
ansible_lint:
  stage: lint
  image: cytopia/ansible-lint:latest
  script:
    # 檢查 playbook 語法
    - ansible-lint playbooks/*.yml --force-color
    # 檢查角色語法
    - ansible-lint roles/ --force-color
  artifacts:
    reports:
      codequality: gl-code-quality-report.json

# Molecule 測試
molecule_test:
  stage: test
  image: quay.io/ansible/molecule:latest
  services:
    - docker:dind
  variables:
    DOCKER_HOST: tcp://docker:2375
  script:
    # 執行 Molecule 測試
    - cd roles/webserver
    - molecule test
  only:
    - merge_requests

# 部署至測試環境
deploy_staging:
  stage: deploy
  image: cytopia/ansible:${ANSIBLE_VERSION}-tools
  before_script:
    # 配置 SSH
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H staging.example.com >> ~/.ssh/known_hosts
  script:
    # 執行 playbook
    - ansible-playbook
        -i ${ANSIBLE_INVENTORY}/staging
        playbooks/deploy.yml
        --extra-vars "env=staging version=${CI_COMMIT_SHA}"
        --diff
  environment:
    name: staging
    url: https://staging.example.com
  only:
    - main

# 部署至生產環境
deploy_production:
  stage: deploy
  image: cytopia/ansible:${ANSIBLE_VERSION}-tools
  before_script:
    - mkdir -p ~/.ssh
    - echo "$SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan -H production.example.com >> ~/.ssh/known_hosts
  script:
    # 檢查模式驗證
    - ansible-playbook
        -i ${ANSIBLE_INVENTORY}/production
        playbooks/deploy.yml
        --extra-vars "env=production version=${CI_COMMIT_SHA}"
        --check
        --diff
    # 實際執行部署
    - ansible-playbook
        -i ${ANSIBLE_INVENTORY}/production
        playbooks/deploy.yml
        --extra-vars "env=production version=${CI_COMMIT_SHA}"
        --diff
  environment:
    name: production
    url: https://www.example.com
  when: manual
  only:
    - main

這個 Ansible 整合範例展示了從 Lint 檢查、Molecule 測試到分階段部署的完整流程。

結語

GitLab CI/CD 作為一個功能完整的自動化部署平台,為現代軟體開發提供了從程式碼提交到生產環境部署的端對端解決方案。本文深入探討了 Pipeline 設計、YAML 配置、容器化策略、問題診斷與 IaC 整合等關鍵主題,透過豐富的程式碼範例與技術細節,展示了企業級自動化部署的最佳實踐。

成功的自動化部署不僅需要強大的工具支援,更需要團隊建立正確的 DevOps 文化與實踐。透過持續整合與持續部署,組織能夠大幅提升軟體交付速度與品質,快速回應市場需求。然而,自動化部署也帶來了新的挑戰,包括複雜度管理、安全性保障與可靠性維護等,這些都需要團隊持續學習與改進。

展望未來,隨著雲端原生技術的普及、容器編排的成熟,以及 AI 技術的應用,自動化部署將變得更加智慧化與高效化。GitLab CI/CD 將持續演進,為開發者提供更強大的功能與更流暢的體驗。持續學習、擁抱變化、積極實踐,是每位技術從業人員在這個快速演進的領域中保持競爭力的關鍵。