目錄
Jib 簡介
什麼是 Jib?
Jib 是 Google 開發的 Java 容器化工具,可以直接從 Gradle/Maven 建構 Docker/OCI 映像,不需要 Docker daemon,也不需要編寫 Dockerfile。
核心優勢
| 特性 | Jib | 傳統 Dockerfile |
|---|---|---|
| 需要 Docker | ❌ 不需要 | ✅ 需要 |
| 需要 Dockerfile | ❌ 不需要 | ✅ 需要 |
| 建構速度 | ⚡ 快(增量建構) | 🐢 慢 |
| 映像分層 | ✅ 自動優化 | ⚠️ 手動優化 |
| 可重現性 | ✅ 完全可重現 | ⚠️ 依賴基礎映像 |
| 整合度 | ✅ 深度整合 Gradle | ⚠️ 需要額外配置 |
Jib 的工作原理
Java 原始碼
↓
Gradle 編譯
↓
Jib 分析依賴
↓
分層映像(自動優化)
├── 基礎層(JRE)
├── 依賴層(jar 檔案)
├── 資源層(resources)
└── 類別層(.class 檔案)
↓
推送到 Registry
分層優勢:
- 修改程式碼只重建類別層
- 新增依賴只重建依賴層
- Docker 快取大幅減少建構時間
基本設定
1. 添加 Jib 插件
// build.gradle
plugins {
id 'java'
id 'com.google.cloud.tools.jib' version '3.4.0'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web:3.0.0'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
}
// Jib 基本配置
jib {
from {
image = 'eclipse-temurin:17-jre' // 基礎映像
}
to {
image = 'myapp' // 目標映像名稱
tags = ['latest', project.version]
}
container {
mainClass = 'com.example.Application' // 主類別
ports = ['8080'] // 暴露的 port
}
}
2. 建構映像
# 建構到本地 Docker
./gradlew jibDockerBuild
# 建構並推送到 Registry
./gradlew jib
# 建構到 tar 檔案
./gradlew jibBuildTar
Jib 配置詳解
完整配置結構
jib {
from {
// 基礎映像配置
}
to {
// 目標映像配置
}
container {
// 容器配置
}
extraDirectories {
// 額外檔案配置
}
}
from - 基礎映像配置
1. 基本用法
jib {
from {
image = 'eclipse-temurin:17-jre' // OpenJDK 17
}
}
2. Docker 鏡像標籤詳解
標籤組成結構:
eclipse-temurin : 17 - jre - noble
↓ ↓ ↓ ↓
鏡像名 Java版本 類型 OS版本
標籤各部分說明:
| 部分 | 說明 | 常見值 |
|---|---|---|
| Java 版本 | JDK/JRE 主版本號 | 8, 11, 17, 21, 25 |
| 發行類型 | 開發工具 vs 運行環境 | jdk (完整), jre (僅運行時) |
| OS 基礎 | 作業系統版本 | noble, jammy, alpine, focal |
OS 版本對照表:
| 標籤後綴 | 作業系統 | 特性 |
|---|---|---|
noble |
Ubuntu 24.04 | 最新 LTS |
jammy |
Ubuntu 22.04 | 穩定 LTS |
focal |
Ubuntu 20.04 | 舊 LTS |
alpine |
Alpine Linux | 最小化 (~5-10MB) |
| 無後綴 | Ubuntu (最新) | 默認選項 |
版本標籤範例:
jib {
from {
// 1. 簡潔版本(默認 jdk + 最新 Ubuntu)
image = 'eclipse-temurin:17'
// 2. 指定 JDK/JRE
image = 'eclipse-temurin:17-jdk' // 開發用(包含編譯器)
image = 'eclipse-temurin:17-jre' // 生產用(僅運行時)
// 3. 指定 OS(推薦用於生產環境)
image = 'eclipse-temurin:17-jdk-noble' // Ubuntu 24.04
image = 'eclipse-temurin:17-jdk-jammy' // Ubuntu 22.04
image = 'eclipse-temurin:17-jre-alpine' // Alpine(最小)
// 4. 完整版本號(最佳實踐 - 確保可重現)
image = 'eclipse-temurin:17.0.17_10-jdk-noble'
image = 'eclipse-temurin:21.0.9_10-jre-alpine'
}
}
如何查看可用版本:
# 方法 1: Docker Hub 網頁查看(最直觀)
# https://hub.docker.com/_/eclipse-temurin/tags
# 方法 2: 使用 API 查詢
curl -s "https://registry.hub.docker.com/v2/repositories/library/eclipse-temurin/tags?page_size=100" \
| grep -o '"name":"[^"]*"' | cut -d'"' -f4 | head -30
# 方法 3: 查看特定 Java 版本的所有變體
curl -s "https://registry.hub.docker.com/v2/repositories/library/eclipse-temurin/tags?page_size=100" \
| grep -o '"name":"17[^"]*"' | cut -d'"' -f4 | sort
# 方法 4: 查看鏡像詳細資訊
docker manifest inspect eclipse-temurin:17-jdk-noble
版本選擇建議:
| 使用場景 | 推薦標籤 | 原因 |
|---|---|---|
| 生產環境建構 | 17-jdk-noble |
完整工具 + 穩定 OS |
| 運行時容器 | 17-jre-alpine |
最小體積(省資源) |
| 開發調試 | 17-jdk |
包含完整開發工具 |
| CI/CD | 17.0.17_10-jdk-noble |
鎖定版本確保可重現 |
| 快速測試 | 17 |
快速開始(不考慮體積) |
鏡像體積對比:
# 實際體積參考(僅供參考,會隨版本變化)
eclipse-temurin:17-jdk # ~450MB
eclipse-temurin:17-jdk-alpine # ~320MB
eclipse-temurin:17-jre # ~270MB
eclipse-temurin:17-jre-alpine # ~170MB
gcr.io/distroless/java17 # ~150MB(最小)
標籤策略最佳實踐:
jib {
from {
// ❌ 不推薦:過於寬泛,可能導致不可預測的更新
image = 'eclipse-temurin:17'
// ✅ 推薦:明確指定 OS 和類型
image = 'eclipse-temurin:17-jdk-noble'
// ✅ 最佳:鎖定完整版本號用於生產
image = 'eclipse-temurin:17.0.17_10-jdk-noble'
}
}
3. 常用基礎映像選擇
jib {
from {
// OpenJDK Eclipse Temurin(推薦 - 最廣泛使用)
image = 'eclipse-temurin:17-jre-noble' // Ubuntu 24.04
image = 'eclipse-temurin:17-jre-alpine' // Alpine(更小)
// Amazon Corretto(AWS 環境推薦)
image = 'amazoncorretto:17'
image = 'amazoncorretto:17-alpine'
// GraalVM(需要原生編譯)
image = 'ghcr.io/graalvm/graalvm-ce:ol8-java17'
// Distroless(最小化,無 shell,安全性最高)
image = 'gcr.io/distroless/java17-debian11'
}
}
3. 私有倉庫認證
jib {
from {
image = 'my-registry.com/base-image:latest'
auth {
username = project.findProperty('registryUser') ?: System.getenv('REGISTRY_USER')
password = project.findProperty('registryPassword') ?: System.getenv('REGISTRY_PASSWORD')
}
}
}
4. 平台指定
jib {
from {
image = 'eclipse-temurin:17-jre'
platforms {
platform {
architecture = 'amd64'
os = 'linux'
}
platform {
architecture = 'arm64'
os = 'linux'
}
}
}
}
to - 目標映像配置
1. 基本配置
jib {
to {
image = 'myapp'
tags = ['latest', project.version, "${project.version}-${new Date().format('yyyyMMdd')}"]
}
}
2. 推送到 Docker Hub
jib {
to {
image = 'docker.io/myusername/myapp'
// 或簡寫
image = 'myusername/myapp'
tags = ['latest', '1.0.0']
auth {
username = System.getenv('DOCKER_USERNAME')
password = System.getenv('DOCKER_PASSWORD')
}
}
}
3. 推送到私有 Registry
jib {
to {
image = 'my-registry.com:5000/myapp'
tags = ['latest', project.version]
auth {
username = project.findProperty('registryUser')
password = project.findProperty('registryPassword')
}
// 允許不安全的 registry(HTTP)
// credHelper = 'osxkeychain' // macOS
// credHelper = 'wincred' // Windows
// credHelper = 'pass' // Linux
}
}
4. 推送到 AWS ECR
jib {
to {
image = '123456789012.dkr.ecr.us-west-2.amazonaws.com/myapp'
tags = ['latest']
// ECR 使用 credential helper
credHelper = 'ecr-login'
}
}
5. 推送到 Google Container Registry (GCR)
jib {
to {
image = 'gcr.io/my-project/myapp'
tags = ['latest']
credHelper = 'gcr'
}
}
container - 容器配置
1. 基本配置
jib {
container {
// 主類別
mainClass = 'com.example.Application'
// 暴露的 port
ports = ['8080', '8443']
// JVM 參數
jvmFlags = [
'-Xms256m',
'-Xmx512m',
'-XX:+UseG1GC',
'-Dspring.profiles.active=prod'
]
// 程式參數
args = ['--server.port=8080']
// 環境變數
environment = [
'JAVA_OPTS': '-Xmx512m',
'APP_ENV': 'production'
]
// 映像格式
format = 'OCI' // 或 'Docker'
// 建立時間(用於可重現建構)
creationTime = 'USE_CURRENT_TIMESTAMP' // 或特定時間
}
}
2. 使用者配置
jib {
container {
user = '1000:1000' // UID:GID
// 或
user = 'appuser'
// 工作目錄
workingDirectory = '/app'
}
}
3. 標籤(Labels)
jib {
container {
labels = [
'maintainer': 'your-email@example.com',
'version': project.version,
'description': 'My Application',
'org.opencontainers.image.source': 'https://github.com/user/repo'
]
}
}
4. 容器啟動配置
jib {
container {
// 使用 shell 格式
entrypoint = ['sh', '-c', 'java $JAVA_OPTS -jar app.jar']
// 或使用預設的 Java entrypoint
// entrypoint = 'INHERIT' // 繼承基礎映像
}
}
extraDirectories - 額外檔案
1. 添加靜態檔案
jib {
extraDirectories {
paths {
path {
from = file('src/main/jib') // 來源目錄
into = '/app' // 容器中的目標路徑
}
}
}
}
目錄結構:
src/main/jib/
├── config/
│ └── application.properties
└── scripts/
└── startup.sh
建構後在容器中:
/app/config/application.properties
/app/scripts/startup.sh
2. 多個來源目錄
jib {
extraDirectories {
paths {
path {
from = file('src/main/jib')
into = '/app'
}
path {
from = file('config')
into = '/config'
}
path {
from = file('scripts')
into = '/scripts'
includes = ['*.sh'] // 只包含 .sh 檔案
excludes = ['test*.sh'] // 排除測試腳本
}
}
permissions = [
'/scripts/startup.sh': '755', // 設定執行權限
'/config/*': '644'
]
}
}
常用指令
建構相關
# 建構到本地 Docker(需要 Docker daemon)
./gradlew jibDockerBuild
# 建構並推送到 Registry
./gradlew jib
# 建構到 tar 檔案
./gradlew jibBuildTar --image=myapp:1.0.0
# 載入 tar 到 Docker
docker load < build/jib-image.tar
參數覆寫
# 覆寫映像名稱
./gradlew jib --image=myregistry.com/myapp:v2
# 設定認證
./gradlew jib \
-Djib.to.auth.username=myuser \
-Djib.to.auth.password=mypass
# 覆寫基礎映像
./gradlew jib \
-Djib.from.image=eclipse-temurin:17-jre-alpine
# 設定 JVM 參數
./gradlew jib \
-Djib.container.jvmFlags=-Xmx1g,-Xms512m
除錯與資訊
# 顯示詳細日誌
./gradlew jib --info
# 顯示建構計畫(不實際建構)
./gradlew jibDockerBuild --dry-run
# 查看映像配置
./gradlew jib --console=plain
進階配置
1. 自訂分層
jib {
// 自訂分層策略
containerizingMode = 'packaged' // 或 'exploded'(預設)
container {
// exploded: 展開 jar(更好的分層)
// packaged: 保持 jar 格式
}
}
2. 排除檔案
jib {
extraDirectories {
paths {
path {
from = file('src/main/resources')
into = '/app/resources'
excludes = [
'**/*.properties', // 排除 properties 檔案
'**/test/**' // 排除測試資源
]
}
}
}
}
3. 離線模式
jib {
allowInsecureRegistries = true // 允許 HTTP registry
// 使用本地快取的基礎映像
from {
image = 'eclipse-temurin:17-jre'
}
}
離線建構:
./gradlew jibDockerBuild --offline
4. 多環境配置
// 定義環境配置
def environments = [
dev: [
registry: 'localhost:5000',
jvmFlags: ['-Xmx256m', '-Dspring.profiles.active=dev']
],
prod: [
registry: 'my-registry.com',
jvmFlags: ['-Xmx1g', '-Dspring.profiles.active=prod']
]
]
// 從系統屬性或環境變數取得環境
def env = System.getProperty('env') ?: 'dev'
def config = environments[env]
jib {
to {
image = "${config.registry}/myapp"
tags = ['latest', project.version]
}
container {
jvmFlags = config.jvmFlags
}
}
使用:
# 開發環境
./gradlew jib -Denv=dev
# 生產環境
./gradlew jib -Denv=prod
5. Spring Boot 整合
plugins {
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'com.google.cloud.tools.jib' version '3.4.0'
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
jib {
from {
image = 'eclipse-temurin:17-jre'
}
to {
image = 'myapp'
}
container {
// Spring Boot 會自動設定 mainClass
ports = ['8080']
jvmFlags = [
'-Dspring.profiles.active=prod',
'-XX:+UseContainerSupport',
'-XX:MaxRAMPercentage=75.0'
]
// Spring Boot 特定配置
environment = [
'SPRING_OUTPUT_ANSI_ENABLED': 'ALWAYS'
]
}
}
多階段建構
概念
Jib 本身不需要多階段建構(因為不使用 Dockerfile),但可以模擬類似效果:
// build.gradle
// 定義建構任務
task buildDev {
doLast {
exec {
commandLine './gradlew', 'jib',
'-Djib.to.image=myapp:dev',
'-Djib.container.jvmFlags=-Xmx256m,-Dspring.profiles.active=dev'
}
}
}
task buildProd {
doLast {
exec {
commandLine './gradlew', 'jib',
'-Djib.to.image=myapp:prod',
'-Djib.container.jvmFlags=-Xmx1g,-Dspring.profiles.active=prod'
}
}
}
實際應用範例
範例 1:簡單 Spring Boot 應用
// build.gradle
plugins {
id 'org.springframework.boot' version '3.0.0'
id 'io.spring.dependency-management' version '1.1.0'
id 'java'
id 'com.google.cloud.tools.jib' version '3.4.0'
}
group = 'com.example'
version = '1.0.0'
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
}
java {
sourceCompatibility = JavaVersion.VERSION_17
}
jib {
from {
image = 'eclipse-temurin:17-jre-alpine'
}
to {
image = 'myapp'
tags = ['latest', project.version]
}
container {
ports = ['8080']
jvmFlags = [
'-Xms256m',
'-Xmx512m',
'-Dspring.profiles.active=prod'
]
creationTime = 'USE_CURRENT_TIMESTAMP'
}
}
建構:
./gradlew jibDockerBuild
docker run -p 8080:8080 myapp:latest
範例 2:推送到 Docker Hub
jib {
from {
image = 'eclipse-temurin:17-jre'
}
to {
image = 'docker.io/myusername/myapp'
tags = ['latest', "${project.version}"]
auth {
username = System.getenv('DOCKER_USERNAME')
password = System.getenv('DOCKER_PASSWORD')
}
}
container {
ports = ['8080']
labels = [
'maintainer': 'your-email@example.com',
'version': project.version
]
}
}
使用:
# 設定環境變數
export DOCKER_USERNAME=myusername
export DOCKER_PASSWORD=mypassword
# 建構並推送
./gradlew jib
範例 3:包含配置檔案
專案結構:
myproject/
├── build.gradle
├── src/
│ └── main/
│ ├── java/
│ └── jib/
│ ├── config/
│ │ └── application.yml
│ └── scripts/
│ └── startup.sh
build.gradle:
jib {
from {
image = 'eclipse-temurin:17-jre'
}
to {
image = 'myapp'
}
container {
ports = ['8080']
entrypoint = ['/scripts/startup.sh']
}
extraDirectories {
paths {
path {
from = file('src/main/jib')
into = '/'
}
}
permissions = [
'/scripts/startup.sh': '755'
]
}
}
startup.sh:
#!/bin/sh
echo "Starting application..."
java $JAVA_OPTS -jar /app/app.jar
範例 4:多模組專案
// 根 build.gradle
subprojects {
apply plugin: 'java'
apply plugin: 'com.google.cloud.tools.jib'
jib {
from {
image = 'eclipse-temurin:17-jre'
}
to {
image = "myregistry.com/${project.name}"
tags = [project.version]
}
}
}
// api/build.gradle
jib {
container {
mainClass = 'com.example.api.ApiApplication'
ports = ['8080']
}
}
// worker/build.gradle
jib {
container {
mainClass = 'com.example.worker.WorkerApplication'
}
}
建構:
# 建構所有模組
./gradlew jib
# 建構特定模組
./gradlew :api:jib
./gradlew :worker:jib
與 Dockerfile 比較
Dockerfile 方式
# Dockerfile
FROM eclipse-temurin:17-jre
WORKDIR /app
COPY build/libs/myapp.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
# 需要先建構 jar
./gradlew build
# 建構映像
docker build -t myapp .
# 推送
docker push myapp
問題:
- 需要 Docker daemon
- 分層不夠細緻(整個 jar 是一層)
- 修改程式碼需要重建整個 jar 層
Jib 方式
// build.gradle
jib {
from {
image = 'eclipse-temurin:17-jre'
}
to {
image = 'myapp'
}
container {
ports = ['8080']
}
}
# 一個指令完成所有事情
./gradlew jib
優勢:
- 不需要 Docker
- 自動分層優化
- 增量建構(快)
分層對比
Dockerfile:
Layer 1: 基礎映像
Layer 2: 整個 jar 檔案(包含所有依賴和程式碼)
Jib:
Layer 1: 基礎映像
Layer 2: 依賴 jar(很少改變)
Layer 3: 資源檔案
Layer 4: 類別檔案(經常改變)
修改程式碼時:
- Dockerfile:重建 Layer 2(整個 jar)
- Jib:只重建 Layer 4(類別檔案)
疑難排解
1. 無法連接到 Registry
錯誤:
Error: Cannot connect to registry
解決:
jib {
allowInsecureRegistries = true // 允許 HTTP
to {
image = 'localhost:5000/myapp'
}
}
2. 認證失敗
錯誤:
Error: 401 Unauthorized
解決:
# 方式 1:使用環境變數
export DOCKER_USERNAME=myuser
export DOCKER_PASSWORD=mypass
# 方式 2:使用 credential helper
./gradlew jib -Djib.to.credHelper=osxkeychain
# 方式 3:使用 gradle.properties
echo "registryUser=myuser" >> gradle.properties
echo "registryPassword=mypass" >> gradle.properties
3. 找不到主類別
錯誤:
Error: Main class was not found
解決:
jib {
container {
mainClass = 'com.example.Application' // 明確指定
}
}
// 或在 application 插件中指定
application {
mainClass = 'com.example.Application'
}
4. 記憶體不足
錯誤:
OutOfMemoryError during build
解決:
# 增加 Gradle 記憶體
export GRADLE_OPTS="-Xmx2g"
# 或在 gradle.properties
org.gradle.jvmargs=-Xmx2g
5. 映像太大
問題:映像檔案過大
解決:
jib {
from {
// 使用更小的基礎映像
image = 'eclipse-temurin:17-jre-alpine' // Alpine 版本
// 或
image = 'gcr.io/distroless/java17-debian11' // Distroless
}
// 排除不必要的檔案
extraDirectories {
paths {
path {
from = file('src/main/resources')
excludes = ['**/*.md', '**/test/**']
}
}
}
}
6. 查看建構的映像資訊
# 檢查映像
docker images myapp
# 查看映像歷史(分層)
docker history myapp:latest
# 檢查映像內容
docker run --rm myapp:latest ls -la /app
最佳實踐
1. 使用輕量基礎映像
✅ 推薦:
jib {
from {
// Alpine(小但完整)
image = 'eclipse-temurin:17-jre-alpine'
// Distroless(最小化)
image = 'gcr.io/distroless/java17-debian11'
}
}
❌ 避免:使用完整的 JDK 映像
// 不必要地大
image = 'eclipse-temurin:17'
2. 設定記憶體限制
jib {
container {
jvmFlags = [
'-XX:+UseContainerSupport', // 偵測容器環境
'-XX:MaxRAMPercentage=75.0', // 使用 75% 容器記憶體
'-XX:InitialRAMPercentage=50.0' // 初始 50%
]
}
}
3. 使用環境變數
jib {
to {
auth {
username = System.getenv('REGISTRY_USER')
password = System.getenv('REGISTRY_PASSWORD')
}
}
container {
environment = [
'JAVA_OPTS': '-Xmx512m',
'APP_ENV': System.getenv('APP_ENV') ?: 'production'
]
}
}
4. 版本標籤策略
jib {
to {
image = 'myapp'
tags = [
'latest',
project.version,
"${project.version}-${new Date().format('yyyyMMdd-HHmm')}",
System.getenv('GIT_COMMIT')?.substring(0, 7) ?: 'dev'
]
}
}
5. 可重現建構
jib {
container {
// 使用固定時間戳記
creationTime = '1970-01-01T00:00:00Z'
// 或使用當前時間
creationTime = 'USE_CURRENT_TIMESTAMP'
}
}
6. CI/CD 整合
# .github/workflows/build.yml
name: Build and Push
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Build and push with Jib
env:
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
run: |
./gradlew jib \
-Djib.to.image=docker.io/${{ secrets.DOCKER_USERNAME }}/myapp \
-Djib.to.auth.username=${{ secrets.DOCKER_USERNAME }} \
-Djib.to.auth.password=${{ secrets.DOCKER_PASSWORD }}
7. 本地測試流程
# 1. 建構到本地 Docker
./gradlew jibDockerBuild
# 2. 執行容器
docker run -p 8080:8080 myapp:latest
# 3. 測試
curl http://localhost:8080/health
# 4. 查看日誌
docker logs <container-id>
# 5. 進入容器除錯
docker exec -it <container-id> sh
總結
Jib 的核心優勢
- 不需要 Docker:可以在沒有 Docker 的環境建構映像
- 快速建構:增量建構和智慧分層
- 簡單配置:不需要編寫 Dockerfile
- 自動優化:自動分層優化,最大化快取效益
- 可重現性:完全可重現的建構
適用場景
✅ 適合使用 Jib:
- Java/Kotlin 應用程式
- Spring Boot 專案
- 需要快速建構的 CI/CD 環境
- 不想維護 Dockerfile
❌ 不適合使用 Jib:
- 需要複雜的自訂建構步驟
- 非 Java 應用程式
- 需要在容器中安裝系統套件
快速指令參考
# 建構到本地 Docker
./gradlew jibDockerBuild
# 建構並推送
./gradlew jib
# 建構到 tar
./gradlew jibBuildTar
# 覆寫映像名稱
./gradlew jib --image=myregistry.com/myapp:v2
# 顯示詳細日誌
./gradlew jib --info
建立日期:2025-11-03 最後更新:2025-11-18