🤖

본 콘텐츠의 이미지 및 내용은 AI로 생성되었습니다.

⚠️

본 콘텐츠의 이미지 및 내용을 무단으로 복제, 배포, 수정하여 사용할 경우 저작권법에 의해 법적 제재를 받을 수 있습니다.

이미지 로딩 중...

Lambda 런타임과 핸들러 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 19. · 12 Views

Lambda 런타임과 핸들러 완벽 가이드

AWS Lambda의 런타임 환경과 핸들러 함수 작성법을 실무 사례로 배웁니다. 다양한 언어별 핸들러 구조와 event, context 객체 활용법, 응답 형식과 의존성 패키징까지 초급 개발자를 위한 완벽 가이드입니다.


목차

  1. 지원_런타임_종류
  2. Python_핸들러_구조
  3. Node.js_핸들러_구조
  4. event와_context_객체
  5. 응답_형식_이해
  6. 의존성_패키징

1. 지원 런타임 종류

입사 한 달 차 신입 개발자 김개발 씨는 팀 회의에서 처음으로 Lambda를 사용하자는 제안을 들었습니다. 팀장님이 말씀하셨습니다.

"우리 프로젝트는 Python으로 할까요, Node.js로 할까요?" 김개발 씨는 Lambda가 여러 언어를 지원한다는 사실조차 몰랐습니다.

AWS Lambda는 다양한 프로그래밍 언어 런타임을 지원하는 서버리스 컴퓨팅 서비스입니다. 마치 다국어를 구사하는 통역사처럼, Lambda는 Python, Node.js, Java, Go 등 여러 언어로 작성된 코드를 실행할 수 있습니다.

각 런타임은 특정 언어 버전과 실행 환경을 제공하며, 프로젝트 요구사항에 맞춰 선택할 수 있습니다.

다음 코드를 살펴봅시다.

# Python 3.12 런타임 예제
import json

def lambda_handler(event, context):
    # Python 런타임에서 기본 제공되는 json 모듈 사용
    return {
        'statusCode': 200,
        'body': json.dumps('Python 3.12 런타임 실행')
    }

# Node.js 20.x 런타임 예제 (JavaScript)
exports.handler = async (event, context) => {
    // Node.js 런타임에서 기본 제공되는 JSON 처리
    return {
        statusCode: 200,
        body: JSON.stringify('Node.js 20.x 런타임 실행')
    };
};

김개발 씨는 회의가 끝난 후 선배 개발자 박시니어 씨에게 다가갔습니다. "선배님, Lambda가 여러 언어를 지원한다는 게 정확히 무슨 뜻인가요?" 박시니어 씨는 친절하게 설명을 시작했습니다.

"좋은 질문이에요. Lambda 런타임이 뭔지 먼저 이해해야 합니다." 런타임이란 무엇일까요?

쉽게 비유하자면, 런타임은 마치 레스토랑의 주방 환경과 같습니다. 이탈리안 레스토랑에는 파스타를 만들 수 있는 도구와 재료가 갖춰져 있고, 한식당에는 김치찌개를 끓일 수 있는 설비가 있습니다.

마찬가지로 Python 런타임에는 Python 코드를 실행할 수 있는 환경이, Node.js 런타임에는 JavaScript 코드를 실행할 수 있는 환경이 준비되어 있습니다. Lambda가 지원하는 주요 런타임을 살펴보겠습니다.

Python 런타임은 현재 3.9, 3.10, 3.11, 3.12 버전을 지원합니다. 데이터 분석, 머신러닝, 백엔드 API 개발에 많이 사용됩니다.

Python의 풍부한 라이브러리 생태계를 그대로 활용할 수 있다는 장점이 있습니다. Node.js 런타임은 18.x, 20.x 버전을 지원합니다.

JavaScript 개발자들에게 친숙하며, 비동기 처리에 강점을 보입니다. 프론트엔드와 백엔드를 같은 언어로 개발할 수 있어 풀스택 개발자들이 선호합니다.

Java 런타임은 Java 8, 11, 17, 21 버전을 지원합니다. 엔터프라이즈 환경에서 검증된 안정성이 필요한 프로젝트에 적합합니다.

기존 Java 프로젝트의 코드를 Lambda로 마이그레이션하기에도 좋습니다. Go 런타임은 Go 1.x를 지원합니다.

빠른 실행 속도와 낮은 메모리 사용량이 특징입니다. 성능이 중요한 마이크로서비스에 자주 사용됩니다.

그 외에도 .NET 런타임 (C#, F#), Ruby 런타임도 지원됩니다. 심지어 커스텀 런타임을 만들어 Rust, PHP 등 다른 언어도 사용할 수 있습니다.

런타임 선택은 어떻게 해야 할까요? 프로젝트 요구사항을 먼저 파악해야 합니다.

팀이 이미 Python에 익숙하다면 Python 런타임을 선택하는 것이 자연스럽습니다. 성능이 최우선이라면 Go를 고려할 수 있습니다.

기존 시스템과의 호환성도 중요한 판단 기준입니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 이미지 처리 서비스를 개발한다고 가정해봅시다. Python의 Pillow 라이브러리를 활용하고 싶다면 Python 런타임을 선택합니다.

실시간 채팅 서비스라면 비동기 처리에 강한 Node.js 런타임이 적합합니다. 금융 시스템처럼 안정성이 중요하다면 Java 런타임을 선택할 수 있습니다.

주의할 점도 있습니다. 각 런타임마다 콜드 스타트 시간이 다릅니다.

Java는 상대적으로 콜드 스타트가 느리고, Python과 Node.js는 빠른 편입니다. Go는 가장 빠릅니다.

또한 런타임 버전은 주기적으로 업데이트되므로, AWS의 지원 종료 일정을 확인해야 합니다. 박시니어 씨의 설명을 들은 김개발 씨는 이제 런타임의 개념을 이해했습니다.

"아, 그래서 팀장님이 언어를 선택하자고 하신 거였군요!" Lambda 런타임을 제대로 이해하면 프로젝트에 가장 적합한 언어를 선택할 수 있습니다. 팀의 기술 스택과 프로젝트 요구사항을 고려하여 현명하게 선택하세요.

실전 팁

💡 - 팀이 이미 익숙한 언어를 우선 고려하세요. 생산성이 가장 중요합니다.

  • 성능이 중요하다면 Go 런타임을, 빠른 개발이 필요하다면 Python이나 Node.js를 선택하세요.
  • AWS 콘솔에서 런타임 지원 종료 일정을 주기적으로 확인하세요.

2. Python 핸들러 구조

김개발 씨는 Python 런타임으로 첫 Lambda 함수를 만들기로 결정했습니다. AWS 콘솔에서 함수를 생성하니 자동으로 생성된 코드가 보였습니다.

"lambda_handler라는 함수가 있는데, 이게 뭐지?" 궁금증이 생긴 김개발 씨는 다시 박시니어 씨를 찾아갔습니다.

핸들러 함수는 Lambda가 이벤트를 받았을 때 실제로 실행되는 진입점입니다. 마치 레스토랑의 주문 접수 담당자처럼, 핸들러는 들어오는 요청을 받아서 처리하고 결과를 반환하는 역할을 합니다.

Python에서는 일반적으로 lambda_handler라는 이름을 사용하며, event와 context 두 개의 매개변수를 받습니다.

다음 코드를 살펴봅시다.

# 기본 Python 핸들러 구조
def lambda_handler(event, context):
    # event: 함수를 트리거한 이벤트 데이터
    # context: 런타임 정보를 담고 있는 객체

    # 이벤트에서 데이터 추출
    name = event.get('name', '방문자')

    # 비즈니스 로직 처리
    message = f"안녕하세요, {name}님!"

    # 응답 반환
    return {
        'statusCode': 200,
        'body': message
    }

박시니어 씨는 모니터를 김개발 씨 쪽으로 돌렸습니다. "핸들러는 Lambda의 심장이에요.

모든 것이 여기서 시작됩니다." 핸들러 함수란 정확히 무엇일까요? 쉽게 비유하자면, 핸들러는 마치 우체국의 집배원과 같습니다.

누군가 편지를 보내면 집배원이 그 편지를 받아서 읽고, 필요한 조치를 취한 뒤 답장을 보냅니다. Lambda 핸들러도 마찬가지입니다.

외부에서 이벤트가 들어오면 핸들러가 그 이벤트를 받아서 처리하고, 결과를 반환합니다. 핸들러가 없던 시절에는 어땠을까요?

전통적인 서버 애플리케이션에서는 main 함수나 애플리케이션 진입점을 직접 설정해야 했습니다. 서버를 띄우고, 포트를 열고, 요청을 받을 준비를 하는 복잡한 과정이 필요했습니다.

하지만 Lambda는 이 모든 것을 자동으로 처리해줍니다. 개발자는 핸들러 함수만 작성하면 됩니다.

Python 핸들러의 구조를 자세히 살펴보겠습니다. 함수 이름은 관례적으로 lambda_handler를 사용합니다.

하지만 이름은 자유롭게 변경할 수 있습니다. my_handler, process_event 등 원하는 이름을 사용해도 됩니다.

단, AWS 콘솔에서 설정을 변경해야 합니다. 첫 번째 매개변수는 event입니다.

이것은 함수를 트리거한 이벤트의 모든 데이터를 담고 있습니다. API Gateway에서 온 HTTP 요청일 수도 있고, S3 버킷에 파일이 업로드된 정보일 수도 있습니다.

event는 Python 딕셔너리 형태로 전달됩니다. 두 번째 매개변수는 context입니다.

이것은 Lambda 런타임에 대한 메타 정보를 담고 있습니다. 함수 이름, 요청 ID, 남은 실행 시간 등을 확인할 수 있습니다.

로깅이나 디버깅할 때 유용합니다. 코드를 한 줄씩 분석해보겠습니다.

먼저 event.get('name', '방문자') 부분을 보면, event 딕셔너리에서 name 키의 값을 가져옵니다. 만약 name 키가 없다면 기본값으로 '방문자'를 사용합니다.

이렇게 하면 데이터가 없을 때 발생하는 에러를 방지할 수 있습니다. 다음으로 비즈니스 로직을 처리합니다.

여기서는 간단히 인사 메시지를 만들었지만, 실제로는 데이터베이스 조회, 외부 API 호출, 파일 처리 등 복잡한 작업을 수행할 수 있습니다. 마지막으로 결과를 반환합니다.

Python 딕셔너리 형태로 statusCode와 body를 포함합니다. 이것이 API Gateway를 통해 클라이언트에게 전달됩니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 사용자 등록 API를 만든다고 가정해봅시다.

핸들러는 event에서 사용자 정보를 추출하고, 데이터베이스에 저장한 뒤, 성공 메시지를 반환합니다. 이메일 인증 코드를 발송하는 Lambda라면, event에서 이메일 주소를 가져와 SES로 메일을 보내고 결과를 반환합니다.

핸들러 이름을 변경하는 방법도 알아두면 좋습니다. AWS 콘솔에서 Lambda 함수 설정으로 들어가면 핸들러 항목이 있습니다.

기본값은 lambda_function.lambda_handler입니다. 앞부분은 파일 이름, 뒷부분은 함수 이름입니다.

만약 함수 이름을 my_handler로 바꾸었다면, lambda_function.my_handler로 설정을 변경해야 합니다. 초보 개발자들이 흔히 하는 실수가 있습니다.

핸들러 함수에서 print 문으로 디버깅하려다가 아무 출력도 보지 못하는 경우입니다. Lambda에서는 print 문 출력이 CloudWatch Logs로 전송됩니다.

콘솔에서 직접 확인할 수 없으니, CloudWatch에서 로그를 확인해야 합니다. 또 다른 실수는 반환값을 제대로 설정하지 않는 것입니다.

API Gateway와 연동할 때는 반드시 statusCode와 body를 포함한 딕셔너리를 반환해야 합니다. 그냥 문자열만 반환하면 오류가 발생합니다.

김개발 씨는 이제 핸들러의 역할을 완전히 이해했습니다. "핸들러가 Lambda의 시작점이고, event로 데이터를 받아서 처리하는 거군요!" Python 핸들러 구조를 제대로 이해하면 Lambda 함수를 효과적으로 작성할 수 있습니다.

event와 context를 적절히 활용하고, 명확한 반환값을 제공하세요.

실전 팁

💡 - 핸들러 이름은 자유롭게 변경 가능하지만, 콘솔 설정도 함께 변경해야 합니다.

  • print 문 출력은 CloudWatch Logs에서 확인하세요.
  • API Gateway 연동 시 반드시 statusCode와 body를 포함한 딕셔너리를 반환하세요.

3. Node.js 핸들러 구조

다음 날, 프론트엔드 팀에서 김개발 씨에게 요청이 들어왔습니다. "JavaScript로 Lambda 함수를 하나 만들어줄 수 있나요?

우리 팀은 JavaScript가 익숙해서요." 김개발 씨는 당황했습니다. Python과 Node.js의 핸들러가 어떻게 다른지 몰랐기 때문입니다.

Node.js 핸들러는 JavaScript 비동기 패턴을 활용한 Lambda 함수의 진입점입니다. Python과 유사하게 event와 context를 받지만, async/await 패턴을 사용하여 비동기 작업을 효율적으로 처리합니다.

마치 주문을 동시에 여러 개 처리할 수 있는 패스트푸드점처럼, Node.js 핸들러는 비동기 작업을 병렬로 처리할 수 있습니다.

다음 코드를 살펴봅시다.

// Node.js 핸들러 기본 구조 (async/await 패턴)
exports.handler = async (event, context) => {
    // event: 트리거 이벤트 데이터
    // context: 런타임 메타 정보

    // 이벤트에서 데이터 추출
    const name = event.name || '방문자';

    // 비동기 작업 예시 (데이터베이스 조회 등)
    const result = await processData(name);

    // 응답 반환
    return {
        statusCode: 200,
        body: JSON.stringify({ message: result })
    };
};

async function processData(name) {
    return `안녕하세요, ${name}님!`;
}

박시니어 씨는 김개발 씨의 고민을 듣고 웃었습니다. "Python과 Node.js는 비슷하면서도 다릅니다.

특히 비동기 처리 부분이 중요해요." Node.js 핸들러의 특징을 살펴보겠습니다. 가장 큰 차이점은 비동기 처리 방식입니다.

Python은 기본적으로 동기 방식이지만, Node.js는 비동기가 기본입니다. 마치 은행 창구와 ATM의 차이와 같습니다.

은행 창구는 한 명씩 순서대로 처리하지만, ATM은 여러 사람이 동시에 사용할 수 있습니다. Node.js도 이처럼 여러 작업을 동시에 처리할 수 있습니다.

함수 선언 방식부터 다릅니다. Python에서는 def lambda_handler(event, context)로 선언했지만, Node.js에서는 exports.handler를 사용합니다.

이것은 Node.js의 모듈 시스템 때문입니다. exports는 이 함수를 외부에서 사용할 수 있도록 내보내는 역할을 합니다.

async 키워드가 함수 앞에 붙어 있습니다. 이것은 이 함수가 비동기 함수라는 것을 의미합니다.

비동기 함수 안에서는 await 키워드를 사용하여 다른 비동기 작업이 완료될 때까지 기다릴 수 있습니다. 예전 방식과 비교해보면 차이가 명확합니다.

Node.js 초기 버전에서는 콜백 패턴을 사용했습니다. callback(null, response) 같은 형태였죠.

이것은 코드가 복잡해지고 가독성이 떨어지는 문제가 있었습니다. 이른바 콜백 지옥이라고 불리는 현상입니다.

이런 문제를 해결하기 위해 async/await 패턴이 등장했습니다. async/await를 사용하면 비동기 코드를 마치 동기 코드처럼 작성할 수 있습니다.

코드 흐름을 이해하기 쉽고, 에러 처리도 try/catch로 간단하게 할 수 있습니다. 현대적인 JavaScript 개발에서는 거의 필수적으로 사용되는 패턴입니다.

코드를 자세히 분석해보겠습니다. 첫 번째 줄의 exports.handler는 Lambda가 호출할 함수를 지정합니다.

함수는 async로 선언되어 있어 내부에서 await를 사용할 수 있습니다. event와 context 매개변수는 Python과 동일한 역할을 합니다.

event.name || '방문자' 부분은 JavaScript의 OR 연산자를 활용한 기본값 설정입니다. event.name이 없으면 '방문자'가 사용됩니다.

Python의 get 메서드와 비슷한 역할입니다. await processData(name) 부분에서 비동기 함수를 호출하고 결과를 기다립니다.

만약 processData가 데이터베이스 조회나 외부 API 호출 같은 작업을 한다면, 그 작업이 완료될 때까지 기다렸다가 다음 줄로 넘어갑니다. JSON.stringify는 중요한 부분입니다.

API Gateway와 연동할 때, body는 반드시 문자열이어야 합니다. JavaScript 객체를 그대로 반환하면 안 됩니다.

JSON.stringify를 사용하여 객체를 JSON 문자열로 변환해야 합니다. 이것을 잊으면 "body must be a string" 오류가 발생합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 외부 날씨 API를 호출하는 Lambda 함수를 만든다고 가정해봅시다.

axios 라이브러리로 API를 호출하고, await로 응답을 기다린 뒤, 결과를 가공하여 반환합니다. 비동기 패턴 덕분에 여러 API를 동시에 호출하고 결과를 모아서 처리하는 것도 쉽습니다.

또 다른 활용 사례는 DynamoDB 같은 데이터베이스 조회입니다. AWS SDK의 모든 작업은 비동기로 동작하므로, async/await 패턴과 완벽하게 어울립니다.

주의할 점이 있습니다. 에러 처리를 빼먹지 마세요.

async 함수 내부에서 발생한 에러는 try/catch로 잡아야 합니다. 그렇지 않으면 Lambda 함수가 실패하고 500 에러를 반환합니다.

특히 외부 API 호출이나 데이터베이스 작업에서는 네트워크 오류가 발생할 수 있으므로 반드시 에러 처리를 해야 합니다. 또한 타임아웃 설정도 중요합니다.

Lambda는 기본적으로 3초 타임아웃이 설정되어 있습니다. 외부 API 호출이 오래 걸린다면 타임아웃을 늘려야 합니다.

하지만 최대 15분이라는 제한이 있으므로, 너무 긴 작업은 Lambda에 적합하지 않습니다. 김개발 씨는 Node.js 핸들러의 구조를 이해했습니다.

"async/await로 비동기 작업을 처리하고, JSON.stringify로 응답을 변환하는 게 핵심이군요!" Node.js 핸들러를 제대로 활용하면 비동기 작업을 효율적으로 처리할 수 있습니다. async/await 패턴과 적절한 에러 처리로 안정적인 Lambda 함수를 만드세요.

실전 팁

💡 - async/await 패턴을 사용하여 비동기 코드를 동기 코드처럼 작성하세요.

  • API Gateway 연동 시 body는 JSON.stringify로 문자열 변환이 필수입니다.
  • 외부 API 호출이나 데이터베이스 작업 시 반드시 try/catch로 에러 처리하세요.

4. event와 context 객체

김개발 씨는 이제 핸들러 함수를 작성할 수 있게 되었습니다. 하지만 실제 프로젝트에서 API Gateway로부터 받은 event 객체를 열어보니 너무 복잡했습니다.

"body는 어디에 있고, 헤더는 어떻게 읽지?" context 객체는 또 언제 사용하는 건지 막막했습니다.

event 객체는 Lambda를 트리거한 소스로부터 전달되는 모든 데이터를 담고 있습니다. context 객체는 함수의 실행 환경과 관련된 메타 정보를 제공합니다.

마치 택배 상자가 event라면, 송장에 적힌 정보가 context입니다. event로 실제 데이터를 처리하고, context로 실행 상태를 모니터링합니다.

다음 코드를 살펴봅시다.

# event와 context 활용 예제
import json

def lambda_handler(event, context):
    # event에서 API Gateway 데이터 추출
    body = json.loads(event.get('body', '{}'))
    headers = event.get('headers', {})
    http_method = event.get('httpMethod', 'GET')

    # context에서 런타임 정보 확인
    request_id = context.request_id
    function_name = context.function_name
    remaining_time = context.get_remaining_time_in_millis()

    # 로깅
    print(f"함수: {function_name}, 요청 ID: {request_id}")
    print(f"남은 시간: {remaining_time}ms")

    # 비즈니스 로직
    user_id = body.get('userId')

    return {
        'statusCode': 200,
        'body': json.dumps({
            'message': '처리 완료',
            'requestId': request_id
        })
    }

박시니어 씨는 김개발 씨의 화면을 보고 고개를 끄덕였습니다. "event 객체가 복잡해 보이죠?

하지만 구조만 이해하면 간단해요." event 객체의 정체를 파헤쳐보겠습니다. event는 Lambda를 호출한 서비스에 따라 구조가 달라집니다.

마치 편지 봉투가 발신인에 따라 다르듯이, API Gateway에서 온 event와 S3에서 온 event는 완전히 다른 형태입니다. 하지만 각 서비스별로 정해진 구조가 있으므로, 한 번 익히면 계속 사용할 수 있습니다.

API Gateway event 구조를 살펴보겠습니다. httpMethod 필드에는 GET, POST, PUT, DELETE 같은 HTTP 메서드가 들어있습니다.

headers 필드에는 요청 헤더 정보가 딕셔너리 형태로 저장됩니다. Authorization 헤더로 인증 토큰을 확인할 수 있습니다.

가장 중요한 것은 body 필드입니다. 클라이언트가 보낸 데이터가 여기에 JSON 문자열로 담겨 있습니다.

주의할 점은 문자열이라는 것입니다. Python 딕셔너리로 사용하려면 json.loads로 파싱해야 합니다.

이것을 잊으면 데이터를 제대로 읽을 수 없습니다. queryStringParameters에는 URL 쿼리 파라미터가 담깁니다.

예를 들어 /users?id=123 같은 요청이라면, event['queryStringParameters']['id']로 '123'을 가져올 수 있습니다. pathParameters는 경로 파라미터를 담습니다.

/users/{userId} 같은 경로에서 userId 값을 추출할 때 사용합니다. S3 event 구조는 또 다릅니다.

S3에 파일이 업로드되면, event['Records'] 배열에 업로드된 파일 정보가 담깁니다. 버킷 이름, 파일 키, 파일 크기 등의 정보를 확인할 수 있습니다.

여러 파일이 동시에 업로드될 수 있으므로 Records는 배열 형태입니다. context 객체는 무엇을 제공할까요?

context는 Lambda 함수의 실행 환경에 대한 정보를 담고 있습니다. 마치 운동선수가 경기 중에 남은 시간을 확인하듯이, Lambda 함수도 context를 통해 자신의 상태를 확인할 수 있습니다.

request_id는 각 요청마다 고유하게 생성되는 ID입니다. 로그를 추적하거나 디버깅할 때 매우 유용합니다.

CloudWatch Logs에서 특정 요청의 로그만 필터링할 수 있습니다. function_name은 현재 실행 중인 Lambda 함수의 이름입니다.

여러 환경(개발, 스테이징, 프로덕션)에서 같은 코드를 사용할 때, 함수 이름으로 환경을 구분할 수 있습니다. get_remaining_time_in_millis는 남은 실행 시간을 밀리초 단위로 반환합니다.

장시간 실행되는 작업을 할 때, 타임아웃 직전에 작업을 중단하고 결과를 저장할 수 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 이미지 리사이징 Lambda를 만든다고 가정해봅시다. S3 event에서 업로드된 이미지 정보를 읽어오고, context의 남은 시간을 확인하면서 리사이징 작업을 진행합니다.

타임아웃이 임박하면 작업을 일시 중지하고 상태를 저장할 수 있습니다. 또 다른 예시로 API 인증 처리가 있습니다.

event의 headers에서 Authorization 토큰을 추출하고, 토큰을 검증한 뒤 사용자 정보를 확인합니다. 검증 실패 시 401 Unauthorized를 반환합니다.

로깅 모범 사례를 알아봅시다. context.request_id를 모든 로그 메시지에 포함시키세요.

그러면 여러 요청이 동시에 처리되더라도 각 요청의 로그를 쉽게 추적할 수 있습니다. print(f"[{request_id}] 처리 시작") 같은 형태로 로깅하면 디버깅이 훨씬 쉬워집니다.

주의할 점도 있습니다. event의 필드에 접근할 때는 항상 get 메서드를 사용하거나 키 존재 여부를 확인하세요.

event['body'] 같은 직접 접근은 키가 없을 때 KeyError를 발생시킵니다. event.get('body', '{}')처럼 기본값을 제공하면 안전합니다.

또한 테스트 이벤트를 만들 때도 실제 서비스의 event 구조와 동일하게 만들어야 합니다. AWS 콘솔에서 제공하는 템플릿을 활용하면 편리합니다.

김개발 씨는 이제 event와 context의 역할을 명확히 알게 되었습니다. "event로 데이터를 받고, context로 상태를 확인하는 거군요!" event와 context를 제대로 활용하면 Lambda 함수를 더욱 견고하게 만들 수 있습니다.

적절한 에러 처리와 로깅으로 안정적인 서비스를 구축하세요.

실전 팁

💡 - event의 body는 JSON 문자열이므로 json.loads로 파싱해야 합니다.

  • context.request_id를 모든 로그에 포함시켜 디버깅을 쉽게 하세요.
  • event 필드 접근 시 get 메서드로 기본값을 제공하여 KeyError를 방지하세요.

5. 응답 형식 이해

김개발 씨가 만든 Lambda 함수를 API Gateway와 연결했는데, 브라우저에서 응답을 받아보니 이상했습니다. 한글이 깨지고, CORS 오류가 발생했습니다.

"분명히 데이터는 잘 반환했는데 왜 이러지?" 박시니어 씨에게 다시 도움을 청했습니다.

Lambda의 응답 형식은 트리거 소스에 따라 다르게 구성해야 합니다. API Gateway와 연동할 때는 statusCode, headers, body를 포함한 특정 구조를 따라야 하며, CORS 설정과 인코딩 처리가 필요합니다.

마치 국제 우편을 보낼 때 정해진 양식을 따라야 하듯이, Lambda 응답도 정확한 형식을 지켜야 제대로 전달됩니다.

다음 코드를 살펴봅시다.

# API Gateway용 응답 형식 (Python)
import json

def lambda_handler(event, context):
    # 비즈니스 로직 처리
    result_data = {
        'userId': 123,
        'name': '김개발',
        'message': '처리 완료'
    }

    # 올바른 응답 형식
    return {
        'statusCode': 200,
        'headers': {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*',  # CORS 설정
            'Access-Control-Allow-Headers': 'Content-Type,Authorization',
            'Access-Control-Allow-Methods': 'GET,POST,PUT,DELETE'
        },
        'body': json.dumps(result_data, ensure_ascii=False)  # 한글 인코딩
    }

박시니어 씨는 김개발 씨의 코드를 보더니 문제를 바로 찾아냈습니다. "응답 형식이 잘못됐네요.

API Gateway는 특정 구조를 요구합니다." Lambda 응답 형식이 왜 중요할까요? Lambda 함수는 다양한 서비스와 연동될 수 있습니다.

마치 번역가가 상황에 맞게 언어를 바꾸듯이, Lambda도 호출한 서비스에 맞는 응답 형식을 제공해야 합니다. API Gateway는 HTTP 응답을 기대하므로, HTTP 응답 형식에 맞춰야 합니다.

statusCode부터 살펴보겠습니다. 이것은 HTTP 상태 코드입니다.

200은 성공, 400은 클라이언트 오류, 500은 서버 오류를 의미합니다. 단순히 200만 쓰는 것이 아니라, 상황에 맞는 코드를 사용해야 합니다.

리소스를 생성했다면 201, 찾을 수 없다면 404, 인증 실패라면 401을 반환합니다. headers 객체는 HTTP 헤더를 설정합니다.

Content-Type은 응답 데이터의 타입을 명시합니다. JSON 데이터를 반환한다면 'application/json'을 사용합니다.

텍스트라면 'text/plain', HTML이라면 'text/html'을 사용합니다. CORS 헤더가 특히 중요합니다.

웹 브라우저는 보안상의 이유로 다른 도메인으로의 요청을 제한합니다. 만약 프론트엔드가 example.com에서 실행되고, Lambda API가 api.myservice.com이라면, CORS 설정 없이는 브라우저가 요청을 차단합니다.

Access-Control-Allow-Origin은 어떤 도메인에서의 요청을 허용할지 지정합니다. '*'는 모든 도메인을 허용하지만, 프로덕션에서는 특정 도메인만 허용하는 것이 안전합니다.

예를 들어 'https://example.com'처럼 명시할 수 있습니다. Access-Control-Allow-Headers는 클라이언트가 보낼 수 있는 헤더를 지정합니다.

Authorization 헤더로 인증 토큰을 받는다면 반드시 포함해야 합니다. Access-Control-Allow-Methods는 허용할 HTTP 메서드를 나열합니다.

GET과 POST만 사용한다면 그것만 명시하면 됩니다. body는 실제 응답 데이터를 담습니다.

중요한 점은 body가 반드시 문자열이어야 한다는 것입니다. Python 딕셔너리나 JavaScript 객체를 그대로 반환하면 오류가 발생합니다.

json.dumps로 JSON 문자열로 변환해야 합니다. 한글 인코딩 문제를 해결해봅시다.

Python의 json.dumps는 기본적으로 한글을 유니코드 이스케이프 시퀀스로 변환합니다. "김개발"이 "\uae40\uac1c\ubc1c" 같은 형태가 됩니다.

이것을 방지하려면 ensure_ascii=False 옵션을 사용해야 합니다. Node.js에서는 JSON.stringify가 자동으로 UTF-8로 인코딩하므로 이런 문제가 없습니다.

하지만 Content-Type 헤더는 여전히 설정해야 합니다. 실제 현업에서는 다양한 응답 시나리오가 있습니다.

성공 응답은 200 또는 201을 사용합니다. 데이터를 조회했다면 200, 새로운 리소스를 생성했다면 201을 반환합니다.

에러 응답은 적절한 상태 코드와 에러 메시지를 포함해야 합니다. 예를 들어 사용자를 찾을 수 없다면 statusCode 404와 함께 "User not found" 메시지를 반환합니다.

페이지네이션 응답은 데이터와 함께 메타 정보를 포함합니다. 총 개수, 현재 페이지, 다음 페이지 URL 등을 함께 반환하면 클라이언트가 편리하게 사용할 수 있습니다.

응답 구조를 표준화하는 것도 좋은 방법입니다. 모든 API가 일관된 응답 형식을 사용하면 클라이언트 개발이 쉬워집니다.

예를 들어 성공 시 {success: true, data: ...}, 실패 시 {success: false, error: ...} 같은 구조를 정하면 예측 가능한 API가 됩니다. 주의할 점이 몇 가지 있습니다.

바이너리 데이터를 반환할 때는 특별한 처리가 필요합니다. 이미지나 PDF 같은 파일을 반환하려면 Base64로 인코딩하고, isBase64Encoded: true 플래그를 설정해야 합니다.

또한 OPTIONS 메서드를 처리해야 합니다. CORS preflight 요청은 OPTIONS 메서드로 들어오므로, 이것을 처리하는 로직이 필요합니다.

일반적으로 200 상태 코드와 CORS 헤더만 반환하면 됩니다. 김개발 씨는 응답 형식의 중요성을 깨달았습니다.

"헤더와 상태 코드, body 형식까지 모두 신경 써야 하는군요!" 올바른 응답 형식을 사용하면 클라이언트와 원활하게 통신할 수 있습니다. CORS 설정과 적절한 상태 코드로 사용자 경험을 개선하세요.

실전 팁

💡 - ensure_ascii=False로 한글 인코딩 문제를 해결하세요.

  • 프로덕션에서는 Access-Control-Allow-Origin을 특정 도메인으로 제한하세요.
  • 상황에 맞는 HTTP 상태 코드를 사용하여 명확한 응답을 제공하세요.

6. 의존성 패키징

김개발 씨는 Lambda 함수에서 requests 라이브러리를 사용하려고 했습니다. 로컬에서는 잘 작동했는데, Lambda에 배포하니까 "No module named 'requests'" 오류가 발생했습니다.

"분명히 설치했는데 왜 안 될까요?" 당황한 김개발 씨가 박시니어 씨를 찾아갔습니다.

의존성 패키징은 Lambda 함수가 필요로 하는 외부 라이브러리를 함께 배포하는 과정입니다. Lambda는 격리된 환경에서 실행되므로, 사용할 라이브러리를 직접 포함시켜야 합니다.

마치 해외여행 갈 때 필요한 물건을 캐리어에 담듯이, Lambda도 필요한 라이브러리를 패키지에 담아서 배포해야 합니다.

다음 코드를 살펴봅시다.

# requirements.txt 파일
requests==2.31.0
boto3==1.28.0
pillow==10.0.0

# Lambda 함수 코드 (lambda_function.py)
import json
import requests  # 외부 라이브러리
import boto3     # AWS SDK

def lambda_handler(event, context):
    # requests로 외부 API 호출
    response = requests.get('https://api.example.com/data')
    data = response.json()

    # boto3로 S3 접근
    s3_client = boto3.client('s3')

    return {
        'statusCode': 200,
        'body': json.dumps(data, ensure_ascii=False)
    }

# 패키징 스크립트 (package.sh)
# pip install -r requirements.txt -t ./package
# cp lambda_function.py ./package
# cd package && zip -r ../lambda.zip .

박시니어 씨는 웃으면서 설명을 시작했습니다. "Lambda는 깨끗한 방과 같아요.

필요한 물건은 직접 가져와야 합니다." 의존성 패키징이 왜 필요할까요? Lambda는 격리된 컨테이너 환경에서 실행됩니다.

마치 새로 이사한 빈 집과 같습니다. Python이나 Node.js 같은 기본 런타임은 제공되지만, 외부 라이브러리는 없습니다.

requests, pandas, numpy 같은 라이브러리를 사용하려면 직접 포함시켜야 합니다. 로컬 개발 환경과 Lambda 환경의 차이를 이해해야 합니다.

로컬에서는 pip install requests로 설치하면 전역 또는 가상환경에 라이브러리가 설치됩니다. 하지만 Lambda는 여러분의 컴퓨터가 아닙니다.

AWS의 서버에서 실행되므로, 그곳에는 여러분이 로컬에 설치한 라이브러리가 없습니다. 패키징 방법을 단계별로 살펴보겠습니다.

첫 번째 단계는 requirements.txt 파일 작성입니다. 이 파일에 필요한 라이브러리와 버전을 명시합니다.

버전을 명시하지 않으면 최신 버전이 설치되는데, 나중에 호환성 문제가 발생할 수 있으므로 버전을 지정하는 것이 좋습니다. 두 번째 단계는 라이브러리 설치입니다.

pip install -r requirements.txt -t ./package 명령을 실행합니다. -t 옵션은 설치 대상 디렉토리를 지정하는 것으로, package 폴더에 라이브러리들이 설치됩니다.

세 번째 단계는 코드 복사입니다. Lambda 함수 코드를 package 폴더에 복사합니다.

라이브러리와 코드가 같은 폴더에 있어야 합니다. 네 번째 단계는 압축입니다.

package 폴더의 내용을 zip 파일로 압축합니다. 주의할 점은 폴더 자체가 아니라 폴더 내용을 압축해야 한다는 것입니다.

package 폴더로 이동한 뒤 zip -r ../lambda.zip . 명령을 실행합니다.

마지막 단계는 업로드입니다. AWS 콘솔이나 CLI로 zip 파일을 Lambda에 업로드합니다.

Lambda Layer는 더 효율적인 방법입니다. 여러 Lambda 함수가 같은 라이브러리를 사용한다면 어떻게 될까요?

각 함수마다 라이브러리를 포함시키면 중복이 많아지고 배포 시간도 길어집니다. Layer를 사용하면 라이브러리를 한 번만 업로드하고 여러 함수에서 공유할 수 있습니다.

Layer 생성 방법을 알아봅시다. 라이브러리를 python/lib/python3.12/site-packages 경로에 설치합니다.

이 특정 경로 구조가 중요합니다. 그런 다음 이것을 zip으로 압축하여 Layer로 업로드합니다.

Lambda 함수 설정에서 이 Layer를 추가하면, 함수가 Layer의 라이브러리를 사용할 수 있습니다. Docker 컨테이너 이미지를 사용하는 방법도 있습니다.

복잡한 의존성이나 대용량 라이브러리가 필요하다면, Lambda를 Docker 이미지로 배포할 수 있습니다. Dockerfile에 필요한 모든 것을 정의하고, 이미지를 ECR에 푸시한 뒤 Lambda에서 사용합니다.

패키지 크기 제한도 훨씬 여유롭습니다. 실제 현업에서는 어떻게 활용할까요?

데이터 분석 Lambda를 만든다면 pandas, numpy 같은 무거운 라이브러리가 필요합니다. 이런 경우 Layer나 Docker 이미지를 사용하는 것이 좋습니다.

반면 간단한 API Lambda라면 requests 정도만 포함시키면 됩니다. boto3는 특별한 경우입니다.

boto3는 AWS SDK로, Lambda 런타임에 기본 포함되어 있습니다. 따라서 별도로 패키징할 필요가 없습니다.

하지만 특정 버전이 필요하다면 requirements.txt에 명시하여 포함시킬 수 있습니다. 주의할 점이 있습니다.

배포 패키지 크기 제한은 직접 업로드 시 50MB, S3를 통한 업로드 시 250MB입니다. 압축 해제 후 크기도 250MB를 넘을 수 없습니다.

큰 라이브러리를 사용할 때는 이 제한을 고려해야 합니다. 또한 바이너리 호환성도 중요합니다.

Lambda는 Linux 환경에서 실행되므로, Windows나 Mac에서 설치한 라이브러리는 작동하지 않을 수 있습니다. 특히 C 확장이 포함된 라이브러리는 주의해야 합니다.

Docker나 EC2 Linux 인스턴스에서 패키징하면 이 문제를 해결할 수 있습니다. 개발 워크플로우를 개선하는 방법도 있습니다.

CI/CD 파이프라인을 구축하면 패키징과 배포를 자동화할 수 있습니다. GitHub Actions나 GitLab CI로 코드를 푸시할 때마다 자동으로 패키징하고 배포할 수 있습니다.

이렇게 하면 수동 작업을 줄이고 실수를 방지할 수 있습니다. 김개발 씨는 이제 의존성 패키징의 전체 흐름을 이해했습니다.

"라이브러리를 함께 패키징하거나 Layer를 사용하면 되는군요!" 의존성을 제대로 관리하면 Lambda 함수를 안정적으로 운영할 수 있습니다. 프로젝트 규모와 요구사항에 맞는 패키징 방법을 선택하세요.

실전 팁

💡 - Layer를 사용하여 여러 함수에서 라이브러리를 공유하고 배포 시간을 단축하세요.

  • 무거운 라이브러리나 복잡한 의존성이 있다면 Docker 컨테이너 이미지를 고려하세요.
  • Linux 환경에서 패키징하여 바이너리 호환성 문제를 방지하세요.

이상으로 학습을 마칩니다. 위 내용을 직접 코드로 작성해보면서 익혀보세요!

#AWS#Lambda#Runtime#Handler#Serverless

댓글 (0)

댓글을 작성하려면 로그인이 필요합니다.