컨테이너와 Kubernetes 배포 — Docker, Jib, Kubernetes Extension
코드를 작성하고, 빌드하고, 이미지를 만들고, 매니페스트를 작성하고, 배포한다. 이 과정 하나하나가 다 귀찮은데, 프레임워크가 이걸 전부 대신 해줄 수 있다면?
Quarkus가 제공하는 Dockerfile
Quarkus 프로젝트를 생성하면 src/main/docker/ 디렉토리에 네 가지 Dockerfile이 자동으로 만들어집니다.
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 기반으로 최소 크기
# 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.jvm | ubi8/openjdk-21 | ~400MB |
| Dockerfile.native | ubi-minimal | ~100MB |
| Dockerfile.native-micro | quarkus-micro-image | ~50MB |
Jib 확장 — Dockerfile 없이 이미지 빌드
Dockerfile을 관리하기 싫다면 Jib을 쓸 수 있습니다. Google이 만든 Jib은 Dockerfile 없이 자바 애플리케이션을 컨테이너 이미지로 만들어주는 도구입니다.
# 확장 추가
./mvnw quarkus:add-extension -Dextensions="container-image-jib"
# 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
# 빌드하면 이미지가 자동으로 생성됨
./mvnw package -Dquarkus.container-image.build=true
Jib의 장점은 Docker 데몬 없이도 이미지를 빌드할 수 있다는 것입니다. 레지스트리에 직접 푸시하는 것도 가능합니다.
# 빌드 + 레지스트리 푸시까지 한 번에
./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 설정만으로 자동 생성합니다.
# 확장 추가
./mvnw quarkus:add-extension -Dextensions="kubernetes"
빌드하면 target/kubernetes/ 디렉토리에 매니페스트가 생성됩니다.
./mvnw package
ls target/kubernetes/
# kubernetes.json
# kubernetes.yml
기본 설정
# 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 예시
위 설정으로 빌드하면 대략 이런 형태의 매니페스트가 자동 생성됩니다.
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로 자동 매핑됩니다.
./mvnw quarkus:add-extension -Dextensions="smallrye-health,kubernetes"
이것만으로 생성되는 매니페스트에 Probe가 자동으로 포함됩니다.
# 자동 생성되는 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 설정을 커스터마이징할 수도 있습니다.
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를 사용한다면 별도의 확장이 있습니다.
./mvnw quarkus:add-extension -Dextensions="openshift"
quarkus.openshift.route.expose=true
quarkus.openshift.deployment-kind=DeploymentConfig
Kubernetes 확장과 같은 설정 방식이지만, OpenShift 고유의 리소스(Route, DeploymentConfig, BuildConfig)를 자동 생성합니다.
Helm Chart 지원
Kubernetes 매니페스트를 Helm Chart로 패키징하고 싶다면 Helm 확장을 사용합니다.
./mvnw quarkus:add-extension -Dextensions="helm"
빌드하면 target/helm/ 디렉토리에 Helm Chart가 생성됩니다.
./mvnw package
ls target/helm/kubernetes/my-app/
# Chart.yaml
# values.yaml
# templates/
# 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로 배포할 수 있습니다.
helm install my-app target/helm/kubernetes/my-app
빌드에서 배포까지 한 번에
가장 인상적인 부분은 빌드부터 배포까지 명령어 하나로 처리할 수 있다는 점입니다.
# 빌드 → 이미지 생성 → K8s 배포까지 한 번에
./mvnw package \
-Dquarkus.container-image.build=true \
-Dquarkus.kubernetes.deploy=true
이 명령어 하나가 다음 과정을 순서대로 수행합니다.
- 애플리케이션 빌드 (JAR 또는 네이티브)
- 컨테이너 이미지 빌드
- kubernetes.yml 생성
kubectl apply실행
네이티브 이미지로 배포하고 싶다면 -Dnative 플래그만 추가하면 됩니다.
./mvnw package -Dnative \
-Dquarkus.native.container-build=true \
-Dquarkus.container-image.build=true \
-Dquarkus.kubernetes.deploy=true
Distroless 이미지로 보안 강화
프로덕션에서는 이미지 보안도 중요합니다. Distroless 이미지는 애플리케이션 실행에 필요한 최소한의 런타임만 포함하고, 쉘이나 패키지 매니저 같은 불필요한 도구를 제거한 이미지입니다.
# 네이티브 바이너리를 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 대응에 실질적인 이점을 줌