構建概述#
構建(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/.npm3. 增量構建
只構建發生變化的模組,而不是每次都全量構建:
# 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 --parallel5. 分層構建(容器場景)
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:#ffcdd22. 階段隔離
每個階段應該獨立,有明確的輸入和輸出:
stages:
- build # 編譯打包
- test # 測試
- analyze # 靜態分析
- package # 製品打包
- deploy # 部署3. 製品傳遞
構建階段產生的製品應該在後續階段重用,而不是重新構建:
build 階段 → 產生 artifact → test 階段使用同一 artifact → deploy 使用同一 artifactJenkins 流水線實踐#
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:#e3f2fdJenkins 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.042. 多階段構建
# 構建階段
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 package4. 減少層數
# 合併 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) │
└─────────────────────────────────────────────────────┘總結#
構建與流水線是持續交付的核心引擎,直接影響團隊的開發效率和交付品質。
關鍵要點:
構建加速
- 使用私有倉庫和本地快取
- 採用增量和並行構建
- 容器構建利用分層快取
構建檢測
- 使用 Maven Enforcer 強制依賴收斂
- 在構建階段整合程式碼品質檢查
流水線設計
- 快速失敗,及早反饋
- 階段隔離,製品傳遞
- 資源彈性伸縮
容器映像
- 多階段構建
- 合規和安全掃描
- 有意義的標籤策略
構建流水線應該是團隊的「無形助手」——自動運行、快速反饋、可靠穩定。如果開發人員經常抱怨構建太慢或不穩定,那就是需要最佳化的信號。