はじめに
Quarkus は、GraalVM Native Image 用に最初から設計されました。ネイティブ実行可能ファイルは ~20ms (対 2~5 秒の JVM) で起動し、~30MB RSS (対 150~300MB JVM) を使用します。これは、Kubernetes 上のマイクロサービスにとって大きな利点です。迅速に拡張し、リソースを節約します。
GraalVM ネイティブ イメージ — 仕組み
┌────────────────────────────┐
│ Java Application │
│ + All Dependencies │
│ + Quarkus Extensions │
└──────────┬─────────────────┘
│
▼
┌────────────────────────────┐
│ GraalVM Native Compiler │
│ (Ahead-of-Time) │
│ │
│ • Static Analysis │
│ • Dead Code Elimination │
│ • Heap Snapshotting │
│ • Substrate VM │
└──────────┬─────────────────┘
│
▼
┌────────────────────────────┐
│ Native Executable │
│ ~50MB standalone binary │
│ Startup: ~20ms │
│ RSS: ~30MB │
└────────────────────────────┘
ネイティブ イメージを構築する
GraalVM をインストールする
# SDKMAN (recommended)
sdk install java 21.0.6-graalce
sdk use java 21.0.6-graalce
# Verify
java -version
# openjdk version "21.0.6" 2025-01-21
# GraalVM CE 21.0.6+7.1
Maven を使用してビルドする
# Build native executable
./mvnw package -Dnative
# Output: target/product-service-1.0.0-runner
# (standalone binary, ~50MB)
# Run
./target/product-service-1.0.0-runner
# Quarkus started in 0.019s
コンテナ内で構築 (ローカルの GraalVM は必要ありません)
# Dùng builder image — không cần cài GraalVM local
./mvnw package -Dnative \
-Dquarkus.native.container-build=true \
-Dquarkus.native.builder-image=\
quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21
マルチステージ Dockerfile
ネイティブ イメージ Dockerfile
# Stage 1: Build native executable
FROM quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21 \
AS build
COPY --chown=quarkus:quarkus mvnw /code/mvnw
COPY --chown=quarkus:quarkus .mvn /code/.mvn
COPY --chown=quarkus:quarkus pom.xml /code/
COPY --chown=quarkus:quarkus src /code/src
USER quarkus
WORKDIR /code
RUN ./mvnw package -Dnative \
-DskipTests \
-Dquarkus.native.additional-build-args=\
--initialize-at-build-time
# Stage 2: Runtime image
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
COPY --from=build /code/target/*-runner /work/application
RUN chmod 775 /work /work/application \
&& chown -R 1001 /work \
&& chmod -R "g+rwX" /work
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
JVM Dockerfile (代替)
FROM eclipse-temurin:21-jre-alpine
ENV LANGUAGE='en_US:en'
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 \
-Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT ["java", ${JAVA_OPTS_APPEND}, "-jar", \
"/deployments/quarkus-run.jar"]
JVM とネイティブの比較
| メトリクス | JVM モード | ネイティブモード |
|---|---|---|
| 起動時間 | 2-5秒 | 0.015~0.05秒 |
| メモリ (RSS) | 150~300MB | 30~80MB |
| Docker イメージ | ~300MB | ~80MB |
| ビルド時間 | 10~30代 | 3~10分 |
| ピーク スループット | より高い (JIT) | ~10 ~ 20% 低い |
| ウォームアップ | ウォームアップが必要 | 必要ありません |
| 反省 | 自由 | 設定が必要 |
ネイティブを使用するのはどのような場合ですか?
- ✅ サーバーレス / 機能 (コールドスタートが重要)
- ✅ マイクロサービスは定期的にスケールアップ/スケールダウンします
- ✅ 高いコンテナ密度 (RAM を節約)
- ✅ CLI ツール
- ❌ 長時間実行されるサービスには最大のスループットが必要 → JVM モードの方が優れています
リフレクションとリソースの構成
// Quarkus tự phát hiện reflection qua extensions
// Nhưng nếu dùng dynamic reflection thủ công:
@RegisterForReflection
public class ProductDTO {
public Long id;
public String name;
public BigDecimal price;
}
// Đăng ký class từ library bên ngoài:
@RegisterForReflection(targets = {
com.external.lib.SomeClass.class,
com.external.lib.AnotherClass.class
})
public class ReflectionConfig {}
ネイティブの application.properties
# Resources cần include trong native image
quarkus.native.resources.includes=\
db/migration/*.sql,\
META-INF/resources/**,\
templates/**
# Native image build settings
quarkus.native.additional-build-args=\
-H:+ReportExceptionStackTraces,\
--initialize-at-build-time
ネイティブテスト
// Integration Test chạy trên native binary
@QuarkusIntegrationTest
class ProductResourceIT {
@Test
void testListProductsNative() {
given()
.when().get("/api/v1/products")
.then()
.statusCode(200);
}
@Test
void testHealthCheck() {
given()
.when().get("/q/health")
.then()
.statusCode(200)
.body("status", equalTo("UP"));
}
}
# Chạy native integration tests
./mvnw verify -Dnative
# Quarkus sẽ:
# 1. Build native executable
# 2. Start native binary
# 3. Run *IT.java tests against it
# 4. Shut down
Docker Compose — すべてのサービス
# docker-compose.prod.yml
services:
product-service:
build:
context: ./product-service
dockerfile: src/main/docker/Dockerfile.native
ports:
- "8081:8080"
environment:
QUARKUS_DATASOURCE_JDBC_URL: >-
jdbc:postgresql://postgres:5432/productdb
QUARKUS_DATASOURCE_USERNAME: product
QUARKUS_DATASOURCE_PASSWORD: ${DB_PASSWORD}
QUARKUS_OIDC_AUTH_SERVER_URL: >-
http://keycloak:8080/realms/ecommerce
depends_on:
postgres:
condition: service_healthy
keycloak:
condition: service_healthy
order-service:
build:
context: ./order-service
dockerfile: src/main/docker/Dockerfile.native
ports:
- "8082:8080"
environment:
QUARKUS_DATASOURCE_JDBC_URL: >-
jdbc:postgresql://postgres:5432/orderdb
QUARKUS_DATASOURCE_USERNAME: order
QUARKUS_DATASOURCE_PASSWORD: ${DB_PASSWORD}
QUARKUS_REST_CLIENT_PRODUCT_SERVICE_URL: >-
http://product-service:8080
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
depends_on:
postgres:
condition: service_healthy
kafka:
condition: service_healthy
payment-service:
build:
context: ./payment-service
dockerfile: src/main/docker/Dockerfile.native
ports:
- "8083:8080"
environment:
QUARKUS_DATASOURCE_JDBC_URL: >-
jdbc:postgresql://postgres:5432/paymentdb
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
notification-service:
build:
context: ./notification-service
dockerfile: src/main/docker/Dockerfile.native
ports:
- "8084:8080"
environment:
KAFKA_BOOTSTRAP_SERVERS: kafka:9092
QUARKUS_MAILER_HOST: mailhog
QUARKUS_MAILER_PORT: 1025
postgres:
image: postgres:17
environment:
POSTGRES_MULTIPLE_DATABASES: >-
productdb,orderdb,paymentdb
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
- ./init-multiple-dbs.sh:\
/docker-entrypoint-initdb.d/init.sh
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 5
keycloak:
image: quay.io/keycloak/keycloak:26.2
command: start-dev --import-realm
ports:
- "8180:8080"
environment:
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: ${DB_PASSWORD}
KEYCLOAK_ADMIN: admin
KEYCLOAK_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD}
volumes:
- ./keycloak/realms:/opt/keycloak/data/import
healthcheck:
test: ["CMD-SHELL",
"exec 3<>/dev/tcp/localhost/8080"]
interval: 10s
timeout: 5s
retries: 10
kafka:
image: confluentinc/cp-kafka:7.8.0
ports:
- "9092:9092"
environment:
KAFKA_NODE_ID: 1
KAFKA_PROCESS_ROLES: controller,broker
KAFKA_LISTENERS: >-
PLAINTEXT://0.0.0.0:9092,CONTROLLER://0.0.0.0:9093
KAFKA_CONTROLLER_QUORUM_VOTERS: 1@kafka:9093
KAFKA_CONTROLLER_LISTENER_NAMES: CONTROLLER
CLUSTER_ID: "MkU3OEVBNTcwNTJENDM2Qk"
healthcheck:
test: ["CMD", "kafka-broker-api-versions",
"--bootstrap-server", "localhost:9092"]
interval: 10s
timeout: 10s
retries: 5
nginx:
image: nginx:alpine
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- product-service
- order-service
- payment-service
jaeger:
image: jaegertracing/jaeger:2
ports:
- "16686:16686" # UI
- "4317:4317" # OTLP gRPC
prometheus:
image: prom/prometheus:v3.2.1
ports:
- "9090:9090"
volumes:
- ./prometheus/prometheus.yml:\
/etc/prometheus/prometheus.yml:ro
grafana:
image: grafana/grafana:11.5.2
ports:
- "3000:3000"
volumes:
- ./grafana/dashboards:/var/lib/grafana/dashboards
volumes:
pgdata:
演習
- 製品サービス用のネイティブ実行可能ファイルをビルドします。
./mvnw package -Dnative - 起動時間とメモリの比較: JVM とネイティブ
- ネイティブ イメージ用の多段階 Dockerfile を作成する
- 走る
@QuarkusIntegrationTestネイティブバイナリ上で - Docker Compose はシステム全体 (5 つのサービス + インフラ) を起動します。
概要
- GraalVM ネイティブ イメージ: 起動 ~20ms、RSS ~30MB — マイクロサービスに最適
- マルチステージ Dockerfile: ビルドステージ (Mandrel) → ランタイムステージ (quarkus-micro-image ~80MB)
@RegisterForReflection動的リフレクションを使用するクラスの場合@QuarkusIntegrationTest実際のネイティブバイナリでテスト済み- 開発/ステージング用の Docker Compose、運用用の Kubernetes (レッスン 22)
次の記事: Kubernetes デプロイメントと CI/CD — Kubernetes クラスターへのデプロイメント。