建置概述#
建置(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 流水線#
持續整合/持續交付流水線是自動化建置、測試、部署的核心基礎設施。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:#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 最佳實踐#
撰寫高品質 Dockerfile 的核心原則:
- 選用精簡基礎映像:優先
*-slim或alpine變體,避免完整 OS 映像 - 多階段建置:build 與 runtime 階段分離,最終映像只保留執行所需檔案
- 分層順序最佳化:不常變化的依賴層放前面,經常變化的原始碼層放後面,最大化快取命中
- 合併 RUN 指令:將
apt-get update && install && clean合併為一層,並清除暫存檔 - 明確版本標籤:避免
latest,使用語義化版本(如openjdk:11-jre-slim) - 以非 root 用戶執行:透過
USER指令降低執行權限 .dockerignore排除無關檔案:避免將.git、target/、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) │
└─────────────────────────────────────────────────────┘總結#
建置與流水線是持續交付的核心引擎,直接影響團隊的開發效率和交付品質。
關鍵要點:
建置加速
- 使用私有倉庫和本地快取
- 採用增量和並行建置
- 容器建置利用分層快取
建置檢測
- 使用 Maven Enforcer 強制依賴收斂
- 在建置階段整合程式碼品質檢查
流水線設計
- 快速失敗,及早反饋
- 階段隔離,製品傳遞
- 資源彈性伸縮
容器映像
- 多階段建置
- 合規和安全掃描
- 有意義的標籤策略
建置流水線應該是團隊的「無形助手」——自動執行、快速反饋、可靠穩定。如果開發人員經常抱怨建置太慢或不穩定,那就是需要最佳化的訊號。