構建概述#

構建(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 流水線#

持續整合/持續交付流水線是自動化構建、測試、部署的核心基礎設施。

流水線設計原則#

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 最佳實踐#

1. 選擇合適的基礎映像

# 推薦:使用官方精簡映像
FROM openjdk:11-jre-slim

# 避免:使用完整 OS 映像
# FROM ubuntu:20.04

2. 多階段構建

# 構建階段
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
EXPOSE 8080
CMD ["java", "-jar", "app.jar"]

3. 利用分層快取

# 不經常變化的層放前面
COPY pom.xml .
RUN mvn dependency:go-offline

# 經常變化的層放後面
COPY src ./src
RUN mvn package

4. 減少層數

# 合併 RUN 指令
RUN apt-get update && \
    apt-get install -y curl vim && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
DooD vs DinD

在容器中構建 Docker 映像有兩種方式:

Docker outside of Docker (DooD)

掛載宿主機的 Docker Socket:

volumes:
  - /var/run/docker.sock:/var/run/docker.sock

優點:

  • 實作簡單
  • 共享宿主機的映像快取

缺點:

  • 安全風險(容器可以控制宿主機 Docker)
  • 與宿主機環境耦合

Docker in Docker (DinD)

在容器內運行獨立的 Docker Daemon:

services:
  dind:
    image: docker:dind
    privileged: true

優點:

  • 完全隔離
  • 更安全

缺點:

  • 無法共享映像快取
  • 需要 privileged 模式

建議:CI/CD 環境推薦使用 DooD 以利用快取;安全要求高的場景使用 DinD。

映像合規檢查#

構建完成的映像應該經過安全和合規檢查。

Clair 安全掃描:

# 掃描映像漏洞
clair-scanner --ip $(hostname -i) \
    -r report.json \
    myapp:latest

檢查項目:

類型說明
漏洞掃描檢查基礎映像和依賴的已知漏洞
映像大小過大的映像影響部署速度
基礎映像是否使用批准的基礎映像
敏感資訊是否包含密鑰、密碼等
用戶權限是否以 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. 容器映像

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

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