M1에서 GraalVM으로 네이티브 이미지 만들기

Photo by toine G on Unsplash

M1에서 GraalVM으로 네이티브 이미지 만들기

GraalVM으로 애플리케이션을 더욱 빠르게 실행해 봅시다.

기존의 JIT 컴파일러는 .java -> javac -> 바이트코드 -> jvm -> 인터프리터 -> JIT 순서로 실행되는데, AOT 컴파일러는 .java -> AOT magic -> 네이티브 실행 파일 순서로 실행됩니다.(JIT와 반대로 빌드 시 정적 코드 분석을 진행합니다.)

이후 특정 플랫폼(Windows, Mac, Linux, x64, ARM 등등)에 대한 기본 실행 파일을 생성합니다. 이는 프로그램을 시작한 후에 바이트코드 해석/컴파일을 수행할 필요가 없고 애플리케이션을 최대 속도로 시작할 수 있다는 것을 의미합니다.

이 글에서는 실행 파일로서 도커 이미지를 생성하고 실행해 봅니다.

💡설치

https://github.com/graalvm/homebrew-tap

Homebrew를 통해 간편하게 설치하실 수 있습니다. 엔터프라이즈 에디션과 커뮤니티 에디션이 있는데, 엔터프라이즈 에디션이 메모리 관리가 더 우수하므로 EE를 설치합니다.

$ brew install --cask graalvm/tap/graalvm-jdk17

다운로드를 받으신 후, GRAALVM_HOME을 설정해주시면 됩니다. 저는 zsh를 쓰고있기 때문에 .zshrc에 환경변수를 등록했습니다.

$ vi ~/.zshrc
...
export GRAALVM_HOME=/Library/Java/JavaVirtualMachines/graalvm-jdk-XX.Y.Z/Contents/Home
export JAVA_HOME=$GRAALVM_HOME

macOS Catalina에서는 "the developer cannot be verified" 경고 문구가 나타날 수 있습니다. 다음 커맨드를 실행하여 이를 무시할 수 있습니다.

xattr -r -d com.apple.quarantine /Library/Java/JavaVirtualMachines/graalvm-jdk-XX.Y.Z

💡빌드

Spring Initializr에서 GraalVM Native Support, Spring Web 2개의 의존성을 추가하여 메이븐 프로젝트를 생성합니다. 그 후 빌드팩 이미지를 dashaun/builder:tiny로 변경해줘야 합니다. (기본 Packeto Buildpacks 이미지는 아직 M1 아키텍처를 지원하지 않습니다.)

<build>
    <plugins>
        <plugin>
            <groupId>org.graalvm.buildtools</groupId>
            <artifactId>native-maven-plugin</artifactId>
        </plugin>
        <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <configuration>
                <image>
                    <builder>dashaun/builder:tiny</builder>
                </image>
            </configuration>
        </plugin>
    </plugins>
</build>

이제 $ mvn -Pnative spring-boot:build-image 명령어로 애플리케이션을 정적 컴파일하여 이미지를 빌드할 수 있습니다. (Docker가 실행 중이어야 합니다.)

로그를 살펴보면 미리 스프링을 가동시키고 이미지를 빌드하는 것을 확인할 수 있는데요, 이 빌드 시간이 굉장히 오래 걸립니다.

드디어 docker.io/library/demo:0.0.1-SNAPSHOT 이미지가 생성됐습니다! 제 맥북(M1 Air 16GB)에서 22분 49초가 걸리네요. 이 정도면 무거운 프로젝트는 빌드 명령해놓고 아예 집 앞 산책 갔다 와도 되겠습니다.

어쨌거나 빌드 시간이 보람 있어야겠죠. 이 네이티브 이미지는 얼마나 빨리 구동될까요?

우선 기존의 방식대로 JAR 파일로 실행해 봅시다.

애플리케이션 실행까지 3.358초가 걸리네요. 그럼 이제 다음과 같이 네이티브 이미지를 실행해봅시다.

$ docker run --rm -p 8080:8080 docker.io/library/demo:0.0.1-SNAPSHOT

이전보다 5배 이상 적은 0.647초가 걸렸네요.

빌드를 미리 해두었기 때문에 실행 과정에서 불필요한 과정들을 모두 생략해버렸습니다.


🎯정리


🔖참고