Назад к блогу
Java 13 мин чтения

Quarkus Native для Kubernetes: быстрый старт и минимальные образы

Как собрать GraalVM native-образ Quarkus, ужать Docker-слой до десятков МБ и задеплоить в K8s с коротким cold start и скромными requests/limits.

QuarkusGraalVMNativeKubernetesDocker
Quarkus Native для Kubernetes: быстрый старт и минимальные образы

Зачем Quarkus Native в Kubernetes

Классический JVM-сервис на Spring Boot / Quarkus JVM часто стартует секунды и просит сотни мегабайт heap. В Kubernetes это бьёт по scale-out, rolling update и scale-to-zero: пока Pod не проходит readiness — трафик не идёт.

Quarkus + GraalVM Native Image даёт другой профиль: cold start часто ~50–300 ms для небольшого REST-сервиса, RSS порядка 30–80 MB, контейнер — десятки МБ вместо сотен. Для K8s это значит быстрее probes, ниже requests/limits и плотнее упаковка на ноде.

Реальный каркас сервиса

mvn io.quarkus.platform:quarkus-maven-plugin:3.17.5:create \
  -DprojectGroupId=by.example \
  -DprojectArtifactId=hello-native \
  -Dextensions="rest,rest-jackson,hibernate-validator,smallrye-health,micrometer-registry-prometheus"
cd hello-native
// GreetingResource.java
@Path("/api/hello")
public class GreetingResource {
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  public Map<String, String> hello(@QueryParam("name") @DefaultValue("world") String name) {
    return Map.of("message", "Hello, " + name);
  }
}
# application.properties
quarkus.application.name=hello-native
quarkus.http.port=8080
quarkus.native.container-build=true
quarkus.native.builder-image=quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21
quarkus.package.jar.enabled=false

Сборка native (два рабочих способа)

1. В контейнере (удобно в CI, без локального GraalVM):

./mvnw package -Dnative -DskipTests
# артефакт: target/hello-native-1.0.0-SNAPSHOT-runner
ls -lh target/*-runner

2. Локальный Mandrel/GraalVM 21:

export GRAALVM_HOME=/opt/mandrel-java21
./mvnw package -Dnative -Dquarkus.native.container-build=false

Типичные цифры на простом REST (ориентир, зависит от CPU CI): build 3–8 минут, binary ~40–70 MB, startup <0.2 s на тёплом CPU.

Минимальный Docker-образ

Официальный подход Quarkus — multi-stage: builder собирает native, runtime — micro/UBI без JDK.

# src/main/docker/Dockerfile.native-micro
FROM quay.io/quarkus/ubi9-quarkus-micro-image:2.0
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work /work/application \
  && chown 1001 /work \
  && chown 1001 /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
docker build -f src/main/docker/Dockerfile.native-micro -t registry.example/hello-native:1.0.0 .
docker images registry.example/hello-native:1.0.0
# часто ~60–90 MB вместо 250–400+ MB JVM-образа

Ещё жёстче — distroless/static, если бинарь собран с нужными libc-флагами; для старта берите ubi9-quarkus-micro-image — меньше сюрпризов с DNS/certs в K8s.

Деплой в Kubernetes

apiVersion: apps/v1
kind: Deployment
metadata:
  name: hello-native
spec:
  replicas: 2
  selector:
    matchLabels:
      app: hello-native
  template:
    metadata:
      labels:
        app: hello-native
      annotations:
        prometheus.io/scrape: "true"
        prometheus.io/path: /q/metrics
        prometheus.io/port: "8080"
    spec:
      containers:
        - name: app
          image: registry.example/hello-native:1.0.0
          ports:
            - containerPort: 8080
          env:
            - name: QUARKUS_HTTP_PORT
              value: "8080"
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 500m
              memory: 128Mi
          startupProbe:
            httpGet:
              path: /q/health/started
              port: 8080
            failureThreshold: 10
            periodSeconds: 1
          readinessProbe:
            httpGet:
              path: /q/health/ready
              port: 8080
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /q/health/live
              port: 8080
            periodSeconds: 10
---
apiVersion: v1
kind: Service
metadata:
  name: hello-native
spec:
  selector:
    app: hello-native
  ports:
    - port: 80
      targetPort: 8080

Из-за быстрого старта startupProbe можно держать коротким — Pod быстрее станет Ready при rolling update и HPA.

Сравнение «на пальцах» (типичный micro-REST)

  • Spring Boot JVM: образ ~250–400 MB, старт 2–8 s, RSS 200–400 MB.
  • Quarkus JVM: образ ~150–250 MB, старт 0.5–2 s, RSS 100–200 MB.
  • Quarkus Native: образ ~60–100 MB, старт <0.3 s, RSS 40–80 MB.

Именно native выигрывает на burst-scale и serverless-подобных сценариях (Knative, KEDA, частые рестарты).

Подводные камни native

  • Рефлексия/JNI/динамические прокси — нужны @RegisterForReflection или extension-поддержка.
  • Дольше CI: кэшируйте Mandrel builder layer в registry.
  • Не всё из экосистемы Spring «просто переносится» — берите Quarkus extensions (REST, Hibernate ORM Panache, Kafka, Redis).
  • Отладка сложнее JVM: держите JVM-профиль для локальной разработки (quarkusDev), native — для staging/prod-образа.
# локально быстро
./mvnw quarkus:dev

# в CI — native image
./mvnw package -Dnative -DskipTests

CI-фрагмент (GitLab)

build-native:
  image: quay.io/quarkus/ubi-quarkus-mandrel-builder-image:jdk-21
  stage: build
  script:
    - ./mvnw -B package -Dnative -DskipTests
    - docker build -f src/main/docker/Dockerfile.native-micro
        -t $CI_REGISTRY_IMAGE/hello-native:$CI_COMMIT_SHORT_SHA .
    - docker push $CI_REGISTRY_IMAGE/hello-native:$CI_COMMIT_SHORT_SHA
  artifacts:
    paths:
      - target/*-runner

Вывод

Quarkus Native — практичный способ получить для Kubernetes быстрый cold start и маленькие образы без отказа от Java. Соберите runner через Mandrel, упакуйте в micro-UBI, выставьте скромные resources и короткие probes. Для API-gateway, BFF и worker'ов с редким трафиком выигрыш в плотности и скорости выката обычно окупает более длинную native-сборку в CI.

Алексей Прыбытков

Senior Software Developer

© 2026 Все права защищены