코드를 작성하고, 빌드하고, 이미지를 만들고, 매니페스트를 작성하고, 배포한다. 이 과정 하나하나가 다 귀찮은데, 프레임워크가 이걸 전부 대신 해줄 수 있다면?

Quarkus가 제공하는 Dockerfile

Quarkus 프로젝트를 생성하면 src/main/docker/ 디렉토리에 네 가지 Dockerfile이 자동으로 만들어집니다.

PLAINTEXT
src/main/docker/
├── Dockerfile.jvm              # JVM 모드 실행용
├── Dockerfile.legacy-jar       # uber-jar 방식 실행용
├── Dockerfile.native           # 네이티브 바이너리 실행용 (UBI 기반)
└── Dockerfile.native-micro     # 네이티브 바이너리 실행용 (최소 이미지)

각각의 용도가 다릅니다.

  • Dockerfile.jvm: 가장 범용적. JVM 위에서 실행하며, fast-jar 포맷을 사용
  • Dockerfile.legacy-jar: 전통적인 uber-jar 방식. 단일 JAR 파일 하나로 실행
  • Dockerfile.native: 네이티브 바이너리를 UBI(Universal Base Image) 위에서 실행
  • Dockerfile.native-micro: quay.io/quarkus/quarkus-micro-image 기반으로 최소 크기
BASH
# JVM 모드 이미지 빌드
docker build -f src/main/docker/Dockerfile.jvm -t my-app:jvm .

# 네이티브 이미지 빌드 (네이티브 바이너리가 먼저 빌드되어 있어야 함)
./mvnw package -Dnative -Dquarkus.native.container-build=true
docker build -f src/main/docker/Dockerfile.native-micro -t my-app:native .

이미지 크기를 비교해보면 차이가 확연합니다.

Dockerfile베이스 이미지결과 이미지 크기
Dockerfile.jvmubi8/openjdk-21~400MB
Dockerfile.nativeubi-minimal~100MB
Dockerfile.native-microquarkus-micro-image~50MB

Jib 확장 — Dockerfile 없이 이미지 빌드

Dockerfile을 관리하기 싫다면 Jib을 쓸 수 있습니다. Google이 만든 Jib은 Dockerfile 없이 자바 애플리케이션을 컨테이너 이미지로 만들어주는 도구입니다.

BASH
# 확장 추가
./mvnw quarkus:add-extension -Dextensions="container-image-jib"
PROPERTIES
# application.properties
quarkus.container-image.build=true
quarkus.container-image.group=myregistry
quarkus.container-image.name=my-app
quarkus.container-image.tag=1.0.0
BASH
# 빌드하면 이미지가 자동으로 생성됨
./mvnw package -Dquarkus.container-image.build=true

Jib의 장점은 Docker 데몬 없이도 이미지를 빌드할 수 있다는 것입니다. 레지스트리에 직접 푸시하는 것도 가능합니다.

BASH
# 빌드 + 레지스트리 푸시까지 한 번에
./mvnw package \
  -Dquarkus.container-image.build=true \
  -Dquarkus.container-image.push=true \
  -Dquarkus.container-image.registry=ghcr.io

Jib 외에도 container-image-docker(Docker CLI 사용)와 container-image-buildpack(Cloud Native Buildpacks) 확장이 있습니다. 각각의 특성을 정리하면 이렇습니다.

확장Docker 데몬 필요Dockerfile 필요네이티브 지원
container-image-jib아니오아니오JVM만
container-image-docker
container-image-buildpack아니오

Kubernetes Extension — YAML 자동 생성

공부하다 보니 Quarkus의 Kubernetes 확장이 정말 편리했습니다. Kubernetes 매니페스트(YAML)를 손으로 작성하는 대신, application.properties 설정만으로 자동 생성합니다.

BASH
# 확장 추가
./mvnw quarkus:add-extension -Dextensions="kubernetes"

빌드하면 target/kubernetes/ 디렉토리에 매니페스트가 생성됩니다.

BASH
./mvnw package

ls target/kubernetes/
# kubernetes.json
# kubernetes.yml

기본 설정

PROPERTIES
# application.properties

# 기본 정보
quarkus.kubernetes.namespace=my-namespace
quarkus.kubernetes.replicas=3

# 리소스 제한
quarkus.kubernetes.resources.requests.memory=256Mi
quarkus.kubernetes.resources.requests.cpu=250m
quarkus.kubernetes.resources.limits.memory=512Mi
quarkus.kubernetes.resources.limits.cpu=500m

# 환경 변수
quarkus.kubernetes.env.vars.DATABASE_HOST=postgres-service
quarkus.kubernetes.env.vars.DATABASE_PORT=5432

# Secret에서 환경 변수 주입
quarkus.kubernetes.env.secrets=my-app-secret

# ConfigMap에서 환경 변수 주입
quarkus.kubernetes.env.configmaps=my-app-config

생성되는 YAML 예시

위 설정으로 빌드하면 대략 이런 형태의 매니페스트가 자동 생성됩니다.

YAML
apiVersion: apps/v1
kind: Deployment
metadata:
  name: my-app
  namespace: my-namespace
spec:
  replicas: 3
  selector:
    matchLabels:
      app.kubernetes.io/name: my-app
  template:
    spec:
      containers:
        - name: my-app
          image: myregistry/my-app:1.0.0
          resources:
            requests:
              memory: 256Mi
              cpu: 250m
            limits:
              memory: 512Mi
              cpu: 500m
          env:
            - name: DATABASE_HOST
              value: postgres-service
          envFrom:
            - secretRef:
                name: my-app-secret
            - configMapRef:
                name: my-app-config

직접 YAML을 작성하는 것보다 오타가 줄고, 설정 변경도 properties 파일 하나에서 관리할 수 있어서 훨씬 깔끔합니다.


Health Check와 Kubernetes Probe 자동 매핑

Quarkus의 SmallRye Health 확장과 Kubernetes 확장을 함께 사용하면, Health Check 엔드포인트가 Kubernetes Probe로 자동 매핑됩니다.

BASH
./mvnw quarkus:add-extension -Dextensions="smallrye-health,kubernetes"

이것만으로 생성되는 매니페스트에 Probe가 자동으로 포함됩니다.

YAML
# 자동 생성되는 Probe 설정
livenessProbe:
  httpGet:
    path: /q/health/live
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
readinessProbe:
  httpGet:
    path: /q/health/ready
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10
startupProbe:
  httpGet:
    path: /q/health/started
    port: 8080
  initialDelaySeconds: 5
  periodSeconds: 10

Probe 설정을 커스터마이징할 수도 있습니다.

PROPERTIES
quarkus.kubernetes.liveness-probe.initial-delay=10s
quarkus.kubernetes.liveness-probe.period=30s
quarkus.kubernetes.readiness-probe.initial-delay=5s
quarkus.kubernetes.readiness-probe.failure-threshold=5

빠른 시작이 Kubernetes에서 중요한 이유

Quarkus가 시작 속도에 집착하는 이유가 Kubernetes 환경에서 명확해집니다.

HPA 스케일아웃

HPA(Horizontal Pod Autoscaler)가 트래픽 증가를 감지하고 새 Pod를 띄웠을 때, 그 Pod가 실제로 요청을 처리할 수 있기까지의 시간이 중요합니다.

  • Spring Boot 앱: Pod 시작 → JVM 부팅 → 애플리케이션 초기화 → Ready (10~30초)
  • Quarkus JVM 앱: Pod 시작 → JVM 부팅 → 애플리케이션 초기화 → Ready (2~5초)
  • Quarkus Native 앱: Pod 시작 → 바이너리 실행 → Ready (0.05~0.5초)

트래픽이 갑자기 몰리는 상황에서 30초 동안 새 Pod가 준비되지 않으면, 기존 Pod에 부하가 집중되어 장애로 이어질 수 있습니다.

롤링 업데이트

배포할 때 롤링 업데이트 전략을 쓰면, 새 버전의 Pod가 Ready 상태가 되어야 이전 버전의 Pod를 종료합니다. 시작이 빠를수록 배포 시간이 짧아지고, 그만큼 불안정한 과도기가 줄어듭니다.

Spot Instance / Preemptible VM

비용 절감을 위해 Spot Instance를 사용하면 VM이 갑자기 종료될 수 있습니다. 새 인스턴스에서 Pod가 빠르게 재시작되어야 서비스 중단이 최소화됩니다.


OpenShift Extension

Red Hat OpenShift를 사용한다면 별도의 확장이 있습니다.

BASH
./mvnw quarkus:add-extension -Dextensions="openshift"
PROPERTIES
quarkus.openshift.route.expose=true
quarkus.openshift.deployment-kind=DeploymentConfig

Kubernetes 확장과 같은 설정 방식이지만, OpenShift 고유의 리소스(Route, DeploymentConfig, BuildConfig)를 자동 생성합니다.


Helm Chart 지원

Kubernetes 매니페스트를 Helm Chart로 패키징하고 싶다면 Helm 확장을 사용합니다.

BASH
./mvnw quarkus:add-extension -Dextensions="helm"

빌드하면 target/helm/ 디렉토리에 Helm Chart가 생성됩니다.

BASH
./mvnw package

ls target/helm/kubernetes/my-app/
# Chart.yaml
# values.yaml
# templates/
PROPERTIES
# Helm 관련 설정
quarkus.helm.name=my-app
quarkus.helm.version=1.0.0
quarkus.helm.description=My Quarkus Application

# values.yaml에 노출할 값
quarkus.helm.values.replicas.property=replicas
quarkus.helm.values.replicas.value=3

생성된 Chart는 바로 helm install로 배포할 수 있습니다.

BASH
helm install my-app target/helm/kubernetes/my-app

빌드에서 배포까지 한 번에

가장 인상적인 부분은 빌드부터 배포까지 명령어 하나로 처리할 수 있다는 점입니다.

BASH
# 빌드 → 이미지 생성 → K8s 배포까지 한 번에
./mvnw package \
  -Dquarkus.container-image.build=true \
  -Dquarkus.kubernetes.deploy=true

이 명령어 하나가 다음 과정을 순서대로 수행합니다.

  1. 애플리케이션 빌드 (JAR 또는 네이티브)
  2. 컨테이너 이미지 빌드
  3. kubernetes.yml 생성
  4. kubectl apply 실행

네이티브 이미지로 배포하고 싶다면 -Dnative 플래그만 추가하면 됩니다.

BASH
./mvnw package -Dnative \
  -Dquarkus.native.container-build=true \
  -Dquarkus.container-image.build=true \
  -Dquarkus.kubernetes.deploy=true

Distroless 이미지로 보안 강화

프로덕션에서는 이미지 보안도 중요합니다. Distroless 이미지는 애플리케이션 실행에 필요한 최소한의 런타임만 포함하고, 쉘이나 패키지 매니저 같은 불필요한 도구를 제거한 이미지입니다.

DOCKERFILE
# 네이티브 바이너리를 distroless 이미지에 넣기
FROM quay.io/quarkus/quarkus-micro-image:2.0 AS base
WORKDIR /work/
COPY target/*-runner /work/application
RUN chmod 775 /work
EXPOSE 8080
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]

Distroless 이미지를 사용하면 다음과 같은 이점이 있습니다.

  • **공격 표면 감소 **: 쉘이 없으므로 쉘 인젝션 공격 불가
  • **CVE 감소 **: 패키지가 적으므로 보안 취약점도 적음
  • ** 이미지 크기 감소 **: 불필요한 파일이 없어서 경량
  • ** 컴플라이언스 **: 보안 감사에서 유리

Quarkus Native + Distroless 조합은 이미지 크기를 50MB 이하로 줄일 수 있습니다. 같은 기능의 Spring Boot JVM 이미지가 400MB 이상인 것과 비교하면 상당한 차이입니다.


정리

Quarkus는 컨테이너와 Kubernetes를 "나중에 연동하는 것"이 아니라 ** 처음부터 프레임워크 안에 내장 **했습니다. Dockerfile 생성, 이미지 빌드, 매니페스트 생성, 배포까지 하나의 빌드 파이프라인으로 묶어줍니다.

핵심을 정리하면 이렇습니다.

  • 프로젝트 생성 시 4가지 Dockerfile이 자동 제공되고, Jib으로 Dockerfile 없이 빌드도 가능
  • Kubernetes Extension으로 매니페스트를 application.properties에서 관리
  • Health Check → Probe 자동 매핑으로 운영 설정이 간소화
  • ./mvnw package -Dquarkus.kubernetes.deploy=true 한 줄로 빌드부터 배포까지 가능
  • Quarkus의 빠른 시작 속도는 K8s 환경에서 HPA, 롤링 업데이트, Spot Instance 대응에 실질적인 이점을 줌
댓글 로딩 중...