서버리스와 자동화로 빠르게 구축하는 간편한 MVP 가이드

devty·2024년 10월 15일
0

DevOps

목록 보기
11/11

시작하며

안녕하세요. 오랜만입니다. 최근에 서버리스 아키텍처, 자동화, 그리고 MVP(최소 기능 제품) 개발에 관심을 가지게 되면서, 두 가지 프로젝트를 진행하게 되었습니다. 바로 Lambda-trigger-pipelineSpring-lambda-MVP입니다.

이 두 프로젝트를 통해 자동화를 더욱 쉽고 효율적으로 구현하고자 했는데요, 이번 글에서는 제가 왜 이 프로젝트들을 시작하게 되었는지, 어떤 문제를 겪었고 어떻게 해결했는지, 그리고 그 과정에서 무엇을 배웠는지 여러분과 공유하고자 합니다. 자동화와 서버리스 기술에 관심이 있으신 분들이라면 제 경험이 도움이 되길 바랍니다.

프로젝트를 시작한 이유

기존 시스템의 한계

처음에는 기존 시스템들이 유연성이 부족하다고 느꼈습니다. 인프라를 직접 관리하다 보니 반복적인 작업이 많아지고, 빠르게 개발하고 배포하는 데 어려움이 있었습니다. 특히 작은 변경 사항에도 많은 시간을 소모하게 되어 효율성을 높일 방법을 찾고 있었습니다.

서버리스 아키텍처의 매력

이때 서버리스 아키텍처가 눈에 들어왔습니다. 서버 관리를 하지 않아도 되며, 필요한 시점에 필요한 만큼의 리소스를 사용할 수 있다는 점이 매력적이었습니다. AWS Lambda를 사용하면 인프라 관리에 대한 부담을 줄이고 비즈니스 로직에만 집중할 수 있을 것 같았습니다.

프로젝트 아이디어의 탄생

AWS Lambda가 다양한 AWS 서비스와 유기적으로 연결될 수 있다는 점을 알게 되면서, 자동으로 파이프라인을 트리거하는 Lambda-trigger-pipeline 프로젝트를 시작하게 되었습니다. 또한, Spring-lambda-MVP 프로젝트를 통해 Spring Boot와 AWS Lambda를 결합하여 빠르게 MVP를 구축하고 배포할 수 있겠다는 생각이 들었습니다.

전체적인 아키텍처

  • 원래 아키텍처는 마지막 정리 때 넣으려다가 일단 전체적인 그림을 그려두고 시작하는게 나을 것 같아 넣어두겠다.
  • 대충만 이해하고 처음부터 다시 설명하도록 하겠다.

첫 번째 프로젝트: Spring-lambda-MVP

프로젝트 소개

Spring-lambda-MVP는 Spring Boot와 AWS Lambda를 결합하여 빠르게 MVP를 구축하고 배포하는 데 중점을 둔 프로젝트입니다. 서버 설정의 복잡함 없이 비즈니스 로직에만 집중할 수 있어 개발 효율성을 높일 수 있었습니다.

레포지토리 구조

spring-lambda-mvp/
├── src/
│   ├── main/
│   │   ├── java/
│   │   │   └── com.example.demo/
│   │   │       ├── handler/
│   │   │       │   └── StreamLambdaHandler.java
│   │   │       └── user/
│   │   │           ├── User.java
│   │   │           ├── UserController.java
│   │   │           ├── UserRepository.java
│   │   │           └── UserService.java
│   │   └── resources/
│   │       └── application.yml
├── build.gradle
└── gradlew

주요 디렉토리 및 파일 설명

  • src/main/java/com.example.demo/handler/StreamLambdaHandler.java AWS Lambda와의 통합을 처리하는 핸들러 클래스입니다. Lambda 함수가 실행될 때 요청을 받아 Spring 애플리케이션으로 전달하고, 응답을 반환하는 역할을 합니다.
  • src/main/java/com.example.demo/user/ 사용자 관리와 관련된 모든 클래스들이 위치합니다.
    • User.java: 사용자 정보를 담는 엔티티 클래스입니다.
    • UserController.java: 사용자 관련 API 엔드포인트를 제공하는 컨트롤러 클래스입니다.
    • UserService.java: 사용자 비즈니스 로직을 처리하는 서비스 클래스입니다.
    • UserRepository.java: 사용자 데이터를 데이터베이스와 연동하는 리포지토리 인터페이스입니다.
  • src/main/resources/application.yml 애플리케이션 설정 파일로, 데이터베이스 연결 정보 및 기타 설정들이 포함되어 있습니다.
  • build.gradle Gradle 빌드 설정 파일입니다.
  • gradlew Gradle Wrapper 실행 스크립트로, 시스템에 Gradle이 설치되어 있지 않아도 빌드를 가능하게 합니다.

주요 기능 및 코드 설명

  • Spring Boot의 개발 편의성 활용: 익숙한 Spring 생태계를 활용하여 빠르게 애플리케이션을 개발했습니다.
  • Lambda의 서버리스 환경 사용: 서버 관리 없이 자동 확장성과 고가용성을 제공받을 수 있었습니다.
  • 비용 효율적인 배포: Lambda의 종량제 과금 모델로 초기 개발 단계에서 비용을 절감할 수 있었습니다.
  • AWS Lambda 핸들러: StreamLambdaHandler.java
    public class StreamLambdaHandler implements RequestStreamHandler {
        private static final SpringLambdaContainerHandler<AwsProxyRequest, AwsProxyResponse> handler;
    
        static {
            try {
                handler = SpringLambdaContainerHandler.getAwsProxyHandler(Application.class);
            } catch (ContainerInitializationException e) {
                e.printStackTrace();
                throw new RuntimeException("Could not initialize Spring framework", e);
            }
        }
    
        @Override
        public void handleRequest(InputStream inputStream, OutputStream outputStream, Context context)
                throws IOException {
            handler.proxyStream(inputStream, outputStream, context);
        }
    }
    • SpringLambdaContainerHandler를 사용하여 AWS Lambda 환경에서 Spring 애플리케이션을 실행합니다.
    • handleRequest 메서드는 Lambda 함수가 호출될 때마다 실행되며, 입력 스트림을 받아 Spring 컨텍스트로 전달하고 결과를 출력 스트림으로 반환합니다.
    • Application.class는 Spring Boot의 메인 애플리케이션 클래스로, 전체 애플리케이션 컨텍스트를 로드합니다.

MVP 프로젝트의 간단한 API 명세서

번호메소드엔드포인트설명요청 데이터 (JSON)
1GET/users/list모든 사용자 목록 조회없음
2GET/users/{username}특정 사용자 정보 조회경로 변수 username 필요
3GET/users/search사용자 이름으로 검색쿼리 파라미터 username 필요
4POST/users/register새로운 사용자 등록{ "username": "string", "password": "string" }
5POST/users/login사용자 로그인{ "username": "string", "password": "string" }

특이사항

  • Lambda 환경에서의 Spring Boot 실행 Spring Boot 애플리케이션을 Lambda 환경에서 실행하는 데 몇 가지 문제가 있었습니다. 초기 로딩 시간이 길어지는 Cold Start 문제가 대표적이었습니다. 해결 방법:
    • AWS Serverless Java Container 사용: Spring Boot를 Lambda에서 실행할 수 있도록 AWS Serverless Java Container 라이브러리를 활용했습니다.
    • 경량화 노력: 불필요한 의존성을 제거하고, 애플리케이션의 크기를 줄여 Cold Start 시간을 최소화했습니다.

두 번째 프로젝트: Lambda-trigger-pipeline

프로젝트 소개

Lambda-trigger-pipeline은 AWS Lambda를 트리거로 사용하는 CI/CD(지속적 통합 및 배포) 자동화 시스템입니다. 특정 이벤트나 조건이 충족되면 Lambda 함수가 자동으로 파이프라인을 시작하여 코드를 빌드하고 배포합니다.

주요 기능

  • 이벤트 기반 워크플로우: GitHub Actions와 AWS CodePipeline 등 다양한 AWS 서비스를 연동하여 특정 이벤트 발생 시 자동으로 파이프라인이 트리거됩니다.
  • 서버리스 확장성: AWS Lambda를 사용하여 서버 관리의 부담 없이 자동으로 확장되는 시스템을 구축했습니다.
  • 유연한 자동화: 트리거 조건과 파이프라인 단계를 자유롭게 설정하여 원하는 방식으로 자동화를 구현할 수 있습니다.

GitHub Actions 워크플로우 상세 설명

프로젝트 자동화를 위해 세 개의 GitHub Actions 워크플로우 파일을 작성했어요. 각각의 파일이 어떤 역할을 하는지 자세히 설명해드릴게요.

  1. build-and-generate-openapi.yml

    역할: Spring Boot 애플리케이션을 빌드하고, OpenAPI 스펙을 생성하여 S3에 업로드해요.

    주요 내용:

    • 코드 체크아웃: spring-lambda-mvp 레포지토리의 코드를 가져와요.
    • JDK 설정: Java 17 환경을 설정해요.
    • 빌드: Gradle을 사용하여 애플리케이션을 빌드해요.
    • 애플리케이션 실행: Spring Boot 애플리케이션을 백그라운드에서 실행해요.
    • OpenAPI 스펙 생성: 애플리케이션에서 /v3/api-docs 엔드포인트를 호출하여 OpenAPI 스펙을 가져와요.
    • 스펙 인코딩: OpenAPI 스펙 파일을 Base64로 인코딩해요.
    • S3 업로드: 생성된 OpenAPI 스펙과 JAR 파일을 S3 버킷에 업로드해요.

    특이 사항:

    • nohup&를 사용하여 애플리케이션을 백그라운드에서 실행하고, sleep으로 애플리케이션이 시작될 때까지 대기해요.
    • AWS CLI를 사용하여 S3에 파일을 업로드해요.
  2. deploy-lambda.yml

    역할: 빌드된 JAR 파일을 사용하여 Lambda 함수를 생성하거나 업데이트해요.

    주요 내용:

    • Lambda 함수 존재 여부 확인: 함수가 이미 존재하는지 체크해요.
    • 함수 생성 또는 업데이트:
      • 함수가 존재하면 update-function-code로 코드를 업데이트해요.
      • 존재하지 않으면 create-function으로 함수를 생성해요.

    특이 사항:

    • 함수 생성 시 필요한 IAM 역할(ROLE_ARN)과 핸들러 정보를 지정해요.
    • 함수 메모리 크기와 타임아웃 등도 설정해요.
  3. api-gateway-setup.yml

    역할: S3에 업로드된 OpenAPI 스펙을 기반으로 API Gateway를 설정하고, Lambda 함수와 연동해요.

    주요 내용:

    • OpenAPI 스펙 다운로드: S3에서 OpenAPI 스펙 파일을 가져와요.
    • API Gateway 생성 또는 업데이트:
      • API가 이미 존재하면 업데이트하고, 그렇지 않으면 새로 생성해요.
    • 리소스와 메서드 설정:
      • API Gateway의 리소스와 메서드를 순회하면서 각 메서드에 Lambda 통합을 설정해요.
      • 필요에 따라 요청 파라미터를 매핑해요.
    • Lambda 권한 설정:
      • API Gateway가 Lambda 함수를 호출할 수 있도록 필요한 권한을 추가해요.
    • 배포:
      • API Gateway를 특정 스테이지(dev or prod)로 배포해요.

    특이 사항:

    • aws apigatewayaws lambda CLI 명령어를 조합하여 API와 Lambda를 연동해요.
    • 메서드의 요청 파라미터를 추출하여 Lambda 통합 요청 파라미터로 매핑해요.

필요 시크릿 설정

이 파이프라인을 사용하려면 레포지토리에 다음과 같은 시크릿을 설정해야 합니다:

  • PERSONAL_ACCESS_TOKENspring-lambda-mvp 레포지토리에 접근할 수 있는 GitHub Personal Access Token.
  • AWS_ACCESS_KEY_ID: AWS 계정에 대한 접근 키 ID.
  • AWS_SECRET_ACCESS_KEY: AWS 계정에 대한 시크릿 접근 키.
  • AWS_REGION: AWS 리전 정보.
  • LAMBDA_FUNCTION_NAME: 배포할 Lambda 함수의 이름.
  • S3_BUCKET_NAME: 빌드 아티팩트를 저장할 S3 버킷 이름.
  • DB_URL: Spring Boot 애플리케이션의 데이터베이스 URL.
  • DB_USERNAME: 데이터베이스 사용자 이름.
  • DB_PASSWORD: 데이터베이스 비밀번호.
  • DB_DRIVER: 데이터베이스 드라이버.

API Gateway와 Lambda 연동

AWS API Gateway를 통해 Lambda 함수와 HTTP 엔드포인트를 연결하여 RESTful API를 구축했습니다. 아래는 설정한 API 리소스와 메서드의 상세 정보입니다:

리소스 ID메서드경로상태Lambda 권한 ARN
6mbp7tGET/users/list성공arn:aws:execute-api:...pezn0se8t1/*/GET/users/list
dy3qm2GET(Path Parameters)/users/{username}성공arn:aws:execute-api:...pezn0se8t1/*/GET/users/{username}
xfvg26GET(Query String Parameters)/users/search성공arn:aws:execute-api:...pezn0se8t1/*/GET/users/search
m2ahn1POST/users/register성공arn:aws:execute-api:...pezn0se8t1/*/POST/users/register
suqx3sPOST/users/login성공arn:aws:execute-api:...pezn0se8t1/*/POST/users/login

API Gateway와 Lambda 통합 로그

아래는 프로젝트에서 사용된 API Gateway 리소스 설정과 관련된 로그를 정리한 것입니다.

각 리소스 ID에 해당하는 로그를 통해 API 메서드가 AWS Lambda 함수와 어떻게 통합되었는지 확인할 수 있습니다.

  1. 리소스 ID: 6mbp7t

    메서드: GET

    경로: /users/list

    처리 로그:

    Processing METHOD: GET on RESOURCE_ID: 6mbp7t
    {
        "type": "AWS_PROXY",
        "httpMethod": "POST",
        "uri": "arn:aws:apigateway:[지역]:lambda:path/2015-03-31/functions/arn:aws:lambda:[지역]:[계정 ID]:function:[함수 이름]/invocations",
        "passthroughBehavior": "WHEN_NO_MATCH",
        "timeoutInMillis": 29000,
        "cacheNamespace": "6mbp7t",
        "cacheKeyParameters": []
    }
  2. 리소스 ID: dy3qm2

    메서드: GET (경로 파라미터)

    경로: /users/{username}

    처리 로그:

    Processing METHOD: GET on RESOURCE_ID: dy3qm2
    {
        "type": "AWS_PROXY",
        "httpMethod": "POST",
        "uri": "arn:aws:apigateway:[지역]:lambda:path/2015-03-31/functions/arn:aws:lambda:[지역]:[계정 ID]:function:[함수 이름]/invocations",
        "passthroughBehavior": "WHEN_NO_MATCH",
        "timeoutInMillis": 29000,
        "cacheNamespace": "dy3qm2",
        "cacheKeyParameters": []
    }
  3. 리소스 ID: xfvg26

    메서드: GET (쿼리 스트링 파라미터)

    경로: /users/search

    처리 로그:

    Processing METHOD: GET on RESOURCE_ID: xfvg26
    {
        "type": "AWS_PROXY",
        "httpMethod": "POST",
        "uri": "arn:aws:apigateway:[지역]:lambda:path/2015-03-31/functions/arn:aws:lambda:[지역]:[계정 ID]:function:[함수 이름]/invocations",
        "passthroughBehavior": "WHEN_NO_MATCH",
        "timeoutInMillis": 29000,
        "cacheNamespace": "xfvg26",
        "cacheKeyParameters": []
    }
  4. 리소스 ID: m2ahn1

    메서드: POST

    경로: /users/register

    처리 로그:

    Processing METHOD: POST on RESOURCE_ID: m2ahn1
    {
        "type": "AWS_PROXY",
        "httpMethod": "POST",
        "uri": "arn:aws:apigateway:[지역]:lambda:path/2015-03-31/functions/arn:aws:lambda:[지역]:[계정 ID]:function:[함수 이름]/invocations",
        "passthroughBehavior": "WHEN_NO_MATCH",
        "timeoutInMillis": 29000,
        "cacheNamespace": "m2ahn1",
        "cacheKeyParameters": []
    }
  5. 리소스 ID: suqx3s

    메서드: POST

    경로: /users/login

    처리 로그:

    Processing METHOD: POST on RESOURCE_ID: suqx3s
    {
        "type": "AWS_PROXY",
        "httpMethod": "POST",
        "uri": "arn:aws:apigateway:[지역]:lambda:path/2015-03-31/functions/arn:aws:lambda:[지역]:[계정 ID]:function:[함수 이름]/invocations",
        "passthroughBehavior": "WHEN_NO_MATCH",
        "timeoutInMillis": 29000,
        "cacheNamespace": "suqx3s",
        "cacheKeyParameters": []
    }

전체적인 과정 설명 및 결과

Spring Boot 애플리케이션을 AWS Lambda에 배포하고, API Gateway를 통해 HTTP 요청을 처리하는 전체적인 과정을 그림으로 표현해보았습니다:

  1. 트리거 생성: Mvp Project를 Github에 Push를 하여 Github Action을 발생시킨다.
  2. OpenAPI 스펙 업로드: OpenAPI 스펙 파일을 S3 버킷에 업로드합니다.
  3. S3 버킷 확인: S3 버킷 안에는 3개(springboot의 jar파일, openapi의 json파일, openapi을 base64로 만든 json파일)의 파일이 존재합니다.
  4. Lambda 함수 배포: Spring Boot 애플리케이션을 Lambda 함수로 배포합니다.
  5. API Gateway 설정: S3에 저장된 OpenAPI 스펙을 기반으로 API Gateway를 설정합니다.
  6. API Gateway 리소스 확인 1: 제대로 설정이 되었다면, 아래와 같이 여러 리소스가 생성이 됩니다.
  7. API Gateway 리소스 확인 2: 제대로 설정이 되어있다면, Lambda 통합이 된 것으로 파악이 됩니다.
  8. Lambda 트리거 확인: Lambda의 트리거에서도 연결 된 것으로 확인이 됩니다.
  9. 클라이언트 요청 처리: 클라이언트가 API Gateway 엔드포인트로 요청을 보내면, Lambda 함수가 이를 처리하고 응답을 반환합니다.

마무리하며

Lambda-trigger-pipelineSpring-lambda-MVP 프로젝트를 통해 서버리스 아키텍처와 자동화의 성능을 느낄 수 있었습니다.

프로젝트의 상세 내용은 아래 링크를 통해 확인하실 수 있습니다:

읽어주셔서 감사합니다!

profile
지나가는 개발자

0개의 댓글