728x90

- 2023년 8월 인턴 업무를 진행하며 IaaS 아키텍처를 FaaS 아키텍처로 바꾸면서
개발팀 내부에 공유했었던 문서를 각색하여 작성하였습니다.
- Fast API 프로젝트에서 서버리스로 마이그레이션하기 위한 방식을 적은 포스팅으로,
본 포스팅은 FastAPI + AWS 환경에서 배포를 진행하는 프로젝트에 최적화되어 있습니다.

 

 

🧍🏻Intro

  • 현재 상황
    • 서버 측에 비디오 처리 요청 시, 한번에 하나의 작업만을 처리하여 워크로드가 많이 듦
    • 문제 상황 재현
      1. 클라이언트가 Merge 작업을 요청하고 해당 작업을 취소
      2. coroutine_tasks 를 통한 Moviepy 작업 수행
    • 🚨 문제 상황 발생
      1. 코루틴의 작업 처리 대기 중 연결 취소 → 큐에 쌓인 채 작업 처리 대기
      2. 이미 취소된 작업임에도 큐에 남아있어서 다른 작업의 Latency가 길어짐 +) 해당 케이스가 아니어도 동시 사용자가 무한히 많아지게 되면 결국 큐에 쌓이는 태스크의 개수는 많아지기 때문에 결국 불가피한 문제임

🤔 관련 의문점 : 코루틴을 쓰는데 왜 병렬 처리가 안되는가

코루틴이 비디오 유닛 하나하나 작업에 대한 동시 처리 제공
But, 병합 작업이 필요한 비디오 묶음에 대해서는 병렬 처리를 하고 있지 않음

 

 


🏃🏻 Try

  1. 애플리케이션 단에 Thread를 도입하고 Thread의 라이프 사이클을 관리하는 로직 추가
  2. IaaS를 FaaS로 마이그레이션

✨ 코드 단에서 작업을 하느냐 관리를 클라우드에게 위임하느냐의 문제인데,
완전한 오토스케일링 지원, 초기 작업 이후 관리 용이성을 위해 서버리스로의 마이그레이션을 결정

 

 

가볍게 용어 정리, Serverless? AWS Lambda?

💡 [What is serverless?]

Serverless is a cloud-native development model that allows developers to build and run applications without having to manage servers.

💡 [What is AWS Lambda?]

AWS Lambda is a compute service that lets you run code without provisioning or managing servers.
Lambda runs your code on a high-availability compute infrastructure and performs all of the administration of the compute resources, including server and operating system maintenance, capacity provisioning and automatic scaling, and logging. With Lambda, all you need to do is supply your code in one of the language runtimes that Lambda supports.

  • 우선 서버리스는 말 그대로 서버가 없다는 뜻으로, 따로 서버를 두지않고 애플리케이션 코드만으로도 애플리케이션 배포가 가능하다.
  • 리소스 관리는 AWS가 해주기때문에 개발자는 애플리케이션 코드만 작성하면 된다.
  • Lambda는 함수 인스턴스를 실행하여 이벤트를 처리하는 FaaS(Function-as-a-Service)이다.
    단순히 애플리케이션 코드만 Lambda에 등록해두면 해당 함수에서 API 호출 및 연결도 가능하다.

 

 

Layer

Lambda 계층 작업 - AWS Lambda

  • 추가 코드 또는 데이터를 포함하기 위해 도입한다.
  • 보통 애플리케이션 실행 중 필요한 패키지들을 미리 설치하는 용도로 사용한다.
  • 레이어 제한
    • 개수 : 5개
    • 용량 : 250MB

 

 


Try 1. 레이어 추가 문제

  • 다음과 같이 mars-video-editor 에서 쓰는 패키지들을 모두 3개로 나눠서 zip파일로 압축했다.
  • zip파일로 압축하는 방법은 zip -r mars-package1.zip python/ 과 같은 명령어를 써서 압축한다.
    • 이때 패키지가 위치하는 디렉토리 경로는 /python/lib/python3.10/site-packages/{패키지명} 다음과 같은 구조가 되어야 패키지를 읽어올 수 있다!
    • 실제로 layer에 추가한 패키지는 런타임 시에 /opt 를 기준으로 읽어올 수 있다.
    • e.g. /opt/python/lib/python3.11/site-packages/{패키지명}
  • 이때 파일 자체로 올릴 수 있는 제한은 50MB까지이다.

∴ 패키지가 너무 무거우면 S3에 업로드해서 해당 리소스의 ARN을 가져와 등록하면 된다.

 

문제가 되는 부분은 “mars-package3”에서 발생했다🥲

위에서 생성한 레이어를 실제 함수와 연결해주면되는데, 아래와 같은 에러가 발생한다.

 

Layers consume more than the available size of 262144000 bytes

전체 레이어의 용량 제한은 262144000 bytes(≒250MB)를 넘을 수 없다는 의미로 레이어 용량이 지나치게 커서 발생한 문제이다.

그렇다면 어떻게 해결하는가…

→ “EFS를 붙여서 Lambda를 구성해라.”

그렇게 시작된 EFS의 길…

 

 


Try 2. EFS Mount

💡 What is Amazon Elastic File System?
Amazon Elastic File System (Amazon EFS) provides serverless, fully elastic file storage so that you can share file data without provisioning or managing storage capacity and performance.

  • AWS에서 별도의 파일 시스템을 제공한다.
  • 여러 인스턴스간 파일 공유 및 병렬 접근을 허용하기 때문에
    AWS 서비스간의 파일 공유가 필요할 때 사용한다.
  • EBS는 인스턴스에 직접 연결되므로 단일 AZ에서만 가능하지만 EFS는 여러 네트워크 상에서 접근할 수 있기 때문에 다중 AZ이다.

 

1) EFS 생성

  • 동일한 VPC망이 구성되어있어야, EFS를 사용할 수 있음
    • ∴ EFS를 사용하는 서비스들끼리는 동일한 VPC 망 내부에서 작동해야한다.

 

1. Lambda 함수를 VPC 내부에서 수행되도록 프라이빗 네트워크로 구성한다.

 

2. EFS를 위한 보안 그룹을 생성한다.

  • NFS 2049번 포트에 대한 인바운드 규칙을 설정한다.
  • 이때, 인바운드의 소스 보안그룹은 직접 EFS에 연결할 EC2의 보안 그룹과 동일한 보안 그룹으로 설정한다.

 

3. 파일 시스템 생성

  • 성능 설정 참고 : ref
    • 성능 모드 : 최대 IOPS, 작업 당 지연 시간 차이 존재
    • 처리량 모드 : 미리 일정 용량을 고정해놓고 사용을 할 건지, 처리량에 따라 유동적으로 다뤄지게 할 건지

 

  • EFS의 네트워크의 보안그룹으로 이전에 만들어둔 EFS를 위한 보안그룹으로 설정한다.

 

  • AWS Lambda에서 해당 EFS에 접근할 수 있는 액세스 포인트를 만든다.
  • 이때 사용자 ID 및 권한 설정을 제대로 해줘야 권한 문제(Permission denied)가 발생하지 않는다.

 

 

2) 인스턴스에 EFS 파일 시스템 탑재

  • DNS로 EFS 파일 시스템 탑재하는 방법

1. EFS 탑재 헬퍼 사용

>> sudo mount -t efs -o tls fs-076455ff6b6dd1014:/ efs

 

 

2. NFS 클라이언트 사용

>> sudo mount -t nfs4 -o nfsvers=4.1,rsize=1048576,wsize=1048576,hard,timeo=600,retrans=2,noresvport fs-076455ff6b6dd1014.efs.ap-northeast-2.amazonaws.com:/ efs

 

3. EFS 탑재 헬퍼를 이용한 EFS 탑재 방법

  1. amazon-efs-utils를 설치한다.
  2. EFS 탑재 헬퍼를 사용하는 방법으로 efs를 탑재한다.
>> sudo mount -t efs -o tls fs-076455ff6b6dd1014:/ efs

 

  • df -h 명령어로 파일 시스템이 정상적으로 탑재되었는지 확인한다.

 

  • Before
Filesystem       Size  Used Avail Use% Mounted on
/dev/root         97G   14G   84G  15% /
tmpfs            3.9G     0  3.9G   0% /dev/shm
tmpfs            1.6G  3.6M  1.6G   1% /run
tmpfs            5.0M     0  5.0M   0% /run/lock
/dev/nvme0n1p15  105M  6.1M   99M   6% /boot/efi
tmpfs            784M  4.0K  784M   1% /run/user/1000

 

  • After
Filesystem       Size  Used Avail Use% Mounted on
/dev/root         97G   14G   84G  15% /
tmpfs            3.9G     0  3.9G   0% /dev/shm
tmpfs            1.6G  3.6M  1.6G   1% /run
tmpfs            5.0M     0  5.0M   0% /run/lock
/dev/nvme0n1p15  105M  6.1M   99M   6% /boot/efi
tmpfs            784M  4.0K  784M   1% /run/user/1000
127.0.0.1:/      8.0E  7.3G  8.0E   1% /efs

 

 

3) EFS 내에 설치된 패키지 Lambda에서 불러오기

💡 여기까지 수행한 목적이 다 Lambda 레이어단에서 파이썬 패키지를 못불러오는 문제에서 발생하였으므로, 필요한 파이썬 패키지들을 EFS 디렉토리에 설치해준다.

이때, 앞서 액세스 포인트로 지정해둔 디렉토리 내에 패키지를 설치해야 Lambda에서 정상적으로 접근이 가능하다.

 

다시 Lambda에서 EFS 파일 시스템 연결, 액세스 포인트 연결을 수행한다.

Lambda 런타임시 EFS 내부 파일들을 읽어올 수 있는 경로를 ‘로컬 탑재 경로’로 지정한다.

→ 절대 경로로 맨 앞에 /mnt 가 반드시 붙어야 제대로 읽어올 수가 있다!

 

 

Try 3. Lambda로 FastAPI 로드

# src/server.py

from fastapi import APIRouter, FastAPI
from mangum import Mangum

from src.routers import edit, sse

app = FastAPI()
api_router = APIRouter()
api_router.include_router(edit.router, tags=["다운로드 API"])
app.include_router(api_router)
api_router.include_router(sse.router, tags=["영상 편집 API"])
app.include_router(api_router)

@app.get("/")
async def root():
    return {"message": "Hello World"}

handler = Mangum(app)
  • mangum 패키지를 import하고 fastAPI 객체를 Mangum으로 감싸준다.

이후, 런타임 설정의 핸들러로 server.handler를 지정해준다.

 

 

  • 패키지 등록
# src/server.py

import sys
from fastapi import APIRouter, FastAPI
from mangum import Mangum

from src.routers import edit, sse

sys.path.append("/mnt/lambda/python3.11/site-packages")

app = FastAPI()
api_router = APIRouter()
api_router.include_router(edit.router, tags=["구간 다운로드"])
app.include_router(api_router)
api_router.include_router(sse.router, tags=["영상 편집 SSE"])
app.include_router(api_router)

@app.get("/")
async def root():
    return {"message": "Hello World"}

handler = Mangum(app)

 

 

Try 4. API Gateway

💡 Lambda를 프라이빗 네트워크 상으로 구동했기 때문에 API Gateway 설정을 통해 외부로 공개해줘야한다.

  • Lambda에 대한 설정을 마쳤으면 API 호출을 통해 Lambda의 서비스를 불러올 수 있게 API Gateway를 연결한다.

  • API Gateway를 생성하는데, aws Lambda를 선택한 후, 다음과 같이 모든 path에 대해 열고 배포를 진행한다.

 

  • Lambda에 API Gateway 트리거를 연결한다.

 

 

Try 5. S3 연결

💡 역시나 Lambda를 프라이빗 네트워크 상으로 구동했기 때문에 S3를 외부로 열어주는 과정이 필요하다.

  • AWS Lambda 권한 설정

  • Lambda에 적용된 전체 권한 내용

 

 


🎊 Result

⌛ Runtime Test

  • Before
MoviePy - Writing audio in 99d5dc98-4225-11ee-a64c-36cf16c8ace3_resultTEMP_MPY_wvf_snd.mp3
chunk:  73%|████████████████████████████████████████████████████████████████████████████████████████▏        | 113/155 [00:03<00:00, 52.59it/s, now=None]
  • After
MoviePy - Writing audio in 46a36b40-4193-11ee-8c3c-957a6bf4c8fc_resultTEMP_MPY_wvf_snd.mp3
chunk:  17%|█▋        | 27/155 [00:19<00:12, 10.18it/s, now=None]

 

 

💥 발생한 문제

  • 기존 방식(IaaS) 52.59it/s 에 비해
  • 서버리스 방식(FaaS) 10.18it/s 에서의 구동 속도가 현저히 차이남

 

 

추가로 해결해야할 문제 상황

  • Video Merge Task 병렬화
  • Video Merge 속도 개선
  • Moviepy 디펜던시 해결

 

 

TBD

  • 서버리스 구성
  • 오케스트레이션
  • 오토스케일링
  • 쓰레드 풀 매니징

 

 

🔗 Reference

728x90