Kubernetes와 Spring Boot 3.0 Native Image의 만남
Spring Boot 3.0 Native Image로 Kubernetes 파드 준비 시간을 50초에서 2초로 줄였다.
넷마블 QA실의 크래시리포트는 게임 실행 중 발생한 크래시 데이터를 수집·통계화해 제공하는 내부 시스템이다. 엣지 서버 클러스터는 Kubernetes(K8S) 기반이며, HPA는 CPU 자원 요청률을 기준으로 파드와 노드를 늘리도록 구성했다.
문제는 트래픽 급증 시 신규 파드 추가에 최소 1분 이상 걸렸다는 점이다. 클라이언트 연결 대기 시간이 10~20초 수준이어서, 파드가 준비되는 동안 데이터 유실이 발생할 수 있었고, 결국 15초 이내로 노드와 파드를 증설할 수 있는 환경이 필요해졌다.
해결책으로 Spring Boot 3.0과 GraalVM Native Image를 도입했다. Spring Boot 3.0은 Java 17을 최소 지원 버전으로 올리면서 Native Image 생성을 정식 지원했고, 기존 Spring Native 프로젝트의 기능을 흡수했다.
네이티브 이미지는 JVM 없이 실행할 수 있어 초기 기동이 빠르고 메모리 사용량도 적다. 대신 동적 기능을 미리 정의해야 하므로, AOT( Ahead Of Time ) 컴파일과 함께 hint file이 필요했고, spring-boot:process-aot로 proxy-config.json, reflect-config.json, resource-config.json, serialization-config.json을 생성했다. 자동으로 잡히지 않는 동적 요소는 RuntimeHintsRegistrar로 직접 등록했다.
빌드는 native-maven-plugin과 mvn -Pnative native:compile로 진행했고, Docker는 멀티 스테이지로 구성했다. 초반에는 GraalVM, musl, zlib, UPX를 조합해 빌드했고, 실행 이미지는 Alpine Linux로 맞췄다.
배포를 거치며 환경을 계속 다듬었다.
- 1차 배포: 빌드/실행 OS가 달라 CPU 사용률과 OOM 재시작이 잦았고, CPU를 800m에서 1200m로, 최소 파드를 15에서 20으로 조정했다.
- 2차 배포: 빌드와 실행 환경을 모두 Oracle Linux 9로 맞추자 OOM 재시작이 2~3회로 줄었다.
- 3차 배포:
spring-boot:process-aot를 다시 돌려 변경된 힌트 파일을 반영하고,tomcat-embed-programmatic을 적용했다. Serial GC 특성 때문에 메모리를 더 넉넉히 잡았다. - 4차 배포:
--libc:musl을 추가해 Alpine Linux 실행으로 전환했고, CPU와 메모리 설정도 JAR 시절 수준으로 복원했다. - 5차 배포: UPX를 적용해 실행 파일 크기를 더 줄였고, 최종 도커 이미지는 69.81MB까지 감소했다. 또한 readinessProbe와 livenessProbe를 활용해 준비 완료 후 더 빠르게 트래픽을 받도록 구성했다.
성능 면에서도 차이가 분명했다. 실행 준비 시간은 기존 50초에서 2초로 줄었고, 신규 파드용 도커 이미지 크기는 기존 300MB 수준에서 70MB 수준으로 축소됐다. 초기 응답과 파드 증설 속도가 중요한 크래시리포트 같은 환경에서는 Native Image의 효과가 매우 컸다.
다만 장기 반복 테스트에서는 JAR 기반 서비스도 시간이 지나며 성능이 안정화됐다. 결국 핵심은 파드 증설이 빈번하고, 준비 시간이 짧아야 하며, 초기 응답 지연이 곧 데이터 유실로 이어지는 환경인지다. 그런 조건이라면 Kubernetes + Spring Boot 3.0 Native Image 조합은 충분히 강력한 선택지가 된다.
이 요약은 원문 이해를 돕기 위한 큐레이션입니다. 저작권은 원저작자에게 있으며, 정확한 내용과 맥락은 원문을 확인하세요.
요약 오류, 출처 표기 문제, 삭제 요청은 문의 · 건의로 알려주세요.