[230309] Spring Graalvm Native Image
Table of Contents
1 Graalvm 은 왜 중요해지는 걸까?
주위에서 마이크로서비스를 위해 쿠버네티스를 사용하는 것을 많이 본다. 왠지 나도 써야할 것만 같고 뒤쳐지는 것 같다.
그래서 쿠버네티스를 찾아보다보니, 사실 자바는 이렇게 마이크로서비스를 사용하기에 뭔가 아쉬운 점이 많다. 대부분 JDK를 갖는 이미지에 jar를 넣어서 사용하는 것을 많이 보았다. 만약에 이렇게 사용한다면, 200MB 가 넘는 것은 순식간이다.
이미지를 작게 만들기 위해서 jlink를 이용하는 사람들도 있다. 기본적으로 제공되는 모든 피처를 사용하는 것이 아니라 사용하는 부분만 모듈에 넣겠다는 것 같다.
그보다 더 공격적인 방법은 Native Image를 만들어서 jar파일이 아닌 실행파일을 만들겠다는 것이다. 확실히 이것을 사용하면 꽤나 빠른 속도를 가지게 된다.
그래서 테스트를 해봤다.
2 코드
다음은 생성된 build.gradle
이다.
plugins { id 'java' id 'org.springframework.boot' version '3.0.3' id 'io.spring.dependency-management' version '1.1.0' id 'org.graalvm.buildtools.native' version '0.9.18' } group = 'com.example' version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' repositories { mavenCentral() } dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' testImplementation 'org.springframework.boot:spring-boot-starter-test' } tasks.named('test') { useJUnitPlatform() } hibernate { enhancement { lazyInitialization true dirtyTracking true associationManagement true } }
여기서 테스트를 위해 api 를 하나 뚫었다.
@RestController public class HelloController { @RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; } }
id 'org.graalvm.buildtools.native' version '0.9.18'
이것이 핵심이다. 이제 ./gradlew buildBootImage
를 수행하면 그대로 도커 이미지가 생성될 것이다.
M1인 경우는 현재 수행이 되지 않는다. image를 생성하기 위한 패키지가 paketo(?)라는 녀석이 M1을 지원하지 않는다. 맥을 사용한다면 intel을 사용해야 한다. (아직 갈길이 멀다)
하지만 paketo를 다른 녀석으로 교체하면 된다.
tasks.named("bootBuildImage") { builder = "dashaun/builder:tiny" environment = ["BP_NATIVE_IMAGE" : "true"] }
이렇게 build.gradle
에 내용을 추가하고 수행하면 6분정도 걸려서 이미지가 만들어진다.
docker images demo 0.0.1-SNAPSHOT 73073922123b 43 years ago 109MB
실행해보자.
$ docker run -p 8080:8080 docker.io/library/demo:0.0.1-SNAPSHOT . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.3) 2023-03-08T16:46:41.420Z INFO 1 --- [ main] com.example.demo.DemoApplication : Starting AOT-processed DemoApplication using Java 17.0.6 with PID 1 (/workspace/com.example.demo.DemoApplication started by root in /workspace) 2023-03-08T16:46:41.420Z INFO 1 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default" 2023-03-08T16:46:41.432Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2023-03-08T16:46:41.433Z INFO 1 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-03-08T16:46:41.433Z INFO 1 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.5] 2023-03-08T16:46:41.438Z INFO 1 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2023-03-08T16:46:41.438Z INFO 1 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 17 ms 2023-03-08T16:46:41.456Z INFO 1 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2023-03-08T16:46:41.457Z INFO 1 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.046 seconds (process running for 0.056)
스타트업 시간이 빠르긴 한 것 같다. 한번 intellij 에서 jar를 만들어서 실행해보겠다.
./gradlew bootJar java -jar build/libs/demo-0.0.1-SNAPSHOT.jar . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v3.0.3) 2023-03-09T01:48:24.743+09:00 INFO 61182 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication v0.0.1-SNAPSHOT using Java 19.0.1 with PID 61182 (/Users/tom/IdeaProjects/demo/build/libs/demo-0.0.1-SNAPSHOT.jar started by tom in /Users/tom/IdeaProjects/demo) 2023-03-09T01:48:24.745+09:00 INFO 61182 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to 1 default profile: "default" 2023-03-09T01:48:25.177+09:00 INFO 61182 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http) 2023-03-09T01:48:25.183+09:00 INFO 61182 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat] 2023-03-09T01:48:25.183+09:00 INFO 61182 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.5] 2023-03-09T01:48:25.229+09:00 INFO 61182 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext 2023-03-09T01:48:25.230+09:00 INFO 61182 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 459 ms 2023-03-09T01:48:25.399+09:00 INFO 61182 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path '' 2023-03-09T01:48:25.408+09:00 INFO 61182 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 0.847 seconds (process running for 1.071)
- buildNative : 0.053s
- buildBootImage : 0.056s
- bootJar : 1.071s
대강 이정도 차이가 난다. 확실히 수십배 차이가 나는 것 같다. 아마 Native Image가 대세가 되지않을까 생각된다.