1. 개요
이 Codelab에서는 Spring Native 프로젝트에 대해 알아보고 이를 사용하는 앱을 빌드한 후 Google Cloud에 배포합니다.
구성요소, 프로젝트의 최근 기록, 몇 가지 사용 사례, 물론 프로젝트에서 이를 사용하는 데 필요한 단계를 살펴봅니다.
Spring Native 프로젝트는 현재 실험 단계에 있으므로 시작하려면 몇 가지 특정 구성이 필요합니다. 하지만 SpringOne 2021에서 발표된 바와 같이 Spring Native는 최고 수준의 지원을 통해 Spring Framework 6.0 및 Spring Boot 3.0에 통합될 예정이므로 출시 몇 개월 전에 프로젝트를 자세히 살펴볼 수 있는 좋은 기회입니다.
JIT 컴파일은 장기 실행 프로세스와 같은 작업에 매우 잘 최적화되어 있지만, 사전 컴파일된 애플리케이션이 더 나은 성능을 발휘하는 특정 사용 사례가 있습니다. 이 내용은 Codelab에서 다룹니다.
다음 실습에서는
- Cloud Shell 사용하기
- Cloud Run API 사용 설정
- Spring 네이티브 앱 만들기 및 배포
- 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 컴파일을 사용하여 이러한 해석 비용을 완화합니다. 이는 함수의 호출 횟수를 집계하여 이루어지며, 임곗값 ( 기본적으로 10,000)을 통과할 만큼 자주 호출되면 런타임에 네이티브 코드로 컴파일되어 더 이상 비용이 많이 드는 해석을 방지합니다.
AOT 컴파일은 이와 반대로 컴파일 시간에 도달 가능한 모든 코드를 네이티브 실행 파일로 컴파일합니다. 이렇게 하면 휴대성을 희생하는 대신 런타임 시 메모리 효율성 및 기타 성능이 향상됩니다.
물론 이는 장단점이 있으며 항상 가치가 있는 것은 아닙니다. 하지만 다음과 같은 특정 사용 사례에서는 AOT 컴파일이 유용할 수 있습니다.
- 시작 시간이 중요한 단기 애플리케이션
- JIT가 너무 비용이 많이 들 수 있는 메모리 제약이 심한 환경
재미있는 사실은 AOT 컴파일이 JDK 9에서 실험용 기능으로 도입되었지만 이 구현은 유지 관리 비용이 많이 들고 제대로 사용되지 않았기 때문에 Java 17에서는 GraalVM만 사용하는 개발자를 위해 조용히 삭제되었습니다.
GraalVM
GraalVM은 매우 빠른 시작 시간, AOT 네이티브 이미지 컴파일, 개발자가 여러 언어를 단일 애플리케이션으로 혼합할 수 있는 다국어 기능을 자랑하는 고도로 최적화된 오픈소스 JDK 배포입니다.
GraalVM은 현재 활발하게 개발 중이며 새로운 기능을 추가하고 기존 기능을 지속적으로 개선하고 있으므로 개발자는 계속해서 많은 관심을 가져 주시기 바랍니다.
최근 주요 기록은 다음과 같습니다.
- 사용자 친화적인 새로운 네이티브 이미지 빌드 출력 ( 2021-01-18)
- Java 17 지원 ( 2022-01-18)
- 다중 언어 컴파일 시간을 개선하기 위해 기본적으로 다층 컴파일을 사용 설정합니다. (2021-04-20)
Spring Native
간단히 말해 Spring Native를 사용하면 GraalVM의 네이티브 이미지 컴파일러를 사용하여 Spring 애플리케이션을 네이티브 실행 파일로 변환할 수 있습니다.
이 프로세스에는 컴파일 시간에 애플리케이션의 정적 분석을 실행하여 진입점에서 도달할 수 있는 애플리케이션의 모든 메서드를 찾는 작업이 포함됩니다.
이렇게 하면 모든 코드가 컴파일 시점에 알려진 것으로 가정되고 런타임 시에는 새 코드가 로드되지 않는 애플리케이션의 '폐쇄된 세계' 개념이 기본적으로 생성됩니다.
네이티브 이미지 생성은 일반 애플리케이션을 컴파일하는 것보다 시간이 오래 걸리고 메모리 집약적인 프로세스이며 Java의 특정 측면에 제한사항을 적용한다는 점에 유의해야 합니다.
경우에 따라 애플리케이션이 Spring Native와 함께 작동하도록 코드를 변경할 필요가 없습니다. 그러나 제대로 작동하려면 특정 네이티브 구성이 필요한 경우도 있습니다. 이러한 경우 Spring 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 저장소 구성
이 프로젝트는 아직 실험 단계이므로 메이븐의 중앙 저장소에서 사용할 수 없는 실험용 아티팩트를 찾을 수 있도록 앱을 구성해야 합니다.
이렇게 하려면 pom.xml에 다음 요소를 추가해야 합니다. 원하는 편집기에서 이를 수행할 수 있습니다.
pom에 다음과 같은 저장소 및 pluginRepositories 섹션을 추가합니다.
<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>
이제 spring-boot-maven-plugin을 업데이트하여 네이티브 이미지 지원을 사용 설정하고 paketo 빌더를 사용하여 네이티브 이미지를 빌드합니다.
<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 프로젝트를 만들었든 기존 프로젝트를 재사용하든, 사용한 리소스에서 불필요한 요금이 청구되지 않도록 주의하세요.
생성된 Cloud Run 서비스를 삭제 또는 사용 중지하거나, 호스팅된 이미지를 삭제하거나, 전체 프로젝트를 종료할 수 있습니다.
8. 추가 리소스
Spring Native 프로젝트는 현재 새롭고 실험적인 프로젝트이지만, 초기 사용자는 문제를 해결하고 참여하는 데 도움이 되는 다양한 리소스를 이미 활용할 수 있습니다.
추가 리소스
다음은 이 튜토리얼과 관련이 있는 온라인 리소스입니다.
라이선스
이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.