0. Aws Lambda 들어가기,
이번 글은 한 번 써봐야지 써봐야지 했던 Aws Lambda를 사용해 보고 수행 과정, 느낀 점, 앞으로 어떻게 활용하면 좋을 지에 대해 많이 배울 수 있었습니다.
우선 새로운 프로젝트에 들어가기에 앞서, 항상 해봤던 것만 사용하면 실력이 더 늘지 않겠다는 생각이 들어 안 해본 기술들을 리스트업 해보고 해당 기술의 개념과 사용했을 때의 장점을 정리해 보았습니다.
그중 Aws Lambda 서비스가 매력적으로 느껴졌습니다.
- 서비스 기능을 빠르게 배포할 수 있다. (코드 용량 제한이 있지만, 긴 코드의 복잡한 기능은 람다가 아닌 다른 서버로 처리하는 것이 좋아 보임!)
- 월 1,000,000건까지 요청이 무료다.
- 대략 월 백만 건까지 무료로 제공해주고 있고, trigger, 이벤트 기반 서비스 등을 하나의 서버에서 분리하여 운영하면 서버 부하나 성능 측면에서 이점이 있을 수 있겠다 싶었습니다.
저는 새로 시작하는 프로젝트에 람다를 S3 이미지 업로드와 이벤트 처리에 사용해 보고자 Lambda 학습을 시작했습니다.
먼저 S3 이미지 업로드를 해보며, 이전에는 서버에서 S3 Config, Service, S3 key 부분을 관리했어야 했는데 이를 Aws Lambda에서 처리함으로써 더 간편하게 처리할 수 있음을 느꼈습니다.
Lambda를 처음 써보면서 안 익숙했던 환경을 제외하고는 활용할 수 있는 점이 무궁무진하다고 느껴져 요즘 스타트업이나 대기업에서도 많이 사용하고 있구나를 느꼈습니다.
그래서 다음에는 Lambda를 통해 알림과 같은 이벤트 서비스를 처리해보려고 합니다.
1. Aws IAM 역할 설정하기
Aws Lambda에서 S3와 API Gateway와 통신하기 위해 권한을 설정해주어야 합니다.
이에 필요한 권한들을 묶어서 역할을 생성하겠습니다.
2개의 권한을 넣은 역할을 생성하고 역할 이름은 상황에 맞게 직관적으로 작성해 주면 좋습니다. (ex: lambda-s3-upload-role)
역할이 올바르게 생성되었는지 확인해 줍니다.
2. Lambda 함수 생성
Aws Lambda 콘솔에 들어가서 함수 생성을 진행합니다.
함수명도 마찬가지로 제공하는 기능에 대해 직관적으로 작성하는 게 좋습니다.
(저는 Python3.12 를 통해 진행합니다.)
Lambda 함수를 생성할 때 역할을 부여하는데, 기존 역할 사용 버튼을 누른 다음 위에서 생성한 IAM을 적용합니다.
2-1. Lambda function
s3 이미지 업로드에 위해 필요한 코드를 작성합니다.
1. 필요한 라이브러리 확인하기
import json
import os
import boto3
import base64
import uuid
from requests_toolbelt.multipart import decoder
위에서 중요한 것은 boto3와 requests_toolbelt.multipart 부분입니다.
- boto3 : AWS에서 제공하는 SDK로 파이썬에서 AWS 리소스를 접근하는 데 사용합니다.
- requests_toolbelt.multipart : multipart/form-data로 전달받은 데이터를 파싱 하여 파일 데이터와 관련 메타 정보를 추출할 때 사용합니다.
- 해당 라이브러리는 추가되어 있지 않아 Layer 계층에 zip 파일을 넣어서 진행해야 합니다.
- Layer 적용 방법은 아래에서 설명하겠습니다.
BUCKET_NAME = os.environ['dev_bucket']
- os.environ[’name’] or os.environ.get(’name’)을 통해 람다에 설정한 환경변수를 읽을 수 있습니다.
- 저는 개발용 함수와 운영용 함수를 분리하여 운영할 예정입니다.
2. S3 Image Upload Function
def upload_file_to_s3(file_content, filename, content_type, path):
s3_filename = f"{path}/{uuid.uuid4()}.{filename.split('.')[-1]}"
try:
s3.put_object(
Bucket = BUCKET_NAME,
Key = s3_filename,
Body = file_content,
ContentType = content_type
)
# 반환할 S3 URL 생성
file_url = f"https://{BUCKET_NAME}.s3.amazonaws.com/{s3_filename}"
return file_url
except Exception as e:
print("Something Happened: ", e)
return e
s3에 저장하기 위해 필요한 정보로는 bucket, 저장할 filename, 파일 데이터, content-type이 필요합니다.
파일을 복 수개 받을 수 있으므로 main handler 함수에서 s3 upload function을 호출하여 처리하도록 하였습니다.
s3에 저장한 파일 주소를 클라이언트에게 전달하여, 최종적으로 API 호출을 했을 때 데이터베이스에는 s3 url이 저장되도록 합니다.
3. handler 함수 적용하기
Lambda에서 handler 함수는 요청을 받아서 응답하는 함수를 의미합니다.
코드 소스 아래에 “런타임 설정” 부분이 있는데, 여기에 handler 함수명을 일치시켜주어야 합니다.
def lambda_handler(event, context):
# header
content_type = event['params']['header']['Content-Type']
path = event['params']['path']['path']
if path not in ALLOWD_PATHS:
return {
'statusCode': 400,
"data": "Invalid path"
}
- multipart/form-data를 전달하는 경우 event 객체에서 body가 아닌 body-json을 통해 접근해야 파일 데이터 정보를 얻을 수 있습니다.
- content_type도 일반적인 경로와 다르게 event.params.header에서 확인할 수 있습니다.
- path variable을 통해 버킷 내 폴더명을 받아 각 이미지를 분리하여 관리하도록 하였습니다.
- params.path 부분에 전달한 pathVariable 정보가 담겨있습니다.
- 저는 /images/{path}로 api를 생성하였습니다.
Decoding을 2번 하는 이유
# Base64 디코딩
body = base64.b64decode(event['body-json'])
# Mutlipart 디코딩
file_data = decoder.MultipartDecoder(body, content_type)
s3_file_urls = []
- 첫 번째 디코딩 (base64.b63decode)
- Lambda 함수로 들어오는 요청은 보통 Aws API Gateway를 통해 전달됩니다. multipart/form-data의 경우 요청 본문이 base64 인코딩된 상태로 전달되어 이를 디코딩 하여 바이너리 데이터로 변환합니다.
- 두 번째 디코딩 (MultipartDecoder)
- 이미지 업로드는 보통 multipart/form-data 형식을 사용합니다. 해당 형식은 파일과 메타데이터가 특정 boundary를 기준으로 분리되어 전송됩니다.
- MultipartDecoder는 이러한 형식의 바이너리 데이터를 디코딩(파싱)하여 파일 데이터와 메타 데이터를 추출합니다.
# all file upload
for file in file_data.parts:
content_disposition = file.headers.get(b'Content-Disposition').decode('utf-8')
filename = content_disposition.split('filename=')[-1].strip('"')
content_type = file.headers.get(b'Content-Type').decode('utf-8')
file_url = upload_file_to_s3(file.content, filename, content_type, path)
if file_url:
s3_file_urls.append(file_url)
return {
'statusCode': 200,
"data": s3_file_urls
}
각 파일의 내용, 이름, content-type을 추출하여 upload_file_to_s3 함수에 전달하여 s3에 이미지를 업로드합니다.
2.2 Lambda Layer 적용하기
필요한 라이브러리가 있는 가상환경을 로컬에서 만든 후 zip 파일을 Layer에 추가해 줍니다.
python 가상환경 만든 후 zip 파일 만들기
python3 -m venv myproject
pip install requests_toolbelt
위 명령어로 가상 환경 생성과 필요한 라이브러리를 설치합니다.
그러면 site-packages 파일이 생성되는 것을 확인할 수 있습니다.
requests_toolbelt가 잘 설치되었는지 확인하고 파일 압축을 진행합니다.
주의할 점, 파일 압축을 할 때 람다에서 인식할 수 있는 폴더 구조여야 합니다.
위와 같이 파일 경로를 수정한 다음 zip 파일을 생성합니다.
저는 처음에 site-packages만 압축하면 되는 줄 알고, 레이어를 추가한 다음에 람다 함수를 테스트하는데 해당 라이브러리를 인식할 수 없다는 문제가 발생했습니다
람다 함수가 읽을 수 없는 경로여서 생기는 문제였습니다. 자세한 내용은 공식문서를 참고하는 것이 좋을 것 같습니다.
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/python-layers.html
적용할 계층을 해당 함수에 추가해주어야 합니다.
사용자 지정 계층을 눌러 우리가 업로드한 환경을 적용합니다.
3. API Gateway 생성하기
Aws API Gateway는 우리가 개발한 Lambda 함수를 외부에서 사용할 수 있도록 해주는 역할을 합니다.
주 작업으로는 원하는 통신 방법의 API Gateway를 생성합니다. (Rest, Websocket 등등)
S3 이미지 업로드 함수의 경우에는 Rest API Gateway를 생성해 줍니다.
그런 다음 리소스를 생성하여, 각 Rest에 대한 메서드를 생성합니다.
1. 리소스 생성하기
원하는 리소스명을 작성해 줍니다.
2. 메서드 생성
생성한 리소스에 필요한 메서드를 추가합니다.
사용하는 HTTP Method를 선택합니다.
생성한 메서드의 메서드 요청, 통합 요청, 통합 응답, 메서드 응답을 보면 각 흐름에 필요한 설정을 진행할 수 있습니다.
해당 API를 누구나 이용하게 된다면 과금이 되고, 잘못된 데이터가 쌓일 수도 있어 권한 또는 api key를 인증을 적용하여 사용자 인증을 진행해야 합니다.
위에서 API 키가 필요함 True로 되어 있는데 기본값은 False입니다.
API 설정에서 이진 미디어 유형에 multipart/form-data를 추가해 줍니다.
3.1 이진 미디어 유형 설정하기 - multipart/form-data
그런 다음 통합 요청 편집 부분에서 해당 multipart/fomr-data 타입을 지정하여 Lambda에서 파일 데이터를 처리할 수 있도록 해줍니다.
4. API Gateway - Lambda 함수 연결 및 배포하기
API 배포를 눌러서 배포를 진행합니다.
기존의 스테이지가 있으면 해당 스테이지에 배포를 진행하고, 없는 경우에는 새로운 스테이지를 생성합니다.
5. API Gateway API Key 생성하기
API를 생성하여 해당 key를 가진 경우에만 통신이 가능하도록 합니다.
해당 key 없이 요청을 보내는 경우 아래에 에러 메시지가 반환됩니다.
{
"message": "Forbbiden"
}
5.1 API Key 생성
5.2 사용량 계획 설정
사용량에 맞게 요율, 버스트, 요청 양을 설정합니다.
5.3 생성한 API key에 사용량 계획 연결하기
5.4 특정 메서드의 메서드 요청 설정
API 키가 필요함 버튼을 활성화하여 보안을 강화합니다.
트러블 슈팅
문제 상황
event에 값이 일반 Json 데이터와 multipart/form-data 데이터를 전달할 때 데이터 형식이 다른 문제가 있었습니다.
event[’body’], event[’headers’]을 읽을 수 없다는 문제가 발생하여, event에 값이 제대로 전달이 안 됐나는 생각에 Lambda와 API Gateway 설정 부분을 다시 확인했습니다.
설정한 부분을 다시 설정하고 확인해 봐도 문제가 없는 것 같아 event에 찍히는 값을 확인해 봐야겠다 싶어 값을 확인해 봤더니 아래처럼 값이 전달되었습니다.
문제 파악
event 객체 찍히는 값을 확인하고, 이진 미디어 형식을 통합 요청에서 설정하여 다르게 전달되는 부분을 확인하였습니다.
body → body-json으로 전달이 되고, headers → params.header 를 통해 값을 확인하고 올바르게 추출할 수 있도록 코드를 수정하였습니다.
evnet 값 더 살펴보기
일반적인 경우
{
"resource": "/images/{path}",
"path": "/images/profile",
"httpMethod": "POST",
"headers": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Cache-Control": "no-cache",
...
},
...,
"body": null,
"isBase64Encoded": false
}
multipart/form-data로 통신한 경우
{
"body-json": "LS0***kdfkk"
"path": {
"path": "profile"
},
"querystring": {},
"header": {
"Accept": "*/*",
"Accept-Encoding": "gzip, deflate, br",
"Cache-Control": "no-cache",
"Content-Type": "multipart/form-data; boundary=--------------------------843445768874416570038807",
...
}
},
"stage-variables": {},
"params": {
...,
"header": { ... }
}
...
}
참고자료
https://docs.aws.amazon.com/ko_kr/lambda/latest/dg/python-layers.html
https://hyun-am-coding.tistory.com/entry/lambdaAPI-gateways3를-이용한-이미지-업로드-API-만들기Python
https://beomi.github.io/2018/11/30/using-aws-lambda-layers-on-python3/
'Aws' 카테고리의 다른 글
Aws Spot Instance란 무엇인가, (0) | 2024.11.26 |
---|