建置概述#

建置(Build)是將原始碼轉換為可部署製品的過程。在持續交付中,建置是連接開發和部署的關鍵環節,建置的速度和品質直接影響整體交付效率。

建置的核心目標#

目標說明
可重現性相同的原始碼應該產生相同的製品
快速反饋盡快讓開發人員知道建置結果
自動化減少人工介入,降低錯誤風險
可追溯性製品可以追溯到原始碼版本

建置加速#

建置速度是影響開發效率的關鍵因素。建置時間過長會:

  • 拖慢反饋迴圈
  • 降低開發人員的專注度
  • 減少每日整合的次數

五種加速方法#

1. 使用私有倉庫

建立組織內部的套件倉庫(如 Nexus、Artifactory),避免每次建置都從公網下載依賴。

<!-- Maven settings.xml -->
<mirrors>
    <mirror>
        <id>nexus</id>
        <url>http://nexus.internal/repository/maven-public/</url>
        <mirrorOf>*</mirrorOf>
    </mirror>
</mirrors>

私有倉庫不只是快取,還可以:

  • 控制外部依賴的准入
  • 發布內部套件
  • 確保建置的可重現性(即使上游套件被刪除)

2. 本地快取

充分利用本地快取,避免重複下載和編譯:

# Maven:指定本地倉庫位置
mvn install -Dmaven.repo.local=/cache/.m2

# Gradle:啟用建置快取
gradle build --build-cache

# npm:使用快取
npm ci --cache /cache/.npm

3. 增量建置

只建置發生變化的模組,而不是每次都全量建置:

# Maven:只建置有變化的模組
mvn install -pl :changed-module -am

# Gradle:預設支援增量建置
gradle build  # 自動跳過未變化的任務

4. 並行建置

利用多核心 CPU 並行編譯:

# Maven:並行建置
mvn install -T 4  # 使用 4 個執行緒
mvn install -T 1C  # 每個 CPU 核心一個執行緒

# Gradle:並行建置
gradle build --parallel

5. 分層建置(容器場景)

Docker 建置利用分層快取加速:

# 利用分層快取的 Dockerfile
FROM openjdk:11-jre-slim

# 先複製依賴(不常變化)
COPY pom.xml /app/
RUN mvn dependency:go-offline

# 再複製原始碼(經常變化)
COPY src /app/src
RUN mvn package -DskipTests
建置加速效果資料

根據業界經驗,各種加速方法的效果:

方法典型加速效果
私有倉庫減少 30-50% 依賴下載時間
本地快取減少 50-80% 重複建置時間
增量建置減少 60-90% 建置時間(小改動時)
並行建置減少 30-60% 建置時間(多模組時)
分層建置減少 50-80% Docker 建置時間

實際效果取決於專案特性,建議針對自己的專案進行測量和最佳化。

建置檢測#

建置不僅是編譯程式碼,還應該包含品質檢查,在問題進入後續環節之前及早發現。

Maven Enforcer 外掛程式#

Maven Enforcer 是一個強大的建構規則強制執行工具。

常用規則:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-enforcer-plugin</artifactId>
    <version>3.0.0</version>
    <executions>
        <execution>
            <id>enforce</id>
            <goals>
                <goal>enforce</goal>
            </goals>
            <configuration>
                <rules>
                    <!-- 依賴收斂:確保沒有依賴衝突 -->
                    <dependencyConvergence/>

                    <!-- 禁止特定依賴 -->
                    <bannedDependencies>
                        <excludes>
                            <exclude>commons-logging:*</exclude>
                            <exclude>log4j:log4j</exclude>
                        </excludes>
                    </bannedDependencies>

                    <!-- 要求最低 Java 版本 -->
                    <requireJavaVersion>
                        <version>[11,)</version>
                    </requireJavaVersion>

                    <!-- 要求最低 Maven 版本 -->
                    <requireMavenVersion>
                        <version>[3.6,)</version>
                    </requireMavenVersion>

                    <!-- 禁止 SNAPSHOT 依賴(發布時)-->
                    <requireReleaseDeps>
                        <onlyWhenRelease>true</onlyWhenRelease>
                    </requireReleaseDeps>
                </rules>
            </configuration>
        </execution>
    </executions>
</plugin>

依賴收斂規則(dependencyConvergence)可以避免很多難以排查的執行期問題。當同一個套件被引入不同版本時,Maven 只會選擇其中一個,可能導致 NoSuchMethodError 等問題。

其他建置時檢查#

檢查類型工具用途
程式碼風格Checkstyle統一程式碼格式
靜態分析PMD, SpotBugs發現潛在 Bug
安全漏洞OWASP Dependency Check檢查依賴的已知漏洞
測試覆蓋JaCoCo確保測試覆蓋率

CI/CD 流水線#

持續整合/持續交付流水線是自動化建置、測試、部署的核心基礎設施。CI/CD 的理念與階段定義請參考 01 持續交付理念,本章聚焦於流水線本身的設計與實作。

流水線設計原則#

1. 快速失敗

flowchart LR
    A[程式碼提交] --> B[編譯]
    B --> C[單元測試]
    C --> D[程式碼檢查]
    D --> E[整合測試]
    E --> F[部署]

    B -.->|失敗| X[停止]
    C -.->|失敗| X
    D -.->|失敗| X
    E -.->|失敗| X

    style X fill:#ffcdd2

2. 階段隔離

每個階段應該獨立,有明確的輸入和輸出:

stages:
  - build # 編譯打包
  - test # 測試
  - analyze # 靜態分析
  - package # 製品打包
  - deploy # 部署

3. 製品傳遞

建置階段產生的製品應該在後續階段重用,而不是重新建置:

build 階段 → 產生 artifact → test 階段使用同一 artifact → deploy 使用同一 artifact

Jenkins 流水線實踐#

Jenkinsfile 範例:

pipeline {
    agent any

    environment {
        MAVEN_OPTS = '-Dmaven.repo.local=/cache/.m2'
    }

    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }

        stage('Build') {
            steps {
                sh 'mvn clean compile -T 1C'
            }
        }

        stage('Test') {
            parallel {
                stage('Unit Tests') {
                    steps {
                        sh 'mvn test'
                    }
                }
                stage('Static Analysis') {
                    steps {
                        sh 'mvn sonar:sonar'
                    }
                }
            }
        }

        stage('Package') {
            steps {
                sh 'mvn package -DskipTests'
            }
        }

        stage('Deploy to Staging') {
            when {
                branch 'main'
            }
            steps {
                sh './deploy.sh staging'
            }
        }
    }

    post {
        always {
            junit '**/target/surefire-reports/*.xml'
            archiveArtifacts artifacts: 'target/*.jar'
        }
        failure {
            slackSend channel: '#ci-alerts',
                      message: "Build Failed: ${env.JOB_NAME}"
        }
    }
}

建置資源彈性伸縮#

建置需求通常有明顯的波峰波谷。上班時段建置頻繁,下班後和週末建置很少。

使用彈性建置資源可以同時最佳化成本和效率:高峰期快速擴容,低谷期自動縮容。

Jenkins Master 高可用:

flowchart TB
    LB[Load Balancer]
    LB --> M1["Master 1<br/>(Active)"]
    LB --> M2["Master 2<br/>(Standby)"]
    M1 --> Storage["Shared Storage<br/>(jobs, configs, plugins)"]
    M2 -.-> Storage

    style M1 fill:#e8f5e9
    style M2 fill:#ffecb3
    style Storage fill:#e3f2fd

Jenkins Slave 彈性伸縮(Kubernetes):

# Jenkins Kubernetes Plugin 組態
apiVersion: v1
kind: Pod
metadata:
  labels:
    jenkins: slave
spec:
  containers:
    - name: jnlp
      image: jenkins/jnlp-slave:latest
      resources:
        requests:
          cpu: "1"
          memory: "2Gi"
        limits:
          cpu: "2"
          memory: "4Gi"
    - name: maven
      image: maven:3.8-openjdk-11
      command: ["cat"]
      tty: true

彈性策略:

策略說明
預熱池預先啟動少量 Slave,減少等待時間
最大上限設定 Slave 數量上限,控制成本
空閒回收空閒超過一定時間自動銷毀
排隊閾值排隊任務超過閾值時擴容

容器映像建置#

容器化應用需要建置 Docker 映像作為部署製品。

Dockerfile 最佳實踐#

撰寫高品質 Dockerfile 的核心原則:

  1. 選用精簡基礎映像:優先 *-slimalpine 變體,避免完整 OS 映像
  2. 多階段建置:build 與 runtime 階段分離,最終映像只保留執行所需檔案
  3. 分層順序最佳化:不常變化的依賴層放前面,經常變化的原始碼層放後面,最大化快取命中
  4. 合併 RUN 指令:將 apt-get update && install && clean 合併為一層,並清除暫存檔
  5. 明確版本標籤:避免 latest,使用語義化版本(如 openjdk:11-jre-slim
  6. 以非 root 用戶執行:透過 USER 指令降低執行權限
  7. .dockerignore 排除無關檔案:避免將 .gittarget/node_modules/ 等帶進 build context

精簡範例(多階段建置 + 分層快取 + 非 root):

# 建置階段
FROM maven:3.8-openjdk-11 AS builder
WORKDIR /build
COPY pom.xml .
RUN mvn dependency:go-offline
COPY src ./src
RUN mvn package -DskipTests

# 執行階段
FROM openjdk:11-jre-slim
WORKDIR /app
COPY --from=builder /build/target/*.jar app.jar
USER 1000
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]
DooD vs DinD

在容器中建置 Docker 映像有兩種主流方式,差異整理如下:

維度DooD(Docker outside of Docker)DinD(Docker in Docker)
實作方式掛載宿主機 docker.sock容器內執行獨立 Daemon
映像快取與宿主機共用獨立、無法共用
隔離性與宿主機耦合完全隔離
權限需求一般需要 privileged: true
安全風險較高(可控制宿主機 Docker)較低

結論:CI/CD 環境通常選 DooD(快取利用率高);對安全隔離要求高的場景(多租戶、不可信任務)選 DinD。

映像合規檢查#

建置完成的映像應該經過安全與合規檢查,主流工具包含 Trivy、Clair、Grype 等,可整合進流水線自動掃描。

檢查項目:

類型說明
漏洞掃描檢查基礎映像和依賴的已知漏洞
映像大小過大的映像影響部署速度
基礎映像是否使用批准的基礎映像
敏感資訊是否包含密鑰、密碼等
用戶權限是否以 root 用戶執行

映像標籤策略:

# 使用有意義的標籤
myapp:1.2.3                    # 語義版本
myapp:1.2.3-abc1234            # 版本 + Git Commit
myapp:20231215-abc1234         # 日期 + Git Commit

# 避免
myapp:latest                   # 難以追蹤版本

永遠不要在生產環境使用 latest 標籤。它無法確保部署的是哪個版本,也無法可靠地回滾。

流水線監控#

流水線的健康狀態需要持續監控和最佳化。

關鍵指標#

指標說明目標
建置時間從提交到建置完成的時間< 10 分鐘
成功率建置成功的比例> 95%
等待時間建置任務排隊等待的時間< 1 分鐘
部署頻率每天/每週的部署次數持續提升
MTTR建置失敗到恢復的時間< 1 小時

建置儀表板#

┌─────────────────────────────────────────────────────┐
│                  Build Dashboard                   │
├─────────────────────────────────────────────────────┤
│  Today's Builds: 127    Success Rate: 94%          │
│  Avg Build Time: 8m32s  Queue Time: 23s            │
├─────────────────────────────────────────────────────┤
│  Recent Failures:                                   │
│  - user-service #456    Test Failed     10:32     │
│  - payment-api #789     Build Timeout   09:45     │
├─────────────────────────────────────────────────────┤
│  Build Time Trend:                                 │
│  ████████████████████░░░░ 8.5m (↑12% from last wk) │
└─────────────────────────────────────────────────────┘

總結#

建置與流水線是持續交付的核心引擎,直接影響團隊的開發效率和交付品質。

關鍵要點:

  1. 建置加速

    • 使用私有倉庫和本地快取
    • 採用增量和並行建置
    • 容器建置利用分層快取
  2. 建置檢測

    • 使用 Maven Enforcer 強制依賴收斂
    • 在建置階段整合程式碼品質檢查
  3. 流水線設計

    • 快速失敗,及早反饋
    • 階段隔離,製品傳遞
    • 資源彈性伸縮
  4. 容器映像

    • 多階段建置
    • 合規和安全掃描
    • 有意義的標籤策略

建置流水線應該是團隊的「無形助手」——自動執行、快速反饋、可靠穩定。如果開發人員經常抱怨建置太慢或不穩定,那就是需要最佳化的訊號。