우당탕탕 개발일지
[AWS] ECR + Lambda + API Gateway(feat. Cold Start) 본문
서버리스 방식으로 SpringBoot를 쉽게 배포할 수 있는 방법이 없을까 찾아보다가 AWS API Gateway와 Lambda서비스를 사용하기로 하였다.
* Lambda로 SpringBoot 실행 시 Cold Start 가 발생하게 되는데, 이 부분 때문에 Snap Start라는 기능이 추가되었다. 즉, SpringBoot 실행이 가능하지만 Snap Start의 경우 Java만 적용이 가능해 컨테이너 진행에 실패하였다.
AWS Labda 함수 생성 방식은 3가지가 있었고, 이 중 컨테이너 이미지로 함수 생성을 했다. 기존에 도커 허브에 이미지를 올려뒀었는데, Lambda의 경우 ECR 이미지 URI가 필요하여 ECR로 변경해주었다.
Github Action을 통해 Docker 이미지를 생성한 후 AWS ECR에 푸쉬한다.
ECR에 푸쉬된 이미지를 기반으로 AWS Lambda 함수 업데이트한다.
API Gateway를 통해 Lambda 함수와 클라이언트를 연결한다.
1. AWS AccessKey
깃허브 액션에서 AWS에 접근하기 위해서는 AWS 액세스 키가 필요하다. 액세스 키를 생성하기 위해서는 먼저 IAM 사용자를 만들어주어야 한다.
IAM 사용자 생성
그룹 생성에서 정책 생성 페이지로 넘어가 권한 부여를 해주었다.
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "Statement1",
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage",
"ecr:InitiateLayerUpload",
"ecr:UploadLayerPart",
"ecr:CompleteLayerUpload",
"ecr:DescribeRepositories",
"ecr:DescribeImages",
"ecr:ListImages",
"ecr:CreateRepository"
],
"Resource": "*"
}
]
}
그다음 만들어준 ECR 정책을 부여한 사용자 그룹을 생성하여 적용하면 된다.
AccessKey 생성
생성된 키는 깃허브 등록 과정을 거쳐야 하기 때문에 메모장에 적어둔다.
깃허브 시크릿 키 등록
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
Repository > Settings > Secrets and variables > actions 에서 시크릿 키를 등록해준다. 시크릿 키 이름은 위와 같이 지정해준다.
2. github workflow
사전작업으로 Dockerfile 등록이 필요하다. 다음은 SpringBoot 기준 작성한 Dockerfile이다.
* 참고로 엔트리포인트 jar 파일이 명시된 파일명처럼 나오지 않아서 build.gradle에 추가 설정을 해주었다.
Dockerfile
# Base image 설정
FROM openjdk:21-jdk-slim
# 작업 디렉토리 설정
WORKDIR /app
# 소스 복사
COPY . .
# 실행 권한 추가
RUN chmod +x ./gradlew
# Gradle 빌드 수행 (테스트 제외)
RUN ./gradlew clean build -x test
# JAR 파일 실행 설정
ENTRYPOINT ["java", "-jar", "/app/build/libs/api-service.jar"]
build.gradle
. . .
repositories {
mavenCentral()
}
bootJar {
archiveBaseName = 'api-service' // 실행 가능한 JAR 파일 이름 설정
archiveVersion = '' // 버전 정보 제거
}
이제 워크플로우를 작성해주면 ECR 도커 이미지 푸쉬는 완료된다.
docker-image.yml
name: Docker Image CI
on:
push:
branches: [ "main" ]
pull_request:
branches: [ "main" ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-2 # AWS 리전 선택
- name: Login to Amazon ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: Build, tag, and push image to Amazon ECR
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: easyresponse # ecr 이름
IMAGE_TAG: ${{ github.run_number }} # git 커밋넘버를 이미지 이름 구분을 위해 넣어줌
run: |
# Build a docker container and
# push it to ECR so that it can
# be deployed to ECS.
docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG .
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
빌드가 성공적으로 완료되면 초록불이 들어오고, ECR에도 도커 이미지가 잘 푸쉬된 것을 확인할 수 있다.
마지막으로 푸쉬된 이미지를 AWS Labda와 연결하면 배포는 완료된다.
3. Lambda와 API Gateway 연동
3가지 유형 중 서버에 사용할 API는 가장 저렴한 HTTP API이다. 하지만 API 요청에 인증/인가가 포함되어 있어 REST API를 써야 할지 감이 잡히지 않아 HTTP API로 테스트 해보고 인증/인가가 안될 경우 REST API로 바꾸기로 하였다.
HTTP API
HTTP 메소드를 선택하고 리소스 경로를 루트 경로로 설정한다.
나머지는 기본으로 설정하고 생성하면, 다음과 같이 경로를 생성할 수 있다.
통합 설정으로 Lambda를 설정해주고 기본 스테이지로 배포를 하면 트리거가 생성되어 있는 것을 확인할 수 있다.
(자동 배포 설정이 되어있을 경우 따로 배포해줄 필요는 없다.)
배포된 API로 postman으로 요청을 해봤더니 Lambda 실행까지는 완료되었다.
CloudWatch 로그 확인
실행이 안되어서 CloudWatch를 통해 로그를 확인해본 결과, 프로젝트에서 Redis를 사용했단 사실을 깜빡했었다. 급하게 Redis의 대체제로 AWS ElasticCashe를 사용하여 Lambda 함수와 연결해주었다.
Redis를 추가해줬음에도 불구하고, Cold Start 오류가 발견되었다.
Cold Start란
AWS Lambda는 요청이 발생하는 시점에 인스턴스를 가동하고 코드를 실행한다. 코드 실행을 위해서는 코드 다운로드, 컨테이너 실행, 런타임 실행 등이 선행 작업되어야 하는데, 이러한 작업들을 Cold Start라고 한다. Cold Start 발생 시점은 다음과 같다.
- 첫 번째 요청 시 발생
- 버전 변경 후 배포 시 발생
- 일정 시간 동안 요청이 발생하지 않아 새로운 인스턴스가 실행될 경우 발생
- 동시 호출 발생하여 가용한 인스턴스가 없는 경우 발생
4. 결론
프로비저닝된 동시성 기능
서버리스의 Cold Start는 결국 Lambda가 꺼져있는 상태가 문제였고, 문제를 해결하기 위해서는 프로비저닝된 동시성 기능을 추가해주어야 한다. 하지만 해당 기능을 한달정도 사용하면 ec2의 m6g.12xlarge 인스턴스를 한달 켜놨을 때 가격과 동일한 것을 확인하였다.
SnapStart
SnapStart는 Lambda 배포 시 초기화 단계를 실행하여 전체 기능의 스냅샷 이미지를 생성하고 캐싱한다. SnapStart를 사용하면 Lambda의 실행 시간 단축으로 인해 사용비용도 절감되고 추가 비용도 없다. 그러나 현재 SnapStart는 Java만 지원하여 더이상 진행이 불가능하다 판단하였다.
'Cloud' 카테고리의 다른 글
[MSA] Docker-Compose(2) (1) | 2024.12.17 |
---|---|
[MSA] Dcoker-Compose (1) (4) | 2024.12.15 |
[AWS] S3 + CloudFront + Route53 (feat. AccessDenied) (1) | 2024.12.10 |
Typescript + Firebase 환경 설정 (0) | 2024.08.11 |
네이버 클라우드 Micro Server (0) | 2024.07.10 |