4/15/2021

Serverless vs. Container 선택 가이드

이번 글에서는 서버리스와 컨테이너에 대해서 설명하고자 한다.

일을 하다보니, VM을 이용할 경우 유후 시간이 많을 때 비용이 최적화되지 못하다는 느낌이 많이 들었다.

서버리스는 서버 프로비저닝 및 유지 관리를 추상화하는 클라우드 아키텍처 모델이다.

FaaS(Function-as-a-Service)라 불리며, 필요에 따라 코드를 실행하는 개념이고 실행 후 종료된다.

일반적으로 FaaS와 Container를 많이 비교하곤 한다. FaaS와 Container는 몇 가지 공통점이 존재한다. 분산 시스템 및 대규모 애플리케이션 개발에 특화 되어 있고 관리상의 번거로움을 제거하고 애플리케이션과 비즈니스의 가치에 집중하고자 하는 목적이 있다.

Container

애플리케이션을 박스에 담아 어디서든 실행 할 수 있다면 좋지 않을까? 호스트 시스템이 무엇이든, 어디에 위치하든 상관없이…

이것이 컨테이너화의 아이디어이다. 필요한 모든 종속성이 사전에 설치되어 있는 컨테이너를 만들고 안에 애플리케이션을 넣고 컨테이너 런타임이 설치된 모든 곳에서 실행되게 한다.

이런 장점으로 인해 많은 기업이 컨테이너를 채택했고, 표준으로 정의되었다. 오늘날 대부분의 클라우드 제공사는 컨테이너화된 애플리케이션을 호스팅하는 방법을 제공하고 있다.

컨테이너의 장점

  • 제어 및 유연성
  • 공급 업체에 Lock-In 되지 않음
  • 쉬운 마이그레이션
  • 휴대성

컨테이너의 단점

  • 관리 작업 (e.g. 패치 적용)
  • 확장 속도가 느림
  • 유지비용
  • 처음 시작시 Learning Curve가 존재
  • 수동 개입이 필요

Serverless (FaaS)

서버리스의 기본 전제는 애플리케이션(모든 비즈니스 로직)이 기능과 이벤트로 구현된다는 점이다.

클라우드 제공사가 어떤 일이 있어도 기능을 사용할 수 있도록 보장한다.

2014년도에 서버리스 컴퓨팅이 처음 도입되었을 때, 워크로드가 상당히 제한된 상태였고 이미지 혹은 데이터 처리와 같은 소규모 작업에만 사용되었었다. 하지만 AWS가 Lambda의 이벤트 소스로 API Gateway를 도입한 후 모든 것이 바뀌게 되었다. 서버리스 컴퓨팅으로 구동되는 전체 API를 만드는 것이 가능하게 되었다. 점점 더 많은 서비스가 Lambda 제품과 통합되어 복잡한 상황의 애플리케이션을 구축할 수 있게 되었다.

서버리스의 장점

  • 관리 필요 없음
  • 사용시에만 비용 지불
  • 유휴 시간 비용 없음
  • 자동 확장
  • 시장 출시 시간 단축
  • 마이크로 서비스 특성 → 명확한 코드 분리
  • 관리 부담 대폭 감소

서버리스의 단점

  • 블랙 박스 환경
  • 공급 업체 종속
  • 콜드 스타트
  • 아주 복잡한 앱을 구축하기 어려울 수 있음

서버리스는 서버 또는 가상머신을 프로비저닝하고 관리할 필요가 없다.

AWS를 예를 들면, EC2서버를 프로비저닝 할 필요 없이 프로그래밍 언어로 작성된 코드를 실행할 수 있다. 내부적으로는 Lambda가 임시 마이크로 컨테이너를 생성하고 코드를 실행하고 결과를 반환하는 역할을 수행한다. 따라서 인프라를 관리할 필요가 없어지게 된다. 개발자는 코드를 배포하고 실행하기만 하면 된다.

흔히, 이런 코드들도 서버에서 실행되기 때문에 서버리스라는 용어가 잘못된 것이라고 주장하는 이들도 있지만, 개발자의 관점에서 서버 처리가 필요하지 않다는 점이 중요하다.

서버 기반 시스템은 주어진 기간 동안 사용한 리소스에 대해 요금을 청구한다. (e.g. EC2는 시간단위로 청구됨)

반면 서버리스는 실제 사용량을 청구한다. 사용하지 않을때는 비용이 청구되지 않는다.

AWS의 Lambda의 경우, 함수 코드 1백만건에 대해 0.2USD, 요청당 0.0000002USD만 청구하고 해당 기능이 동작하기 위해 필요한 메모리의 양에 따라 추가 요금이 부과된다.

이 가격 모델은 고정 비용을 가변 비용으로 전환하기에 스타트업 및 중소형 애플리케이션에 유리하다.

대규모 애플리케이션에서도 유리할 수 도 있다. 피크 타임때 기존 인프라에서는 프로비저닝을 미리 해야하기에 유휴 리소스에 대한 추가 비용이 생기기 때문이다.

예를 들어서, 미국내 뉴스 및 엔터테인먼트 서비스인 Bustle은 IT지출이 약 84% 감소했다. 유지 보수 직원이 자체 서버를 관리하는 비용 대비 저렴하기 때문이다.

서버리스 비용 계산은 여기를 참고하길 바란다.

“우리가 프로젝트를 진행할 때 어떤 기술을 선택해야 할까? 아마도 상황에 따라 다를 것이다.”

컨테이너를 선택해야 하는 경우

컨테이너를 사용하면 기본 운영 체제를 선택하고 설치된 프로그래밍 언어 및 런타임 버전을 완전히 제어할 수 있다. 따라서 특정 버전에 대한 요구사항이 있는 소프트웨어를 활용할 경우 유용하다.

대규모 컨테이너에서 서로 다른 소프트웨어 스택을 사용하여 컨테이너를 운영할 수 있다. 특히 오래된 레거시 시스템을 컨테이너 환경으로 마이그레이션을 해야 하는 경우 매우 유용하다.

하지만 이러한 유연성에는 운영 비용이 함께 제공된다. 컨테이너는 여전히 많은 유지 관리 및 설정이 필요하다.

최대한 이점을 취하려면 Monolithic 애플리케이션을 마이크로 서비스로 분리해야 하며, 이를 개별 컨테이너 그룹으로 이용해야 한다. 또한 정기적으로 업데이트를 통해 운영 체제를 최신 상태로 유지해야 하는 번거로운 작업을 수행해야 한다.

트래픽이 없는 경우, 완전한 종료가 불가능하다. 따라서 항상 런타임 비용이 지불해야 한다는 점을 고려해야 한다.

서버리스를 선택해야 하는 경우

서버리스는 트래픽을 자동으로 감지하고 즉시 처리해야 하는 경우 유용하다. 트래픽이 전혀 없으면 애플리케이션이 완전히 종료된다. 사용한 리소스에 대해서만 비용을 지불하게 된다.

서버리스로 개발하게되면 기본 인프라 관리에 대해 신경 쓸 필요가 없다. 최종 사용자에 대한 코드와 비즈니스 가치에만 집중하면 된다. 설정이나 프로비저닝 없이 코드를 더 빠르게 전달 할 수 있기에, 개발시간이 빨라질 수 있다.

그러나 현재 몇가지 제약이 존재한다. 프로그래밍 언어 및 런타임은 공급자가 지원하는 범위내에서 사용 가능하다.

또한, 인프라와 코드가 너무 분리되어 있으면 애플리케이션 스택의 모든 부분에 대해 추론하기가 어려워진다.

결론

유연성이 필요하거나 레거시 서비스를 마이그레이션 해야 하는 경우에는 컨테이너를 선택하고 개발 속도, 자동 확장 및 현저히 낮은 런타임 비용을 지불하고 싶으면 서버리스를 선택하는 것을 고려했으면 한다.

4/13/2021

GraalVM 소개

 

“Graal”이라는 단어는 “Grail”을 의미하는 고대 프랑스어에서 유래되었다. GraalVM의 “VM”은 “JVM”내부에서 실행된다는 사실에서 비롯되었다.

GraalVM은 Java 코드를 작성하고 실행할 수 있는 도구이다. Oracle에서 만든 JVM(Java Virtual Machine) 및 JDK(Java Development Kit)이고 애플리케이션의 성능과 효율성을 개선하는 목적으로 나온 고성능 런타임이다.

GraalVM의 목표는 더 빠르고 유지하기 쉬운 컴파일러 작성, JVM에서 실행되는 언어의 성능 향상, 애플리케이션 시작 시간 단축, Java 에코 시스테에 다국어 지원 통합이다.

GraalVM은 JDK에 최적화 컴파일러를 추가하여 성능 최적화와 다중 언어 애플리케이션에 대한 상호 운용성을 제공한다. Java코드 지원 외에도 Scala, Kotlin, Groovy, Clojure, R, Python, Javascript, Ruby등을 추가로 지원한다. 기본적으로 GraalVM을 사용하면 개발자가 단일 애플리케이션에서 여러 언어 및 라이브러리를 효율적으로 실행할 수 있다.

GraalVM은 많은 것을 제공한다.

Graal?


Graal은 오라클 연구소에서 진행하는 프로젝트이다. 2012년부터 개발팀은 GraalVM에 대한 60개 이상의 논문을 발표했다. 이 프로젝트는 매우 오래 지속된 프로젝트이고 성공적으로 보고 있다.

위의 그림에서 Truffle은 GraalVM의 다국어를 전담한다. Javascript, Ruby, R 및 모든 LLVM 언어의 영역이다. LLVM 컴파일러는 C코드를 LLVM 비트 코드로 변환하여 GraalVM에서 실행되게 만든다.

GraalVM의 구성 요소

GraalVM을 구성하는 세 가지 구성 요소는 고성능 최적화 Just-In-TIme 컴파일러, 네이티브 실행 파일을 빌드하기 위한 Ahead-of-Time 컴파일러, 다국어 지원이다.

GraalVM 컴파일러 (Just-In-Time 컴파일러)

  • Ahead-of-Time 컴파일러
  • JVM기반 애플리케이션을 기본적으로 실행 가능한 바이너리로 컴파일하는데 사용

다국어 프로그래밍 언어 지원

  • 프로그래밍 언어 인터프린터를 제공한다. 이를 통해 GraalVM을 확장하여 Java 에코 시스템에 언어를 추가할 수 있다. 또한 언어에 구애받지 않는 디버거, 프로파일러 및 힙 뷰어와 같은 도구를 지원한다.

JVM 대체제로써의 GraalVM

GraalVM은 Java, Scala, Kotlin 및 Java 바이트 코드에서 실행되는 모든 언어를 실행하는 JVM을 대체하려고 한다. 2019년 부터 GraalVM은 Linux상에서 프로덕션 준비가 완료되었다. 그리고 20.1.0 버전에서 Windows도 지원한다고 밝혔다.

여러 언어를 지원한다는 장점이 있지만, 아직까지 성능은 빠른 언어쪽도 있지만, 개별 컴파일러에 미치지는 못한다. 그럼에도 불구하고 GraalVM에 주목을 하는 이유는 무엇일까?

유지 보수

HotSpot컴파일러는 C/C++로 작성되어 있지만, Java로 JVM 컴파일러를 다시 작성하면 새로운 기회가 열린다. 수많은 자바 프로그래머가 GraalVM에 시간을 할애하고 개선에 기여할 것이기 때문이다. V8엔진과 HotSpot 컴파일러는 모두 수십년간 최적화의 어려움을 겪고있다. 개선을 하려면 큰 틀을 깨뜨려야 하는 문제점도 안고 있다. GraalVM은 새로운 아이디어를 바탕으로 이 문제에 대한 새로운 해석을 가지고 있다. 최적화 및 확장성을 염두해두고 만들어졌기 때문이다.

이런 부분들이 트위터가 GraalVM을 채택한 이유중에 하나일 것이다. 트위터는 Scala를 이용해 서비스를 만들고 있고, Scala는 JVM 바이트 코드로 컴파일되는 언어이다.

Truffle

Truffle는 컴파일러는 만드는 영리한 접근 방식이다. 최적화 Just-In-Time 컴파일러를 사용하면 모든 것을 컴파일하고 최적화한다는 아이디어이다. 인터프리터를 작성하면 인터프리터도 Just-In-Time 컴파일러에 의해 컴파일되고 최적화된다. 이 방식의 장점은 인터프리터이자 Compiler라고 불린다. Graal은 Ruby를 수동으로 최적화된 어셈블리 코드와 거의 비슷한 네이티브 코드로 컴파일한다.

Truffle을 사용하면 새로운 프로그래밍 언어를 작성할 수 있다. 각 언어마다 특징이 존재하는데 예를 들어서 동적 타이핑은 Groovy와 Ruby에 의해 유명해졌다. Scala와 Groovy의 출현으로 스트림과 함수형 프로그래밍이 현실화되었다. 향후 Kotlin이 이 아이디어를 채택한 후 마침내 Java8에 적용되었다.

Truffle로 프로그래밍 언어를 쉽게 구현할 수 있기 때문에 위와 같은 새로운 개념을 쉽게 사용할 수 있다. Truffle은 괜찮은 프로그래밍 언어를 사용하는데 걸리는 시간을 절반으로 줄여준다. 이는 결국 개발자가 프로그래밍 언어를 잘 선택하도록 도울 수 있다.

클라우드

GraalVM의 가장 큰 핵심은 AOT 컴파일러이다. 20년이 지난 후 Java가 어셈블리 코드로 컴파일 할 수 있게 되었다. 더 중요한 점은 네이티브 코드가 필요한 상황이 존재한다는 것이고 이는 AWS Lambda로 표현되었다.

Lambda의 흥미로운 점은 사용량에 따라 비용을 지불한다는 것이다. 코드가 실행되는 경우에만 비용을 지불한다는 의미다. 만약 이를 VM기반으로 구현한다고 가정하면 사용하지 않을 때 VM을 반복해서 종료해야 할 것이다. 그리고 요청이 왔을때 구동을 해야 할 것이다. 이 경우 대략 3초의 시간이 필요하다.

이런 문제를 AOT 컴파일러가 해결 할 수 있다. 일반적으로 Java는 수천 개의 클래스를 로드해야 하기 때문에 느리게 시작한다. 그러나 간단한 CRUD 애플리케이션은 로드된 클래스의 극히 일부만 사용한다. GET 요청에 응답하기 위해 Spring Boot의 모든 기능이 필요하진 않다.

AOT 컴파일러는 코드를 최적화하고 컴파일 후 네이티브 코드로 제공된다. Quarkus, Helidon과 같은 클라우드 네이티브 프레임워크를 이용하면 0.005초에 응답하는 Lambda 함수를 만들 수 있다.

GraalVM의 가격

GraalVM의 가격은 버전에 따라 달라진다. Community Edition은 오픈 소스이다. Enterprise는 Oracle GraalVM OTN 라이센스 계약 및 Oracle 마스터 라이센스 계약에 따라 사용할 수 있다. 엔터프라이즈 에디션의 가격은 라이센스에 따라 달라진다.

Oracle Master License Agreement에 따라 GraalVM Enterprise는 프로덕션 용도로 구입해야 한다.

결론

https://jaxenter.com/graalvm-chris-thalinger-interview-163074.html 에서는 트위터가 GraalVM을 사용하는 이유에 대해서 설명하고 있다.

GraalVM은 새로운 마이크로서비스 프레임워크를 촉진하는 것처럼 보인다. 예를 들어, Quarkus는 업계 표준 프레임워크의 모음이며 네이티브 바이너리를 생성할 수 있도록 확장해준다.

앞으로 GraalVM이 클라우드 네이티브 영역과 R, Python 및 Ruby와 같은 프로그래밍 언어에 상당한 영향을 미칠 것으로 기대한다.


4/08/2021

Spring Native 베타 소개

3/11일에 Spring Native 베타가 릴리즈되었다. GraalVM을 활용하여 Spring Java 및 Kotlin 애플리케이션을 네이티브 이미지로 컴파일하여 JVM에 구동되는 애플리케이션에 비해 시작 시간과 메모리 오버 헤드를 줄인다.

JVM 실행 파일에 비해 네이티브 이미지는 시작 시간이 더 빠르고 (100ms 미만) 메모리 소비가 적다. 하지만 네이티브 이미지를 빌드하려면 JVM을 이용하는 빌드 대비 더 많은 시간이 필요하다.

이 프로젝트는 아직 베타 버전이긴 하지만, Spring Framework, Spring Boot, Spring Security 및 Spring Cloud를 포함한 대부분의 Spring 프로젝트 모듈을 지원한다.

Spring Native는 Java 및 Kotlin 언어를 지원한다. Spring Native가 좋은 선택이 될 수 있는 상황은 아래와 같다.

  • Spring Cloud Function을 사용하는 서버리스 애플리케이션
  • Spring을 이용한 마이크로 서비스
  • Kubernetes 환경에서의 애플리케이션

Spring Native를 사용하면 개발자는 Java Development Kit, Spring의 필수 기능 및 애플리케이션에 필요한 종속성만으로 최소한의 OS 계층과 네이티브 실행 파일로 최적화된 컨테이너 이미지를 생성할 수 있다. Spring 진영에서는 기존 Spring Boot 애플리케이션을 변형하지 않아도 적용될 수 있게 고려중이다.

일반 JVM과 Native Image의 주요 차이점은 다음과 같다.

  • 애플리케이션의 정적 분석이 빌드시 수행된다.
  • 사용하지 않는 것은 빌드시 제거된다.
  • 리플렉션, 리소스 및 동적 프록시에 대한 구성이 필요하다.
  • 클래스 경로는 빌드시 고정된다.
  • 클래스 지연 로딩이 없다. → 제공된 모든것이 시작시 메모리에 로드됨
  • 일부 코드는 빌드시 실행된다.
  • 몇 가지 제한 사항이 존재한다.

사전 작업

현재 사용하고 있는 환경이 맥이라서 OSX 기준으로 설명하려 한다.

SDKMAN를 이용해 GraalVM과 maven을 설치한다.

SDKMAN 설치

아래의 명령어를 이용해 sdkman을 설치한다.

$curl -s "<https://get.sdkman.io>" | bash
$source "$HOME/.sdkman/bin/sdkman-init.sh"
view rawsdkman_install hosted with ❤ by GitHub

GraalVM 설치

$sdk list java | grep GraalVM
GraalVM | >>> | 21.0.0.2.r11 | grl | installed | 21.0.0.2.r11-grl
$sdk install java 21.0.0.2.r11-grl

Maven 설치

$sdk install maven
view rawmaven_install hosted with ❤ by GitHub

Spring Native Boot strap

Spring Initializr에서 프로젝트를 부트 스트랩 할 때 애플리케이션에 Spring Native를 추가 할 수 있다.


생성된 프로젝트에는 Spring Native 프로젝트와 애플리케이션 소스 코드를 네이티브 실행 파일로 컴파일하는데 사용되는 Spring AOT 플러그인에 대한 종속성이 포함되며 호환성과 풋 프린트가 향상된다.

아래는 Maven Script 예시이다.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>
<groupId>com.giljae</groupId>
<artifactId>spring-native-example</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-native-example</name>
<description>Demo project for Spring Native</description>
<properties>
<java.version>11</java.version>
<spring-native.version>0.9.1</spring-native.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>${spring-native.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>native</id>
<build>
<plugins>
<plugin>
<groupId>org.graalvm.nativeimage</groupId>
<artifactId>native-image-maven-plugin</artifactId>
<version>20.2.0</version>
<configuration>
<imageName>${project.artifactId}</imageName>
<!-- The native image build needs to know the entry point to your
application -->
<mainClass>com.giljae.springnativeexample.SpringNativeExampleApplication</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>native-image</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>jar</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<image>
<builder>paketobuildpacks/builder:tiny</builder>
<env>
<BP_NATIVE_IMAGE>true</BP_NATIVE_IMAGE>
</env>
</image>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>${spring-native.version}</version>
<executions>
<execution>
<id>test-generate</id>
<goals>
<goal>test-generate</goal>
</goals>
</execution>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<build>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<name>Spring Releases</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
</project>
view rawpom.xml hosted with ❤ by GitHub

Spring WebFlux로 REST 엔드 포인트 정의

애플리케이션을 테스트할 수 있도록 Spring WebFlux로 REST 엔드 포인트를 정의해보자.

SpringNativeExampleApplication 클래스, RouterFunction을 사용하여 REST 엔드 포인트를 추가 할 수 있다.

package com.giljae.springnativeexample;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
import static org.springframework.web.reactive.function.server.ServerResponse.ok;
@SpringBootApplication
public class SpringNativeExampleApplication {
public static void main(String[] args) {
SpringApplication.run(SpringNativeExampleApplication.class, args);
}
@Bean
RouterFunction<ServerResponse> routes() {
return route()
.GET("/", request -> ok().body(Mono.just("Spring Native Example!!!"), String.class))
.build();
}
}

그리고 SpringNativeExampleApplicationTests 클래스에서 REST에 대한 테스트 코드를 작성해보자.

package com.giljae.springnativeexample;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@AutoConfigureWebTestClient
class SpringNativeExampleApplicationTests {
@Autowired
private WebTestClient webClient;
@Test
void getRootReturn() {
webClient
.get().uri("/")
.exchange()
.expectStatus().is2xxSuccessful()
.expectBody(String.class).isEqualTo("Spring Native Example!!!");
}
}

애플리케이션을 jar로 실행하기

프로젝트의 Spring Native 종속성은 Spring AOT 플러그인을 통해 JAR로 실행하는 경우에도 시작 시간과 메모리 소비를 최적화 한다.

터미널 창을 열고 프로젝트 디렉토리로 이동 후 아래의 명령을 실행한다.

$mvn clean package
$mvn spring-boot:run
view rawexecute_jar hosted with ❤ by GitHub

정상적으로 구동되었는지 테스트해보자.

$curl <http://localhost:8080>
**Spring Native Example!!!**
view rawexecute_test hosted with ❤ by GitHub

jar로 정상 동작되는것을 확인했으니., 이제 네이티브 이미지로 실행해보도록 한다.

애플리케이션을 Native 이미지로 실행하기

이제 GraalVM과 함께 Spring Native를 활용하여 네이티브 이미지를 빌드하고 실행하자.

네이티브 이미지를 빌드하는 것은 Spring Boot 플러그인을 사용하면 매우 간단하다. 여기서는 Docker를 이용하지 않고, Native Image를 빌드하는 것으로 설명한다.

이미 pom.xml에 네이티브 이미지 생성 plugin을 작성했기에, 프로젝트 디렉토리에서 아래의 명령어를 실행한다.

$mvn -Pnative clean package
view rawnative_execute hosted with ❤ by GitHub

많은 시간이 흐른 후, 빌드가 성공하면 프로젝트내 /target 디렉토리 밑에 spring-native-example이라는 파일이 생성된다.

/target 디렉토리로 이동하여 아래의 명령어를 실행해서 애플리케이션을 구동해보자.

$./spring-native-example
2021-04-08 17:12:18.522 INFO 1751 --- [ main] o.s.nativex.NativeListener : This application is bootstrapped with code generated with Spring AOT
. ____ _ __ _ _
/\\\\ / ___'_ __ _ _(_)_ __ __ _ \\ \\ \\ \\
( ( )\\___ | '_ | '_| | '_ \\/ _` | \\ \\ \\ \\
\\\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.4.4)
2021-04-08 17:12:18.524 INFO 1751 --- [ main] c.g.s.SpringNativeExampleApplication : Starting SpringNativeExampleApplication v0.0.1-SNAPSHOT using Java 11.0.10 on CRE8ISM.local with PID 1751 (/Users/giljae/Projects/sandbox/spring-native-example/target/spring-native-example started by davian in /Users/giljae/Projects/sandbox/spring-native-example/target)
2021-04-08 17:12:18.524 INFO 1751 --- [ main] c.g.s.SpringNativeExampleApplication : No active profile set, falling back to default profiles: default
2021-04-08 17:12:18.570 INFO 1751 --- [ main] o.s.b.web.embedded.netty.NettyWebServer : Netty started on port 8080
2021-04-08 17:12:18.571 INFO 1751 --- [ main] c.g.s.SpringNativeExampleApplication : Started SpringNativeExampleApplication in 0.067 seconds (JVM running for 0.069)
view rawnative_run hosted with ❤ by GitHub

정상적으로 구동되었는지 테스트 해보자.

$curl <http://localhost:8080>
**Spring Native Example!!!**
view rawrunning_test hosted with ❤ by GitHub

결론

본 글에서는 Spring Boot 애플리케이션을 빠르게 부트 스트랩하고 Spring Native 및 GraalVM을 사용하여 네이티브 실행 파일로 컴파일하는 방법을 살펴보았다. 위에서 사용된 코드는 이곳에 올려두었다. Spring Native 프로젝트에 대한 자세한 정보를 보려면 공식 문서를 참조하길 바란다.