1. 개요
이 Codelab에서는 Spring Native 프로젝트에 대해 알아보고, 이를 사용하는 앱을 빌드하고, Google Cloud에 배포합니다.
구성요소, 프로젝트의 최근 기록, 몇 가지 사용 사례, 프로젝트에서 사용할 때 필요한 단계를 살펴봅니다.
Spring Native 프로젝트는 현재 실험 단계에 있으므로 시작하려면 몇 가지 특정 구성이 필요합니다. 하지만 SpringOne 2021에서 발표된 바와 같이 Spring Native는 최고 수준의 지원과 함께 Spring Framework 6.0 및 Spring Boot 3.0에 통합될 예정이므로 출시 몇 개월 전에 프로젝트를 자세히 살펴볼 수 있는 좋은 기회입니다.
JIT(Just-In-Time) 컴파일은 장기 실행 프로세스와 같은 항목에 매우 잘 최적화되어 있지만, 사전 컴파일된 애플리케이션이 훨씬 더 나은 성능을 보이는 특정 사용 사례가 있습니다. 이는 코드랩에서 설명합니다.
다음 실습에서는
- Cloud Shell 사용하기
- Cloud Run API 사용 설정
- Spring Native 앱 만들기 및 배포
- 이러한 앱을 Cloud Run에 배포합니다.
필요한 항목
- 활성 GCP 결제 계정이 있는 Google Cloud Platform 프로젝트
- gcloud CLI가 설치되어 있거나 Cloud Shell에 액세스할 수 있어야 합니다.
- 기본 Java + XML 기술
- 일반적인 Linux 명령어에 대한 실무 지식
설문조사
이 튜토리얼을 어떻게 사용하실 계획인가요?
귀하의 Java 사용 경험을 평가해 주세요.
귀하의 Google Cloud 서비스 사용 경험을 평가해 주세요.
2. 배경
Spring Native 프로젝트는 여러 기술을 활용하여 개발자에게 네이티브 애플리케이션 성능을 제공합니다.
Spring Native를 완전히 이해하려면 이러한 구성요소 기술, 이러한 기술이 지원하는 기능, 이러한 기술이 여기에서 함께 작동하는 방식을 이해하는 것이 좋습니다.
AOT 컴파일
개발자가 컴파일 시간에 javac를 정상적으로 실행하면 .java 소스 코드가 바이트 코드로 작성된 .class 파일로 컴파일됩니다. 이 바이트 코드는 Java 가상 머신에서만 이해할 수 있으므로 코드를 실행하려면 JVM이 다른 머신에서 이 코드를 해석해야 합니다.
이 프로세스를 통해 Java의 대표적인 이식성, 즉 '한 번 작성하고 어디서나 실행'이 가능하지만 네이티브 코드를 실행하는 것과 비교하면 비용이 많이 듭니다.
다행히도 JVM의 대부분의 구현에서는 JIT(Just-In-Time) 컴파일을 사용하여 이 해석 비용을 완화합니다. 이는 함수의 호출을 계산하여 달성되며, 함수가 기준점 ( 기본값 10,000)을 통과할 만큼 자주 호출되면 런타임에 네이티브 코드로 컴파일되어 추가적인 비용이 많이 드는 해석을 방지합니다.
사전 컴파일은 컴파일 시간에 도달 가능한 모든 코드를 네이티브 실행 파일로 컴파일하여 반대 접근 방식을 취합니다. 이렇게 하면 메모리 효율성과 런타임의 기타 성능 향상을 위해 이식성이 저하됩니다.

물론 이는 절충안이며 항상 가치가 있는 것은 아닙니다. 하지만 AOT 컴파일은 다음과 같은 특정 사용 사례에서 빛을 발할 수 있습니다.
- 시작 시간이 중요한 단기 애플리케이션
- JIT 비용이 너무 많이 들 수 있는 메모리 제약이 심한 환경
재미있는 사실은 AOT 컴파일이 JDK 9에서 실험적 기능으로 도입되었지만 이 구현은 유지하는 데 비용이 많이 들고 인기를 얻지 못해 Java 17에서 개발자가 GraalVM을 사용하는 것을 선호하여 조용히 삭제되었다는 것입니다.
GraalVM
GraalVM은 매우 빠른 시작 시간, AOT 네이티브 이미지 컴파일, 개발자가 여러 언어를 단일 애플리케이션으로 혼합할 수 있는 다국어 기능을 자랑하는 고도로 최적화된 오픈소스 JDK 배포입니다.
GraalVM은 활발하게 개발되고 있으며 새로운 기능이 추가되고 기존 기능이 지속적으로 개선되고 있으므로 개발자 여러분은 계속해서 관심을 가져 주시기 바랍니다.
최근의 주요 기록은 다음과 같습니다.
- 새로운 사용자 친화적인 네이티브 이미지 빌드 출력 ( 2021-01-18)
- Java 17 지원 ( 2022년 1월 18일)
- Polyglot 컴파일 시간을 개선하기 위해 기본적으로 다중 계층 컴파일 사용 설정 ( 2021년 4월 20일)
Spring Native
간단히 말해 Spring Native를 사용하면 GraalVM의 native-image 컴파일러를 사용하여 Spring 애플리케이션을 네이티브 실행 파일로 변환할 수 있습니다.
이 프로세스에는 컴파일 시간에 애플리케이션의 정적 분석을 실행하여 진입점에서 도달할 수 있는 애플리케이션의 모든 메서드를 찾는 작업이 포함됩니다.
이렇게 하면 컴파일 시간에 모든 코드가 알려진 것으로 가정되고 런타임에 새 코드를 로드할 수 없는 애플리케이션의 '폐쇄형' 개념이 생성됩니다.
네이티브 이미지 생성은 일반 애플리케이션 컴파일보다 시간이 오래 걸리는 메모리 집약적인 프로세스이며 Java의 특정 측면에 제한사항이 적용됩니다.
경우에 따라 애플리케이션이 Spring Native와 함께 작동하기 위해 코드 변경이 필요하지 않습니다. 하지만 일부 상황에서는 제대로 작동하려면 특정 네이티브 구성이 필요합니다. 이러한 상황에서 Spring Native는 이 프로세스를 간소화하기 위해 Native 힌트를 제공하는 경우가 많습니다.
3. 설정/사전 작업
Spring Native 구현을 시작하기 전에 앱을 만들어 배포하여 나중에 네이티브 버전과 비교할 수 있는 성능 기준을 설정해야 합니다.
1. 프로젝트 만들기
먼저 start.spring.io에서 앱을 가져옵니다.
curl https://start.spring.io/starter.zip -d dependencies=web \
-d javaVersion=11 \
-d bootVersion=2.6.4 -o io-native-starter.zip
이 스타터 앱은 작성 시점에 spring-native 프로젝트에서 지원하는 최신 버전인 Spring Boot 2.6.4를 사용합니다.
GraalVM 21.0.3 출시 이후에는 이 샘플에 Java 17을 사용할 수도 있습니다. 이 튜토리얼에서는 관련된 구성을 최소화하기 위해 Java 11을 계속 사용합니다.
명령줄에 zip이 있으면 프로젝트의 하위 디렉터리를 만들고 그 안에 폴더의 압축을 풀 수 있습니다.
mkdir spring-native cd spring-native unzip ../io-native-starter.zip
2. 코드 변경
프로젝트를 열면 생명력을 빠르게 추가하고 실행한 후 Spring Native 성능을 보여줍니다.
DemoApplication.java를 다음과 일치하도록 수정합니다.
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.context.event.EventListener;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.time.Instant;
@RestController
@SpringBootApplication
public class DemoApplication {
private static Instant startTime;
private static Instant readyTime;
public static void main(String[] args) {
startTime = Instant.now();
SpringApplication.run(DemoApplication.class, args);
}
@GetMapping("/")
public String index() {
return "Time between start and ApplicationReadyEvent: "
+ Duration.between(startTime, readyTime).toMillis()
+ "ms";
}
@EventListener(ApplicationReadyEvent.class)
public void ready() {
readyTime = Instant.now();
}
}
이제 기준 앱을 사용할 수 있으므로 네이티브 애플리케이션으로 변환하기 전에 이미지를 빌드하고 로컬로 실행하여 시작 시간을 파악해 보세요.
이미지를 빌드하려면 다음을 실행하세요.
mvn spring-boot:build-image
docker images demo를 사용하여 기준 이미지의 크기를 파악할 수도 있습니다. 
앱을 실행하려면 다음 단계를 따르세요.
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
3. 기준 앱 배포
이제 앱이 있으므로 앱을 배포하고 시간을 기록합니다. 이 시간은 나중에 네이티브 앱 시작 시간과 비교할 것입니다.
빌드하는 애플리케이션 유형에 따라 내 콘텐츠를 호스팅하는 방법이 여러 가지 있습니다.
하지만 이 예는 매우 간단하고 직접적인 웹 애플리케이션이므로 간단하게 Cloud Run을 사용할 수 있습니다.
자체 머신에서 따라 하는 경우 gcloud CLI 도구가 설치되고 업데이트되었는지 확인하세요.
Cloud Shell을 사용하는 경우 이 모든 것이 처리되므로 소스 디렉터리에서 다음을 실행하면 됩니다.
gcloud run deploy
4. 애플리케이션 구성
1. Maven 저장소 구성
이 프로젝트는 아직 실험 단계에 있으므로 Maven의 중앙 저장소에서 사용할 수 없는 실험적 아티팩트를 찾을 수 있도록 앱을 구성해야 합니다.
이를 위해서는 pom.xml에 다음 요소를 추가해야 하며, 원하는 편집기에서 이를 수행할 수 있습니다.
다음 저장소 및 pluginRepositories 섹션을 pom에 추가합니다.
<repositories>
<repository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-release</id>
<name>Spring release</name>
<url>https://repo.spring.io/release</url>
</pluginRepository>
</pluginRepositories>
2. 종속 항목 추가
다음으로 Spring 애플리케이션을 네이티브 이미지로 실행하는 데 필요한 spring-native 종속 항목을 추가합니다. 참고: Gradle을 사용하는 경우 이 단계는 필요하지 않습니다.
<dependencies>
<!-- ... -->
<dependency>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-native</artifactId>
<version>0.11.2</version>
</dependency>
</dependencies>
3. 플러그인 추가/사용 설정
이제 AOT 플러그인을 추가하여 네이티브 이미지 호환성과 설치 공간을 개선합니다 ( 자세히 알아보기).
<plugins>
<!-- ... -->
<plugin>
<groupId>org.springframework.experimental</groupId>
<artifactId>spring-aot-maven-plugin</artifactId>
<version>0.11.2</version>
<executions>
<execution>
<id>generate</id>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
이제 네이티브 이미지 지원을 사용 설정하고 paketo 빌더를 사용하여 네이티브 이미지를 빌드하도록 spring-boot-maven-plugin을 업데이트합니다.
<plugins>
<!-- ... -->
<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>
</plugins>
소형 빌더 이미지는 여러 옵션 중 하나일 뿐입니다. 추가 라이브러리와 유틸리티가 거의 없어 공격 표면을 최소화하는 데 도움이 되므로 이 사용 사례에 적합합니다.
예를 들어 일부 일반적인 C 라이브러리에 액세스해야 하는 앱을 빌드하는 경우나 앱의 요구사항을 아직 잘 모르는 경우 full-builder가 더 적합할 수 있습니다.
5. 네이티브 앱 빌드 및 실행
이 모든 것이 준비되면 이미지를 빌드하고 컴파일된 네이티브 앱을 실행할 수 있습니다.
빌드를 실행하기 전에 유의해야 할 사항이 몇 가지 있습니다.
- 일반 빌드보다 시간이 더 오래 걸립니다 (몇 분).

- 이 빌드 프로세스는 많은 메모리 (몇 기가바이트)를 사용할 수 있습니다.

- 이 빌드 프로세스에서는 Docker 데몬에 연결할 수 있어야 합니다.
- 이 예에서는 프로세스를 수동으로 진행하지만 네이티브 빌드 프로필을 자동으로 트리거하도록 빌드 단계를 구성할 수도 있습니다.
이미지를 빌드하려면 다음을 실행하세요.
mvn spring-boot:build-image
빌드가 완료되면 네이티브 앱을 실행할 수 있습니다.
앱을 실행하려면 다음 단계를 따르세요.
docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT
이제 네이티브 애플리케이션 방정식의 양쪽을 모두 살펴볼 수 있습니다.
컴파일 시 약간의 시간과 추가 메모리 사용량을 포기했지만 그 대신 훨씬 빠르게 시작하고 워크로드에 따라 메모리를 훨씬 적게 소비할 수 있는 애플리케이션을 얻을 수 있습니다.
docker images demo을 실행하여 네이티브 이미지의 크기를 원본과 비교하면 크게 줄어든 것을 확인할 수 있습니다.

또한 더 복잡한 사용 사례에서는 앱이 런타임에 실행될 작업을 AOT 컴파일러에 알리기 위해 추가 수정이 필요합니다. 따라서 일괄 작업과 같은 예측 가능한 특정 워크로드는 이 기능에 매우 적합할 수 있지만 다른 워크로드는 더 큰 작업이 필요할 수 있습니다.
6. 네이티브 앱 배포
앱을 Cloud Run에 배포하려면 네이티브 이미지를 Artifact Registry와 같은 패키지 관리자에 넣어야 합니다.
1. Docker 저장소 준비
저장소를 만들어 이 프로세스를 시작할 수 있습니다.
gcloud artifacts repositories create native-image-docker-repo --repository-format=docker \
--location=us-central1 --description="Repository for our native images"
다음으로 새 레지스트리에 푸시할 수 있도록 인증해야 합니다.
gcloud CLI를 사용하면 이 프로세스를 상당히 간소화할 수 있습니다.
gcloud auth configure-docker us-central1-docker.pkg.dev
2. Artifact Registry에 이미지 푸시
이제 이미지에 태그를 지정합니다.
export PROJECT_ID=$(gcloud config list --format 'value(core.project)')
docker tag demo:0.0.1-SNAPSHOT \
us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
그런 다음 docker push를 사용하여 Artifact Registry로 전송할 수 있습니다.
docker push us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
3. Cloud Run에 배포
이제 Artifact Registry에 저장한 이미지를 Cloud Run에 배포할 준비가 되었습니다.
gcloud run deploy --image us-central1-docker.pkg.dev/$PROJECT_ID/native-image-docker-repo/quickstart-image:tag2
앱을 네이티브 이미지로 빌드하고 배포했으므로 애플리케이션이 실행될 때 인프라 비용을 최대한 활용하고 있다고 확신할 수 있습니다.
기준 앱의 시작 시간과 이 새로운 네이티브 앱의 시작 시간을 직접 비교해 보세요.

7. 요약/삭제
Google Cloud에서 Spring Native 애플리케이션을 빌드하고 배포하신 것을 축하드립니다.
이 튜토리얼을 통해 Spring Native 프로젝트에 대해 자세히 알아보고 향후 필요에 따라 활용할 수 있기를 바랍니다.
선택사항: 서비스 정리 또는 사용 중지
이 Codelab을 위해 Google Cloud 프로젝트를 만들었든 기존 프로젝트를 재사용하든, 사용한 리소스에서 불필요한 요금이 청구되지 않도록 주의하세요.
Google에서 만든 Cloud Run 서비스를 삭제하거나 사용 중지하거나, Google에서 호스팅한 이미지를 삭제하거나, 전체 프로젝트를 종료할 수 있습니다.
8. 추가 리소스
Spring Native 프로젝트는 현재 새롭고 실험적인 프로젝트이지만, 얼리 어답터가 문제를 해결하고 참여할 수 있도록 지원하는 다양한 리소스가 이미 마련되어 있습니다.
추가 리소스
다음은 이 튜토리얼과 관련이 있을 수 있는 온라인 리소스입니다.
라이선스
이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.