Google Cloud에서 Spring 네이티브 실행

1. 개요

이 Codelab에서는 Spring 네이티브 프로젝트에 관해 알아보고, 이를 사용하는 앱을 빌드하고 Google Cloud에 배포합니다.

구성요소, 프로젝트의 최근 기록, 몇 가지 사용 사례, 프로젝트에서 이 구성요소를 사용하는 데 필요한 단계를 알아봅니다.

Spring Native 프로젝트는 현재 실험 단계에 있으므로 시작하기 전에 특정 구성이 필요합니다. 하지만 SpringOne 2021에서 발표된 바와 같이 Spring Native는 첫 번째 클래스 지원이 포함된 Spring Framework 6.0 및 Spring Boot 3.0에 통합되도록 설정되어 있습니다. 따라서 프로젝트를 프로젝트를 자세히 살펴보기에 적당한 시기입니다. 출시되기 몇 개월 전입니다.

적시 컴파일은 장기 실행 프로세스 등에 매우 최적화되었지만, 특정 컴파일 사례보다 사전 컴파일된 애플리케이션이 훨씬 더 나은 성능을 발휘하는 경우도 있고 이에 대해서는 Codelab에서 다룰 것입니다.

학습 목표

  • Cloud Shell 사용하기
  • Cloud Run API 사용 설정
  • Spring Native 앱 만들기 및 배포
  • 이러한 앱을 Cloud Run에 배포

필요한 항목

설문조사

본 가이드를 어떻게 사용하실 계획인가요?

읽기만 할 계획입니다 읽은 다음 연습 활동을 완료할 계획입니다

자바 사용 경험이 어떠셨나요?

초급 중급 고급

귀하의 Google Cloud 서비스 사용 경험을 평가해 주세요.

초급 중급 고급

2 배경

Spring Native 프로젝트는 여러 기술을 활용하여 개발자에게 네이티브 애플리케이션 성능을 제공합니다.

Spring Native를 완벽하게 이해하려면 몇 가지 구성요소 기술, 이러한 기능의 사용 방법, 이들이 함께 작동하는 방식을 이해하면 도움이 됩니다.

AOT 컴파일

개발자가 컴파일 시간에 javac를 정상적으로 실행하면 .java 소스 코드가 바이트 코드로 작성된 .class 파일로 컴파일됩니다. 이 바이트 코드는 자바 가상 머신에서만 이해할 수 있도록 JVM을 통해 다른 머신에서 이 코드를 해석해야 코드를 실행할 수 있습니다.

이 프로세스는 자바의 서명 이식성을 제공하는 역할을 합니다. 즉, '한 번 작성하여 어디서나 실행'할 수 있지만, 네이티브 코드를 실행하는 경우에 비해 비용이 많이 듭니다.

다행히 JVM의 대부분 구현에서는 이러한 해석 비용을 완화하기 위해 적시 컴파일을 사용합니다. 이는 함수의 호출을 계산하여 이루어지며 임계값 ( 기본값: 10,000)을 전달할 만큼 충분한 빈도로 호출되는 경우 런타임 시 네이티브 코드로 컴파일되어 추가 비용이 드는 해석을 방지합니다.

AOT 컴파일은 컴파일 가능한 시간에 도달할 수 있는 모든 코드를 네이티브 실행 파일로 컴파일하는 반대의 접근 방식을 취합니다. 런타임 시 메모리 효율성과 기타 성능 향상을 위해 이식성을 사용합니다.

5042e8e62a05a27.png

물론 이는 장단점을 가지며 항상 가치가 있는 것은 아닙니다. 그러나 다음과 같은 특정 사용 사례에서는 AOT 컴파일이 빛을 발할 수 있습니다.

  • 시작 시간이 중요한 단기 애플리케이션
  • 메모리 제약이 있는 환경(JIT가 너무 비쌉니다.)

흥미로운 사실은 JDK 9에서 실험용 기능으로 AOT 컴파일이 도입되었다는 의미이지만, 이 구현은 유지하는 데 비용이 많이 들고 잘 포착되지 않으므로 조용히 삭제되었습니다. 자바 17에서 GraalVM을 사용하는 개발자만을 위한 옵션입니다.

GraalVM

GraalVM은 매우 빠른 시작 시간, AOT 네이티브 이미지 컴파일, 개발자가 여러 언어를 단일 애플리케이션에 혼합할 수 있는 다국어 기능 기능을 자랑하는 고도로 최적화된 오픈소스 JDK 배포입니다.

GraalVM은 활발한 개발 중이며 새로운 기능을 얻고 기존 기능을 항상 개선하고 있으므로 개발자는 계속 관찰할 것을 권장합니다.

최근 주요 목표는 다음과 같습니다.

  • 사용자 친화적인 신규 네이티브 이미지 빌드 출력 ( 2021-01-18)
  • 자바 17 지원 ( 2022-01-18)
  • 다단계 컴파일을 개선하기 위해 기본적으로 다중 계층 컴파일 사용 설정 ( 2021-04-20)

스프링 네이티브

간단히 말해, Spring Native는 GraalVM의 네이티브 이미지 컴파일러를 사용하여 Spring 애플리케이션을 네이티브 실행 파일로 변환할 수 있게 해줍니다.

이 과정에서 컴파일 시간에 애플리케이션의 정적 분석을 수행하여 진입점에서 도달할 수 있는 애플리케이션의 모든 메서드를 찾습니다.

이렇게 하면 애플리케이션의 '닫힌 세계' 개념이 만들어집니다. 즉, 모든 코드가 컴파일 시간에 알려진 것으로 간주되고 런타임에 새 코드를 로드할 수 없습니다.

네이티브 이미지 생성은 일반 애플리케이션을 컴파일하는 것보다 시간이 오래 걸리고 자바의 특정 측면에 제한을 부과하는 메모리 집약적인 프로세스입니다.

경우에 따라 애플리케이션이 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 Boot 2.6.4를 사용합니다.

GraalVM 21.0.3이 출시되었으므로 이 샘플에서도 자바 17을 사용할 수 있습니다. 이 튜토리얼에서는 자바 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를 사용하여 기준 이미지(6ecb403e9af1482e.png)의 크기를 알 수도 있습니다.

앱을 실행하려면 다음 단계를 따르세요.

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의 중앙 저장소에서 사용할 수 없습니다.

이렇게 하려면 선택한 요소를 Google의 pom.xml에 추가해야 합니다.

Repo에 다음 저장소 및 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 기반 종속 항목을 추가합니다. 참고: 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 네이티브 앱 빌드 및 실행

이 모든 작업이 완료되면 이미지를 빌드하고 네이티브 컴파일 앱을 실행할 수 있습니다.

빌드를 실행하기 전에 다음 사항에 유의해야 합니다.

  • 일반 빌드보다 몇 분 더 오래 걸립니다 (d420322893640701.png).
  • 이 빌드 프로세스는 많은 메모리 (몇 기가바이트)를 차지할 수 있습니다. cda24e1eb11fdbea.png
  • 이 빌드 프로세스를 사용하려면 Docker 데몬에 연결할 수 있어야 합니다.
  • 이 예에서는 이 과정을 수동으로 진행하는 동안 네이티브 빌드 프로필을 자동으로 트리거하도록 빌드 단계를 구성할 수도 있습니다.

이미지를 빌드하려면 다음 안내를 따르세요.

mvn spring-boot:build-image

빌드가 완료되면 네이티브 앱의 실제 작동 방식을 확인할 수 있습니다.

앱을 실행하려면 다음 단계를 따르세요.

docker run --rm -p 8080:8080 demo:0.0.1-SNAPSHOT

이 시점에서 네이티브 애플리케이션 방정식의 양면을 모두 확인할 수 있습니다.

컴파일 시간에 약간의 시간과 추가 메모리 사용량이 확보되었지만, 대신 훨씬 더 빠르게 시작할 수 있고 애플리케이션에 따라 메모리를 훨씬 많이 소비할 수 있는 애플리케이션이 제공됩니다.

docker images demo를 실행하여 네이티브 이미지의 크기를 원본과 비교하면 급격한 감소를 확인할 수 있습니다.

e667f65a012c1328.png

또한 더 복잡한 사용 사례에서는 런타임 시 앱이 수행할 작업을 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

앱을 네이티브 이미지로 빌드하고 배포했기 때문에 애플리케이션이 실행되고 있을 때 인프라 비용을 효율적으로 사용하고 있다는 사실에 안심할 수 있습니다.

기준 앱의 시작 시간을 이 새로운 네이티브 앱과 직접 비교해 보세요.

6dde63d35959b1bb.png

7 요약/삭제

Google Cloud에서 Spring Native 애플리케이션을 빌드하고 배포한 것을 축하드립니다.

이 튜토리얼을 통해 Spring Native 프로젝트에 더 익숙해지고 향후 요구사항을 충족할 때 이 사항을 염두에 두시기 바랍니다.

선택사항: 서비스 정리 또는 사용 중지

이 Codelab용 Google Cloud 프로젝트를 만들었든 기존 프로젝트를 재사용하든 Google에서 사용한 리소스의 불필요한 요금이 발생하지 않도록 주의하세요.

생성한 Cloud Run 서비스를 삭제 또는 사용 중지하거나, 호스팅한 이미지를 삭제하거나, 전체 프로젝트를 종료할 수 있습니다.

8 추가 리소스

Spring Native 프로젝트는 현재 신규 및 실험용 프로젝트이지만, 얼리 어답터가 문제를 해결하고 관여하는 데 도움이 되는 훌륭한 리소스가 이미 많이 준비되어 있습니다.

추가 리소스

다음은 이 튜토리얼과 관련이 있을 수 있는 온라인 리소스입니다.

라이선스

이 작업물은 Creative Commons Attribution 2.0 일반 라이선스에 따라 사용이 허가되었습니다.