서버리스

jhkim·2023년 12월 10일

최근 인계받게 된 프로젝트가 AWS Lambda를 사용해 배포된 프로젝트라,
서버리스에 대해 정리해 보았다.
AWS Lambda를 기준으로 작성되었다.

서버리스 (Serverless)

서버리스는 서버가 없다? X
서버를 개발자가 관리하지 않는다 O

서버를 항시 켜두는 게 아니고,
요청 발생시 휴면 상태에 있던 함수 실행하는 방식이다.

대표적인 서버리스 서비스

  • AWS Lambda
  • Google Cloud Function
  • Azure Funcitons
  • IBM Cloud Function

몇 가지 더 존재하지만, 우선 이 게시글에서는 Lambda를 위주로 작성했다.

Lambda vs Google Function 비교

  • 배포에 있어서는 Lambda보다 Google Function이 더 간단하다.
    • 그러나 Lambda 설정 과정을 간편화하는 서드 파티 라이브러리가 시중에 많이 있으므로, 필요에 따라 사용하면 된다.
  • 성능 면에서는 Cloud Function보다 Lambda가 약간 우위에 있다.
  • 무료로 사용할 수 있는 레벨에서는 Google Cloud Function이 더 좋지만, 이후 금액이 발생할 때부터는 Google Cloud Function이 조금 더 비싸다고 한다.

서버리스 동작원리

각 Lambda함수마다 EC2기반의 개별 컨테이너에서 독립적으로 실행된다.
하나의 실행환경은 하나의 람다가 점유하여 1대1 대응되고, 이로 인해 같은 람다 함수가 종료 전 연달아 호출됐을 경우 새 컨테이너에서 병렬적으로 실행된다.

또한 각 람다 컨테이너는 복수의 가용 영역에서 실행되고, Worker Manager에 의해 항상 실행 가능한 워커를 관리하므로 Host Failure에 대해 고가용성을 제공한다.

내부적으로는 각 요청을 어느 AZ의 Frontend로 전달할지 결정하는 Load balancer,

요청을 전달받고 검증한 후 전달하는 Frontend,

요청을 받기에 괜찮은 상태인지 확인하고, 워커에 요청을 할당하는 Worker Manager

실제 코드를 실행하는 Worker 등 많은 구성요소로 관리된다.

라이프사이클


람다의 실행환경에 대한 이해를 위해 라이프사이클을 이해하는게 필수적이다.

1. INIT

  • Extension, Runtime, Function에 대한 초기화 동작 진행
  • Extension init - CloudWatch 모니터링, Lambda Extension, 보안 작업 등의 Extension
  • Runtime init - 코드 실행을 위해 필요한 요소들을 부트스트랩. 새로운 Execution Environment를 생성함
  • Function init - 핸들러 함수 준비
  • Warm Start할 경우, INIT단계는 완료된 상태

2. INVOKE

  • 실제 람다 함수 핸들러 호출
  • 이 과정에서 시간 초과 및 장애 발생시 실행 환경 재설정 (Shutdown과 같이 동작)

3. SHUTDOWN

  • 일정 시간 이상 호출을 받지 않을 시 발생
  • 런타임 및 Extension을 종료시키고, 최종적으로 실행 환경 종료
  • 일정 시간 동안 Execution Environment 유지 → Warm Start를 위함

여기서 Lambda가 실행 환경을 바로 내리지 않는다는 점에 유의해야 한다.
이 점에서 Cold Start와 , Warm Start가 구별되고
성능 차이를 발생시킨다.

Cold Start, Warm Start❓

Warm Start: 이미 실행 준비가 완료된 상태

  • 람다는 Shutdown이후 Execution Environment를 일정 시간 유지함

Warm Start에서는 실행 환경을 재사용하므로, 컨테이너가 종료되기 전 호출이 다시 발생하면 INIT단계를 스킵할 수 있다.
바로 핸들러 호출 단계로 접어들기 때문에, Cold Start에서 발생하는 Latency를 피할 수 있다.

동시 요청이 들어올 경우, 실행 환경을 병렬적으로 구성한다.
현재 준비된 실행 환경이 내려가기 전에 새 요청이 들어오면, 해당 실행 환경을 재사용하지만
그 시간에 Invoke단계라면 새 컨테이너가 필요하다.
Lambda의 경우 동시 실행 디폴트 1000개 제한이 걸려 있다.

Execution Environment 유지할 경우?

실행환경이 유지되면 아래와 같은 영향이 있다.

  • 전역 변수 유지 → 핸들러간 데이터 공유
  • 함수 종료시 완료되지 않은 콜백함수가 다음 함수 호출에서 실행됨
  • 각 실행 환경마다 제공되는 /tmp디렉토리의 디스크 공간 공유

트리거


이렇게 람다의 실행 방식을 살펴보았다. 이제 "그래서 대체 언제 실행되는건데?"에 대한 답을 해보자.
람다는 호출 트리거에 의해 실행된다. 이 실행 트리거는 여러 가지 종류가 있다.

트리거 종류

  • API Gateway로 API호출
  • S3버킷 데이터 변경 발생 (업로드, 삭제 등)
  • CloudWatch 이벤트 - EC2인스턴스 상태 변화, Batch, Step Functions 작업 완료 이벤트 등을 감지
  • DynamoDB 테이블에 새 항목 추가, 수정 등
  • Kinesis 데이터 스트림에 데이터 전송

예시: S3 버킷(이벤트소스)에 파일 업로드 → 파일 생성 이벤트 트리거 → 람다 함수 실행

람다 모델


  • 동기 방식
    • 요청에 대해 동기적 응답
    • 재시도에 대한 처리는 애플리케이션 내에서 관리해야 함
    • 예시: Alexa, CloudFront, API Gateway 등
  • 비동기 방식
    • CloudWatch, S3, SNS 등을 이벤트 트리거로 구성 → 이벤트 발생시 람다함수 비동기 실행
    • 기본적으로 2회까지 재시도함
    • 예시: CloudWatch Logs, SNS 등
  • 스트림 베이스
    • Kinesis 데이터 스트림과 결합하여 사용

사용 사례


  • API Gateway를 이용한 HTTP 엔드포인트 구성

  • 이미지 업로드시 썸네일 생성

- S3에 원본 이미지 업로드 → 썸네일 및 리사이징 람다 트리거 → 생성된 썸네일 저장
  • 로그 데이터 파이프라인 구성

  • 로그 데이터 Kinesis Stream으로 로드 → 로그 종류 구분하는 람다 함수 트리거 → 분류된 데이터로 파티션 생성하여 S3에 저장

  • CloudWatch 로그 스트림에서 특정 패턴에 매치 → 알람 전송 혹은 스토리지에 로그 저장하는 람다 트리거

  • 인스턴스 상태 변화에 따른 알람 / 장애 발생시 복구 프로세스 자동화

  • EC2내부 프로세스 장애 발생 → CloudWatch 이벤트 발생
  • SNS를 통해 프로세스 재시작 람다 트리거

- EC2 인스턴스 장애 발생 or 오토 스케일업 → SES sendEmail API를 이용해 메일 전송하는 람다 트리거
  • 배치 작업
    • CloudWatch를 이용한 배치 처리 스케줄링
    • 일정 주기로 람다 트리거

성능 개선

앞서 말했듯 Cold Start는 상당한 latency비중을 차지하므로, 이를 최대한 줄이는 것이 관건이다.
이를 위한 방법들이 있다.
기존에는 VPC ENI로인한 latency가 존재했으나, 이후 VPC접근 방식이 프록시와 같이 개선되어 해당 문제는 해결되었다.


  1. Provisioned Concurrency
    • 특정 수의 Lambda 인스턴스를 초기화된 상태로 유지하여 Warm Start하도록 함
    • 실행 환경 유지한 채 대기
    • 지정한 수 이상으로 동시 요청 발생시 Cold Start 발생 가능
    • 최대 트래픽 시간대를 예상하고 프로비저닝된 동시성 증가 예약 가능
    • 만약 새로 실행 환경을 초기화하는 작업이 필요하다면, 초기화 작업으로 인해 오히려 지연시간 증가할 수 있음
  2. 함수 워밍
    • 일정 주기로 람다를 호출하여 인스턴스를 유지해 Warm Start하도록 함
    • 죽지만 않도록 주기적으로 밥 떠먹이는 방식
    • Lambda 함수를 호출하도록 스케줄링

DB connection 관리


만약 한 람다 함수가 DB커넥션을 맺어 뒀다면,

람다 종료시 DB커넥션이 종료되어야 한다.

그러나 Shutdown이벤트를 코드 상에서 감지할 수 없고 Connection Timeout에 의존하므로, 커넥션을 관리하지 않을 경우 문제가 발생한다.

서버리스 함수가 idle state(게으른 상태)로 진입했을 때 커넥션을 종료하지 않으므로, Idle 커넥션 문제가 발생한다.

또한 여러 람다 함수는 각기 다른 컨테이너에서 실행될 수 있으므로, 커넥선 재사용을 보장할 수 없다.

그렇다면 함수 실행시 DB커넥션을 맺고, 함수 종료시 커넥션 해제해야 하나?

→ 방법 자체가 틀린 건 아니지만, DB커넥션을 맺고 끊는 것은 고비용이다.

이를 해결하기 위해 DynamoDB, RDS 등에서 커넥션 풀링 기능을 하는 DB Proxy 제공하고 있다.

DB연결 전용 프록시 함수를 생성하여, 이 함수를 거치도록 해 각각의 람다 Function이 DB커넥션을 관리하지 않고, 이로 인해 Connection 수를 개선할 수 있다.

또한 이미 연결된 DB세션을 재활용하므로, Cold Start시 재연결 시간을 줄일 수 있다.

과도한 Connection 발생 방지

  • DB 액세스 람다 함수에 대해 account / function 레벨에서 동시성 제한
  • 동적 커넥션 관리 아키텍처 구성 - DB커넥션에 대한 람다 함수를 사용하며 DynamoDB에 메타데이터 저장

만약 RDS, DynamoDB를 쓰지 않는다면 어떻게 커넥션 관리를 하게 되는지 궁금해서 많이 찾아 보았는데, 이렇다 할 명쾌한 해결책은 찾아보지 못했다.
이에 대한 것은 더 조사가 필요할 것 같다!

서버리스 장단점


장점

  • 트래픽에 따른 오토 스케일링
  • 고가용성 보장
  • 사용한 만큼 비용이 발생하므로 저렴 - 사이드 프로젝트에 굿👍
    • 사용량이 많으면 EC2가 더 합리적일 수 있음
  • 배포 및 업데이트가 간편함

단점

  • 플랫폼 종속적 - Lambda에 배포했다면, 다른 서비스로 마이그레이션하기 쉽지 않음
  • 리소스 한계 - 함수로 등록 가능한 메모리 사이즈 한계가 있고, Lambda의 경우 15초 실행 타임 리밋 존재함
  • Stateless - 이벤트 발생시 독립적으로 수행하므로, 전역 데이터를 참조할 수 없음
  • 요청 발생시 실행하는 방식이므로, 미세한 latency 발생
  • Cold Start시의 latency 발생

참고 링크


https://futurecreator.github.io/2019/03/14/serverless-architecture/

https://velog.io/@woo_sae/AWS-Lambda에-대해-알아보자

Kinesis, Lambda를 이용한 로그 파이프라인 구성

Lambda, SNS, Cloudwatch를 이용한 복구 자동화

서버리스 서비스 내부 동작 방식

람다 함수 생명주기, Execution Environment

실행 환경 병렬 프로비저닝

VPC 액세스 - Hyperplane

0개의 댓글