Spring Boot Application을 AWS Lambda에 올리려면 문제가 있음.

첫 번째 문제는 Spring Boot 애플리케이션을 이벤트 기반으로 만들어야 한다는 것.

AWS Lambda 함수가 호출되면 요청 객체를 핸들러로 보내는데

Spring Boot는 이 요청을 처리할 수 없으므로 앱을 변경해야 한다는 문제가..

 

이 문제를 해결하기 위해 AWS는 개발자가 사용자 지정 핸들러를 생성할 수 있는 라이브러리를 게시했다.

 


 

aws-serverless-java-container 라이브러리를 사용하여

AWS Lambda에서 Spring Boot 2 애플리케이션을 실행할 수 있습니다.

Lambda 핸들러 내의 라이브러리를 사용하여 Spring Boot 애플리케이션 및 프록시 이벤트를 로드합니다.

 

리포지토리에는 샘플 Spring Boot 애플리케이션이 포함되어 있습니다.

Serverless Java Container는 Spring Boot 버전 2.2.x, 2.3.x, 2.4.x 및 2.5.x에 대해 테스트 되었고,

Serverless Java Container 버전 1.4부터 WebFlux 애플리케이션도 지원합니다.

 

Maven archetype

 

아래 명령어를 통해 project를 생성해줍니다.

mvn archetype:generate -DgroupId=my.service -DartifactId=my-service -Dversion=1.0-SNAPSHOT \
       -DarchetypeGroupId=com.amazonaws.serverless.archetypes \
       -DarchetypeArtifactId=aws-serverless-springboot2-archetype \
       -DarchetypeVersion=1.6

 

Manual setup / Converting existing projects

 

<dependency>
    <groupId>com.amazonaws.serverless</groupId>
    <artifactId>aws-serverless-java-container-springboot2</artifactId>
    <version>1.6</version>
</dependency>

 

그러면 aws-serverless-java-container-core 및 aws-lambda-java-core 라이브러리도 자동으로 가져옵니다.
Spring을 사용한 종속성 주입은 함수의 cold start time에 상당한 영향을 미칠 수 있습니다.
이 문제를 해결하기 위해 spring-context-indexer 포함하여 컴파일 타임에 후보 구성 요소 목록을 생성할 수 있습니다.

애플리케이션 패키지에서 Lambda의 RequestStreamHandler 인터페이스를 구현하는 새 클래스를 선언합니다. 프록시 통합으로 API Gateway를 구성한 경우 기본 제공 POJO AwsProxyRequest 및 AwsProxyResponse를 사용할 수 있습니다.

 

다음 단계는 컨테이너 핸들러 객체를 선언하는 것입니다.

라이브러리는 AWS 프록시 이벤트에 대해 SpringBootLambdaContainerHandler 객체를 구성하는 유틸리티 정적 메서드를 노출합니다. 메서드는 Spring Boot의 @SpringBootApplication으로 주석이 달린 클래스를 수신합니다. 개체는 클래스 속성으로 선언되어야 하며 정적이어야 합니다. 이렇게 하면 Lambda가 후속 요청에 인스턴스를 재사용합니다.

 

클래스의 handleRequest 메소드는 이전 단계에서 선언한 핸들러 객체를 사용하여 Spring 애플리케이션에 요청을 보낼 수 있습니다.

 

public class StreamLambdaHandler implements RequestStreamHandler {
    private static SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
    static {
        try {
            handler = SpringBootLambdaContainerHandler.getAwsProxyHandler(Application.class);
            // If you are using HTTP APIs with the version 2.0 of the proxy model, use the getHttpApiV2ProxyHandler
            // method: handler = SpringBootLambdaContainerHandler.getHttpApiV2ProxyHandler(Application.class);
        } catch (ContainerInitializationException e) {
            // if we fail here. We re-throw the exception to force another cold start
            e.printStackTrace();
            throw new RuntimeException("Could not initialize Spring Boot application", e);
        }
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
        handler.proxyStream(inputStream, outputStream, context);
    }
}

위 코드는 mvn으로 생성한 프로젝트에 가보면 자동으로 생성되있는 것을 확인할 수 있다.

 

기본적으로 Spring Boot 프로젝트에는 spring-boot-maven-plugin 및 임베디드 Tomcat 애플리케이션 서버가 포함됩니다. AWS Lambda용 Spring Boot 애플리케이션을 패키징하려면 Spring Boot maven 플러그인이 필요하지 않으며 내장된 Tomcat을 제외하도록 shade 플러그인을 구성할 수 있습니다. serverless-java-container 라이브러리가 대신합니다.

 
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>2.3</version>
            <configuration>
                <createDependencyReducedPom>false</createDependencyReducedPom>
            </configuration>
            <executions>
                <execution>
                    <phase>package</phase>
                    <goals>
                        <goal>shade</goal>
                    </goals>
                    <configuration>
                        <artifactSet>
                            <excludes>
                                <exclude>org.apache.tomcat.embed:*</exclude>
                            </excludes>
                        </artifactSet>
                    </configuration>
                </execution>
            </executions>
        </plugin>
    </plugins>

 

Asynchronous initialization

 

Spring Boot 2 애플리케이션은 시작 속도가 느릴 수 있습니다.

위의 예에서 정적 블록 또는 RequestStreamHandler 클래스의 생성자를 사용하여 초기화 단계 동안 AWS Lambda에서 사용 가능한 더 높은 CPU를 활용하도록 프레임워크를 초기화하는 것이 좋습니다. 그러나 AWS Lambda는 초기화 단계를 10초로 제한합니다. 애플리케이션을 시작하는 데 10초 이상 걸리는 경우 AWS Lambda는 샌드박스가 중단된 것으로 가정하고 새 샌드박스 시작을 시도합니다. 초기화에 사용 가능한 10초를 최대한 활용하고 적시에 Lambda 런타임으로 제어를 반환하기 위해 비동기 초기화를 지원합니다.

public class StreamLambdaHandler implements RequestStreamHandler {
    private SpringBootLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
    
    public StreamLambdaHandler() throws ContainerInitializationException {
        long startTime = Instant.now().toEpochMilli();
        handler = new SpringBootProxyHandlerBuilder()
                .defaultProxy()
                .asyncInit(startTime)
                .springBootApplication(SlowApplication.class)
                .buildAndInitialize();
    }

    @Override
    public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
            throws IOException {
        handler.proxyStream(inputStream, outputStream, context);
    }
}

비동기식 이니셜라이저는 타임스탬프를 사용하여 AWS Lambda가 핸들러를 생성한 후 이미 경과한 시간을 추정합니다. 이러한 이유로 생성자에서 가능한 한 빨리 이 측정을 수행하는 것이 중요합니다. 

 

비동기식 이니셜라이저는 별도의 스레드에서 Spring Boot 애플리케이션 컨텍스트를 시작하고 10초 제한 시간이 만료되기 직전에 AWS Lambda에 제어를 반환합니다. 분명히, 애플리케이션을 시작하는 데 10초 미만이 소요되는 경우 AWS Lambda에 제어 기능을 즉시 반환합니다. Lambda가 첫 번째 이벤트를 보낼 때 Serverless Java Container는 처리를 위해 이벤트를 보내기 전에 비동기 이니셜라이저가 작업을 완료할 때까지 기다립니다. 기본적으로 Serverless Java Container는 추가로 10초 동안 대기합니다. ContainerConfig 객체의 setInitializationTimeout 메소드를 사용하여 이를 변경할 수 있습니다.

 

-

내가 개발할 프로젝트의 경우 초기화가 오래 걸리지 않을 것으로 예상, Async 로 진행하는 코드는 생략하기로..

 

 

 

출처

https://github.com/awslabs/aws-serverless-java-container/wiki/Quick-start---Spring-Boot2

 

 

+ Recent posts