0. 개발환경
Python 3.12
Docker Desktop 4.30
1. 문제의 발생
프로젝트 중 파이썬으로 개발된 AI 기능을 배포해야할 일이 생겼습니다. 항상 유지해야하는 서비스가 아닌 특정 시점에 Batch로 돌아가면되는 기능이라 EC2서버보다 lambda를 이용한 배포를 선택했습니다.
(지금 생각해보니 spot instance로 특정시점에 자동으로 생성했다가 사용하고 지우는 사이클을 짜는게 비용이 더 저렴했을 수도..?)
Python으로 학습된 모델 파일의 사이즈는 650MB로 람다에서 허용하고 있는 패키지 파일의 크기를 훌적 넘었습니다.
구글링을 해본 결과 2가지 해결방법이 있었습니다.
1. 큰 파일은 S3에 올리고, 런타임에 S3에서 다운로드 해서 사용.
2. Docker 컨테이너로 감싸서 ECR에 올리고, 그 이미지로 lambda 함수 생성.
런타임에 600MB라는 파일을 다운로드 받으면 당연히 그만큼 속도가 느려지고, S3 데이터전송 요금까지 이중으로 부과할 수 있습니다. 그렇기에 Docker로 감싸서 lambda에 올리는 방법을 선택했습니다.
2. 구조분석
전체 아키텍처는 아래와 같습니다.
1. 개발된 모델과 lambda_function 파일을 local에서 Docker Image로 만들어줍니다.
2. AWS CLI를 이용해서 ECR에 이미지를 올려주고, 해당 이미지로 lambda 함수를 생성합니다.
3. 백엔드 서버(Spring)가 올라간 EC2에서 lambda함수를 실행할 수 있도록, API Gateway가 lambda 함수를 호출하는 REST API를 생성합니다.
3. 문제의 원인 및 해결
1. [Errno 30] Read-only file system: '/home/sbx_user1051'
{
"errorMessage": "[Errno 30] Read-only file system: '/home/sbx_user1051'",
"errorType": "OSError",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 61, in handler\n model = inferenceModel()\n",
...
" File \"/var/lang/lib/python3.8/os.py\", line 223, in makedirs\n mkdir(name, mode)\n"
]
}
람다에서는 Write가 가능한 디렉토리를 /tmp 로 제한하고 있습니다. python 코드에 포함된 라이브러리에서 실행할 때 외부에서 download 받는 동작이 있는데, 그 과정에서 mkdir을 시도하니 file system permission에 막혀버린 것입니다.
파이썬 코드에서 pretrain된 데이터를 내려받을 때, 그 위치를 '/tmp'로 설정해서 해결할 수 있었습니다.
# lambda_function.py
self.tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased', cache_dir='/tmp')
2. "errorMessage": "RequestId: ... Error: Runtime exited with error: signal: killed"
{
"errorType": "Runtime.ExitError",
"errorMessage": "RequestId: ... Error: Runtime exited with error: signal: killed"
}
Lambda 함수 실행과정에서 발생한 에러입니다. 처음에 Timeout 문제로 제한 시간이 부족한 것 같아 최대 시간인 900s(15분)까지 늘려봤지만, 동일한 에러가 발생했습니다.
확인해보니 제한 시간 뿐만아니라 메모리가 부족해도, 동일한 에러를 전시하는 것을 알 수 있었는데, 'lambda-구성-일반 구성' 설정값을 바꿔줄 수 있었습니다.
참고로 lambda의 요금은 메모리와 사용 시간에 비례해서 부과되기 때문에 비용과 직결되는 부분입니다,
본인은 2GB 메모리로도 병목이 생겨서 3000까지 늘렸고, 아래 요금표를 참고해서, 메모리 X 시간의 비용이 가장 작아지는 메모리를 찾아서 설정해야 경제적으로 사용할 수 있을 것 같습니다.
3. Incorrect path_or_model_id: '/tmp/model_pt'. Please provide either the path to a local folder or the repo_id of a model on the Hub.
{
"errorMessage": "Incorrect path_or_model_id: '/tmp/model_pt'. Please provide either the path to a local folder or the repo_id of a model on the Hub.",
"errorType": "OSError",
"requestId": "184e0e09-d4b5-4931-b6d7-21d19b3330c5",
"stackTrace": [
" File \"/var/task/lambda_function.py\", line 59, in handler\n model = inferenceModel()\n",
" File \"/var/task/lambda_function.py\", line 18, in __init__\n self.model = BertForSequenceClassification.from_pretrained(\"/tmp/model_pt\")\n",
" File \"/var/lang/lib/python3.12/site-packages/transformers/modeling_utils.py\", line 3051, in from_pretrained\n resolved_config_file = cached_file(\n",
" File \"/var/lang/lib/python3.12/site-packages/transformers/utils/hub.py\", line 463, in cached_file\n raise EnvironmentError(\n"
]
}
파이썬 단에서 발생한 에러이지만 Docker와 밀접한 관련이 있었습니다.
후술할 Dockerfile 설정과정에서의 에러인데, dockerfile에서 WORKDIR이 어디로 설정되어있는가와 관련되어있는 문제입니다. local에서 model_pt라는 디렉토리를 docker에 포함시키면, python에서는 어떤 path로 이 디렉토리를 찾을 수 있을까요? aws-lambda-base-images에서는 WORKDIR /var/task 로 사전 설정이 되어있었습니다.
# Dockerfile
COPY model_pt ./model_pt
이런식으로 local의 디렉토리를 docker image로 옮겼다면, 파이썬에서는 아래와 같이 /var/task 아래의 폴더로 경로를 지정해줘야합니다.
# lambda_function.py
model_path = "/var/task/model_pt"
self.model = BertForSequenceClassification.from_pretrained(model_path)
os.listdir을 코드 중간에 넣어서 디버깅하면 편하니 참고하면 좋을 것 같습니다.
4. 전체 설정 과정
1. Dockerfile 생성
# Dockerfile
FROM amazon/aws-lambda-python:3.12
RUN /var/lang/bin/python3.12 -m pip install --upgrade pip
COPY lambda_function.py ./
COPY requirements.txt ./
COPY model_pt ./model_pt
RUN pip install --no-cache-dir -r requirements.txt
CMD ["lambda_function.handler"]
aws에서 제공하는 base image를 활용해줍니다. 필요한 파일을 로컬에서 COPY로 image에 포함시키고, requirements.txt에 기록된 라이브러리를 사전에 다운받아줍니다.
2. lambda_function.py 생성
# lambda_function.py
...
class inferenceModel:
def __init__(self):
...
self.tokenizer = BertTokenizer.from_pretrained('bert-base-multilingual-cased', cache_dir='/tmp')
model_path = "/var/task/model_pt"
self.model = BertForSequenceClassification.from_pretrained(model_path)
...
def handler(event, context):
try:
body = event.get("body-json", {})
data = body.get("data", "")
if not data:
raise ValueError("No data provided")
model = inferenceModel()
_id, _mask = model.preprocess(data)
result = model.inference(_id, _mask)[0]
return {
'statusCode': 200,
'body': int(result)
}
except Exception as e:
return {
'statusCode': 400,
'body': json.dumps(f"Error: {str(e)}")
}
lambda 양식에 맞춰서 handler 함수를 작성해줍니다.
3. ECR(Amazon Elastic Container Registry) Repo 생성
AWS 웹 콘솔에서 리포지토리를 생성해줍니다.
4. 로컬에서 Image build 및 AWS ECR 로그인 후 Push
* local 환경에 docker desktop이 설치되어 있어야합니다.
* 미리 IAM 설정과 AWS CLI 로그인이 되어 있어야합니다. 관련해서는 다른 블로그에 많이 있으니 참고바랍니다.
docker build -t <ECR Repo URI> .
aws ecr get-login-password --region <리전 코드> | docker login --username AWS --password-stdin <계정 ID>.dkr.ecr.ap-northeast-2.amazonaws.com
docker push <ECR Repo URI>
5. Lambda 함수 생성
컨테이너 이미지로 lambda 함수를 생성합니다. 같은 리전이어야하고, 아키텍처가 일치해야합니다.
6. API Gateway 생성 및 lambda 와 연결
API Gateway에서 REST API를 구축해줍니다.
메서드 생성에서 원하는 메서드 유형으로 lambda 함수를 호출할 수 있도록 메서드를 만들어줍니다.
우측상단에 API 배포를 해주면 Endpoint를 생성할 수 있고, postman과 같은 API 테스트 툴로 확인할 수 있습니다.
5. 참고문서
* https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/gettingstarted-limits.html
* https://aws.amazon.com/ko/lambda/pricing/
* https://github.com/aws/aws-lambda-base-images/blob/python3.12/Dockerfile.python3.12
'인프라 > AWS' 카테고리의 다른 글
[AWS] AWS 기술 정리 1편 리전과 AZ, IAM - (AWS SAA, Udemy 강의 정리) (0) | 2024.08.09 |
---|---|
[AWS] SQS+Lambda로 task queue 만들기, 반복되는 Long-Run 작업 안정적으로 처리하기 (0) | 2024.07.01 |
[AWS] API Gateway + Lambda 사용하다가 생긴 502에러 'Internal server error' 트러블슈팅 with Terraform (1) | 2024.02.18 |
[AWS] Elastic Beanstalk node package 설치 에러 트러블슈팅 (0) | 2023.08.02 |
[AWS] Elastic Beanstalk 내부 로직, 구성, 네트워크 연결 정리 (0) | 2023.04.12 |