🤖

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

⚠️

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

이미지 로딩 중...

AWS Lambda 서버리스 배포 완벽 가이드 - 슬라이드 1/7
A

AI Generated

2025. 12. 18. · 6 Views

AWS Lambda 서버리스 배포 완벽 가이드

AWS Lambda를 사용한 서버리스 아키텍처 구축부터 배포 자동화까지, 초급 개발자를 위한 실전 가이드입니다. API Gateway, CORS 설정, 배포 자동화를 단계별로 학습할 수 있습니다.


목차

  1. 서버리스_아키텍처_이해
  2. API_Gateway_설정
  3. Lambda_함수_구성
  4. Bedrock_호출_Lambda
  5. CORS_설정
  6. 배포_자동화_SAM_CDK

1. 서버리스 아키텍처 이해

어느 날 김개발 씨는 회사에서 새로운 프로젝트를 맡게 되었습니다. 팀장님께서 "이번에는 서버리스로 구축해보세요"라고 하셨는데, 도대체 서버리스가 무엇인지 막막하기만 합니다.

서버가 없는데 어떻게 백엔드를 만든다는 걸까요?

서버리스 아키텍처는 서버가 없다는 뜻이 아니라, 개발자가 서버를 직접 관리하지 않아도 된다는 의미입니다. 마치 전기를 사용할 때 발전소를 직접 운영하지 않고 필요한 만큼만 전기를 사용하는 것처럼, 코드 실행에 필요한 만큼만 컴퓨팅 리소스를 사용합니다.

AWS Lambda는 이러한 서버리스 환경에서 코드를 실행할 수 있게 해주는 핵심 서비스입니다.

다음 코드를 살펴봅시다.

// 간단한 Lambda 함수 구조
exports.handler = async (event) => {
    // event: API Gateway 또는 다른 서비스로부터 받은 입력
    console.log('받은 이벤트:', JSON.stringify(event));

    // 비즈니스 로직 처리
    const message = `안녕하세요, ${event.name || '방문자'}님!`;

    // 응답 반환 - statusCode와 body는 필수
    return {
        statusCode: 200,
        body: JSON.stringify({ message })
    };
};

김개발 씨는 지금까지 항상 EC2 인스턴스를 띄워서 서버를 운영했습니다. 새벽에 서버가 다운되면 급하게 재부팅하고, 트래픽이 늘어나면 서버를 추가하는 일이 일상이었습니다.

매달 서버 관리에 많은 시간을 쏟았지만, 정작 코드를 작성하는 시간은 부족했습니다. 선배 개발자 박시니어 씨가 김개발 씨의 고민을 듣고 조언합니다.

"요즘은 서버리스로 많이 개발하는데, 한번 시도해보는 게 어때요?" 서버리스 아키텍처란 무엇일까요? 쉽게 비유하자면, 서버리스는 마치 택시를 타는 것과 같습니다.

자동차를 직접 소유하고 관리하는 대신, 필요할 때만 택시를 불러서 이동하는 것입니다. 주차 걱정도 없고, 정비 걱정도 없습니다.

그저 탑승한 시간만큼만 비용을 지불하면 됩니다. 전통적인 서버 운영 방식에는 여러 문제가 있었습니다.

먼저, 서버를 24시간 가동해야 했습니다. 밤 12시에 사용자가 단 한 명만 접속해도 서버는 계속 돌아가야 했고, 그만큼 비용이 발생했습니다.

두 번째 문제는 확장성이었습니다. 갑자기 트래픽이 몰리면 서버를 추가로 띄워야 했는데, 이 과정이 자동화되어 있지 않으면 서비스가 다운될 수 있었습니다.

바로 이런 문제를 해결하기 위해 서버리스가 등장했습니다. 서버리스를 사용하면 코드를 작성하는 것에만 집중할 수 있습니다.

서버 운영체제 업데이트, 보안 패치, 스케일링 등은 모두 AWS가 알아서 처리해줍니다. 더 중요한 것은 비용 효율성입니다.

코드가 실행되는 시간만큼만 비용을 지불하기 때문에, 사용자가 없을 때는 비용이 전혀 발생하지 않습니다. AWS Lambda는 이러한 서버리스 환경의 핵심 서비스입니다.

Lambda는 이벤트가 발생했을 때만 코드를 실행합니다. 사용자가 API를 호출하거나, 파일이 S3에 업로드되거나, 데이터베이스에 변경이 생기면 Lambda 함수가 깨어나서 작동합니다.

작업이 끝나면 다시 잠들고, 그 짧은 실행 시간만큼만 비용이 청구됩니다. 위의 코드를 살펴보겠습니다.

Lambda 함수는 항상 handler라는 함수를 내보냅니다. 이 함수가 실제로 실행되는 진입점입니다.

event 객체에는 함수를 호출한 서비스(예: API Gateway)로부터 전달받은 모든 정보가 담겨 있습니다. 함수는 처리 결과를 statusCodebody를 포함한 객체로 반환해야 합니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 이미지 썸네일 생성 서비스를 만든다고 가정해봅시다.

사용자가 S3에 원본 이미지를 올리면 Lambda 함수가 자동으로 실행되어 썸네일을 생성합니다. 하루에 이미지가 10개 올라오든 10,000개 올라오든, Lambda는 자동으로 확장되어 처리합니다.

서버를 추가로 띄울 필요가 없습니다. 많은 스타트업이 서버리스를 선택하는 이유가 바로 여기에 있습니다.

초기에는 사용자가 적으니 비용이 거의 들지 않고, 서비스가 성장하면 자동으로 확장됩니다. 하지만 주의할 점도 있습니다.

Lambda는 콜드 스타트 문제가 있습니다. 오랫동안 호출되지 않은 함수는 처음 실행될 때 시작 시간이 조금 걸립니다.

마치 겨울 아침에 차 시동을 걸 때 시간이 걸리는 것과 같습니다. 따라서 실시간 응답이 매우 중요한 서비스라면 이 점을 고려해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. 박시니어 씨의 설명을 들은 김개발 씨는 눈이 반짝입니다.

"서버 관리 안 해도 되고, 자동으로 확장된다니 정말 좋네요!" 서버리스 아키텍처를 제대로 이해하면 더 빠르고 효율적으로 서비스를 만들 수 있습니다. 이제 본격적으로 Lambda를 사용한 서버리스 배포를 배워보겠습니다.

실전 팁

💡 - Lambda 무료 티어는 매달 100만 건의 요청과 40만 GB-초의 컴퓨팅 시간을 제공합니다

  • 짧은 실행 시간을 유지하려면 함수를 작고 단일 책임으로 설계하세요
  • CloudWatch Logs를 활용하면 Lambda 함수의 실행 로그를 쉽게 확인할 수 있습니다

2. API Gateway 설정

김개발 씨가 Lambda 함수를 만들었지만, 웹 브라우저에서 어떻게 호출해야 할지 막막합니다. Lambda 함수는 그 자체로 HTTP 엔드포인트를 제공하지 않기 때문입니다.

선배 박시니어 씨가 다가와 말합니다. "API Gateway를 연결하면 되죠."

API Gateway는 Lambda 함수 앞에서 HTTP 요청을 받아주는 관문 역할을 합니다. 마치 호텔 프론트 데스크가 손님의 요청을 받아 적절한 직원에게 전달하는 것처럼, API Gateway는 클라이언트의 HTTP 요청을 받아 Lambda 함수로 전달합니다.

REST API와 HTTP API 두 가지 타입이 있으며, HTTP API가 더 저렴하고 빠릅니다.

다음 코드를 살펴봅시다.

// API Gateway 이벤트를 처리하는 Lambda 함수
exports.handler = async (event) => {
    // HTTP 메서드 확인 (GET, POST, PUT, DELETE 등)
    const method = event.requestContext.http.method;
    // 경로 파라미터 추출 (/users/123 에서 123)
    const userId = event.pathParameters?.id;
    // 쿼리 스트링 파라미터 (?name=kim에서 kim)
    const name = event.queryStringParameters?.name;
    // POST 요청의 body 데이터
    const body = event.body ? JSON.parse(event.body) : null;

    // 비즈니스 로직
    const result = { method, userId, name, body };

    return {
        statusCode: 200,
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(result)
    };
};

김개발 씨는 첫 번째 Lambda 함수를 성공적으로 배포했습니다. 하지만 문제가 생겼습니다.

AWS 콘솔에서는 테스트 버튼을 눌러 함수를 실행할 수 있지만, 실제 웹 애플리케이션에서는 어떻게 이 함수를 호출해야 할까요? 박시니어 씨가 설명합니다.

"Lambda는 그 자체로 HTTP 엔드포인트가 아니에요. API Gateway를 연결해야 합니다." API Gateway란 무엇일까요?

쉽게 비유하자면, API Gateway는 마치 호텔의 프론트 데스크와 같습니다. 손님이 호텔에 들어와서 "룸서비스 주세요"라고 하면, 프론트 데스크 직원이 적절한 부서에 연락을 취합니다.

손님은 직접 주방이나 청소팀에 연락하지 않아도 됩니다. 모든 요청은 프론트 데스크를 거쳐갑니다.

API Gateway가 없던 시절에는 어땠을까요? 개발자들은 EC2에 웹 서버를 띄우고, Nginx나 Apache를 설정하고, SSL 인증서를 관리해야 했습니다.

트래픽이 늘어나면 로드 밸런서를 추가로 구성해야 했습니다. 이 모든 과정이 복잡하고 시간이 많이 걸렸습니다.

바로 이런 문제를 해결하기 위해 API Gateway가 등장했습니다. API Gateway를 사용하면 몇 번의 클릭만으로 HTTP 엔드포인트를 만들 수 있습니다.

SSL 인증서는 자동으로 발급되고, 트래픽이 늘어나면 자동으로 확장됩니다. 무엇보다 요청 검증, 인증, 사용량 제한 같은 기능을 별도 코드 없이 설정할 수 있습니다.

AWS에는 두 가지 타입의 API Gateway가 있습니다. REST API는 기능이 풍부하지만 비용이 조금 높습니다.

HTTP API는 단순하지만 더 빠르고 저렴합니다. 대부분의 경우 HTTP API로 충분하며, 비용은 REST API의 약 70% 수준입니다.

위의 코드를 한 줄씩 살펴보겠습니다. API Gateway가 Lambda 함수를 호출할 때, event 객체에 요청에 대한 모든 정보를 담아서 전달합니다.

requestContext.http.method를 통해 GET인지 POST인지 확인할 수 있습니다. pathParameters에는 경로에 포함된 변수가 들어있습니다.

예를 들어 /users/{id} 경로로 /users/123을 요청하면 id에 123이 담깁니다. queryStringParameters에는 URL 뒤에 붙는 쿼리 문자열이 들어있습니다.

/users?name=kim&age=30 같은 요청에서 name과 age를 추출할 수 있습니다. POST나 PUT 요청의 경우 body에 JSON 문자열이 담겨오므로, JSON.parse로 객체로 변환해야 합니다.

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

GET /products로 상품 목록을 조회하고, POST /products로 새 상품을 등록하고, **GET /products/{id}**로 특정 상품을 조회합니다. 각 경로마다 다른 Lambda 함수를 연결할 수도 있고, 하나의 함수에서 메서드에 따라 분기 처리할 수도 있습니다.

많은 기업이 API Gateway와 Lambda를 조합하여 마이크로서비스 아키텍처를 구축합니다. 사용자 관리, 상품 관리, 주문 관리 각각을 독립적인 Lambda 함수로 만들고, API Gateway로 하나의 API처럼 통합합니다.

하지만 주의할 점도 있습니다. API Gateway에는 타임아웃 제한이 있습니다.

최대 30초까지만 기다리므로, Lambda 함수가 30초 이상 걸리면 오류가 발생합니다. 긴 작업은 비동기로 처리하거나 다른 아키텍처를 고려해야 합니다.

또한 요청 크기 제한도 있습니다. 페이로드는 최대 10MB까지만 허용됩니다.

대용량 파일 업로드가 필요하다면 S3 Presigned URL을 사용하는 것이 좋습니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

API Gateway를 설정하고 나니, 이제 웹 브라우저에서 https://abc123.execute-api.ap-northeast-2.amazonaws.com/users 같은 URL로 Lambda 함수를 호출할 수 있게 되었습니다. API Gateway를 제대로 이해하면 서버리스 아키텍처의 힘을 최대한 활용할 수 있습니다.

다음 단계로는 실제 비즈니스 로직을 담은 Lambda 함수를 구성해보겠습니다.

실전 팁

💡 - HTTP API가 REST API보다 저렴하고 빠르므로, 특별한 이유가 없다면 HTTP API를 선택하세요

  • API Gateway 콘솔에서 테스트 기능을 활용하면 배포 전에 API를 미리 검증할 수 있습니다
  • Custom Domain을 설정하면 example.com/api 같은 깔끔한 URL을 사용할 수 있습니다

3. Lambda 함수 구성

이제 김개발 씨는 실제 비즈니스 로직을 담은 Lambda 함수를 작성해야 합니다. 간단한 사용자 정보 조회 API를 만들기로 했는데, 데이터베이스 연결은 어떻게 해야 할지, 환경 변수는 어떻게 관리할지 고민이 됩니다.

Lambda 함수 구성은 단순히 코드를 작성하는 것을 넘어, 환경 변수, 메모리 설정, 타임아웃, IAM 권한 등을 적절히 설정하는 것을 의미합니다. 마치 자동차를 운전하기 위해 연료를 넣고, 타이어 공기압을 맞추고, 엔진 오일을 점검하는 것처럼, Lambda 함수도 최적의 성능을 내기 위한 세밀한 구성이 필요합니다.

다음 코드를 살펴봅시다.

// DynamoDB를 사용하는 Lambda 함수
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient, GetCommand } = require('@aws-sdk/lib-dynamodb');

// 클라이언트는 함수 외부에서 초기화 (재사용을 위해)
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);

exports.handler = async (event) => {
    try {
        // 환경 변수에서 테이블 이름 가져오기
        const tableName = process.env.TABLE_NAME;
        const userId = event.pathParameters?.id;

        // DynamoDB에서 데이터 조회
        const command = new GetCommand({
            TableName: tableName,
            Key: { id: userId }
        });

        const result = await docClient.send(command);

        if (!result.Item) {
            return {
                statusCode: 404,
                body: JSON.stringify({ message: '사용자를 찾을 수 없습니다' })
            };
        }

        return {
            statusCode: 200,
            body: JSON.stringify(result.Item)
        };
    } catch (error) {
        console.error('오류 발생:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: '서버 오류가 발생했습니다' })
        };
    }
};

김개발 씨는 이제 실전입니다. 실제 서비스에서 사용할 사용자 정보 조회 API를 만들어야 합니다.

데이터는 DynamoDB에 저장되어 있고, Lambda 함수가 이 데이터를 조회해서 반환해야 합니다. 박시니어 씨가 코드 리뷰를 하다가 중요한 조언을 합니다.

"데이터베이스 클라이언트는 handler 함수 밖에서 초기화해야 해요. 그래야 재사용되거든요." Lambda 함수 구성에서 가장 중요한 것은 무엇일까요?

쉽게 비유하자면, Lambda 함수는 마치 패스트푸드 주방과 같습니다. 주문이 들어오면 빠르게 요리를 만들어 내보내야 합니다.

재료는 미리 준비되어 있어야 하고, 조리 도구도 손에 닿는 곳에 있어야 합니다. 매번 새로 준비하면 시간이 오래 걸립니다.

초보 개발자들이 흔히 하는 실수가 있습니다. 모든 초기화 코드를 handler 함수 안에 작성하는 것입니다.

데이터베이스 클라이언트를 매번 새로 만들고, 환경 변수를 매번 읽고, 설정을 매번 로드합니다. 이렇게 하면 함수 실행 시간이 길어지고, 불필요한 연결이 반복됩니다.

바로 이런 문제를 해결하기 위해 초기화 코드 분리가 필요합니다. Lambda는 함수를 실행할 때 실행 환경을 재사용합니다.

같은 함수가 짧은 시간 안에 여러 번 호출되면, 이전에 사용했던 실행 환경을 그대로 사용합니다. 마치 주방을 계속 열어두고 다음 주문을 기다리는 것과 같습니다.

따라서 handler 함수 외부에서 초기화한 변수는 다음 호출에서도 재사용됩니다. 데이터베이스 연결, HTTP 클라이언트, 환경 변수 읽기 등은 모두 함수 밖에서 한 번만 실행하면 됩니다.

위의 코드를 살펴보겠습니다. DynamoDBClientDynamoDBDocumentClient는 handler 함수 밖에서 생성됩니다.

이렇게 하면 첫 번째 호출에서만 초기화되고, 이후 호출에서는 재사용됩니다. process.env.TABLE_NAME으로 환경 변수를 읽는데, 테이블 이름을 코드에 하드코딩하지 않고 환경 변수로 관리하면 배포 환경마다 다른 값을 사용할 수 있습니다.

try-catch 블록으로 오류를 처리하는 것도 중요합니다. 데이터베이스 연결 실패, 권한 오류 등 예상치 못한 오류가 발생할 수 있습니다.

오류를 catch해서 적절한 HTTP 상태 코드와 메시지를 반환해야 합니다. 환경 변수 설정은 어떻게 할까요?

Lambda 콘솔의 Configuration 탭에서 환경 변수를 추가할 수 있습니다. TABLE_NAME, DB_HOST, API_KEY 같은 값들을 여기에 저장합니다.

민감한 정보는 AWS Secrets Manager나 Parameter Store를 사용하는 것이 더 안전합니다. 메모리 설정도 중요합니다.

Lambda는 128MB부터 10,240MB(10GB)까지 메모리를 설정할 수 있습니다. 메모리를 늘리면 CPU 성능도 함께 증가합니다.

간단한 CRUD 작업은 256MB로 충분하지만, 이미지 처리나 데이터 분석 같은 작업은 1024MB 이상이 필요할 수 있습니다. 타임아웃 설정도 고려해야 합니다.

기본값은 3초이지만, 최대 15분까지 설정할 수 있습니다. 하지만 API Gateway를 사용한다면 30초 제한을 기억해야 합니다.

대부분의 API는 3초 이내에 응답하는 것이 좋습니다. IAM 권한은 어떻게 설정할까요?

Lambda 함수는 실행 역할(Execution Role)을 통해 다른 AWS 서비스에 접근합니다. DynamoDB를 사용한다면 dynamodb:GetItem, dynamodb:Query 같은 권한이 필요합니다.

S3를 사용한다면 s3:GetObject, s3:PutObject 권한이 필요합니다. 최소 권한 원칙을 따라야 합니다.

필요한 권한만 부여하고, 모든 리소스가 아닌 특정 테이블이나 버킷만 접근할 수 있도록 제한합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

박시니어 씨의 조언대로 클라이언트를 함수 밖에서 초기화하고, 환경 변수로 테이블 이름을 관리하니 코드가 훨씬 깔끔해졌습니다. 실행 시간도 200ms에서 50ms로 줄어들었습니다.

Lambda 함수를 제대로 구성하면 성능과 유지보수성을 모두 높일 수 있습니다. 다음 단계로는 AI 모델을 호출하는 Lambda 함수를 만들어보겠습니다.

실전 팁

💡 - 외부 연결(DB 클라이언트, HTTP 클라이언트)은 반드시 handler 외부에서 초기화하세요

  • CloudWatch Logs에서 실행 시간과 메모리 사용량을 확인하여 최적의 메모리 설정을 찾으세요
  • 민감한 정보는 환경 변수 대신 AWS Secrets Manager를 사용하는 것이 더 안전합니다

4. Bedrock 호출 Lambda

김개발 씨는 이제 AI 기능을 추가하라는 요청을 받았습니다. 사용자가 질문을 입력하면 AWS Bedrock의 Claude 모델이 답변을 생성하는 API를 만들어야 합니다.

그런데 Bedrock을 Lambda에서 어떻게 호출하는지 처음이라 막막합니다.

AWS Bedrock은 다양한 AI 모델을 API로 제공하는 서비스입니다. Lambda 함수에서 Bedrock을 호출하면 서버리스 AI 애플리케이션을 만들 수 있습니다.

마치 도서관 사서에게 책을 추천받는 것처럼, Bedrock에게 질문을 보내면 AI 모델이 답변을 생성해서 돌려줍니다.

다음 코드를 살펴봅시다.

// AWS Bedrock의 Claude 모델을 호출하는 Lambda 함수
const { BedrockRuntimeClient, InvokeModelCommand } = require('@aws-sdk/client-bedrock-runtime');

// Bedrock 클라이언트 초기화 (재사용을 위해 함수 외부에서)
const bedrockClient = new BedrockRuntimeClient({ region: 'us-east-1' });

exports.handler = async (event) => {
    try {
        const body = JSON.parse(event.body);
        const userMessage = body.message;

        // Claude 모델에 전달할 요청 페이로드
        const payload = {
            anthropic_version: "bedrock-2023-05-31",
            max_tokens: 1024,
            messages: [
                { role: "user", content: userMessage }
            ]
        };

        // Bedrock 모델 호출
        const command = new InvokeModelCommand({
            modelId: "anthropic.claude-3-sonnet-20240229-v1:0",
            contentType: "application/json",
            accept: "application/json",
            body: JSON.stringify(payload)
        });

        const response = await bedrockClient.send(command);
        const responseBody = JSON.parse(new TextDecoder().decode(response.body));

        // AI 응답 추출
        const aiResponse = responseBody.content[0].text;

        return {
            statusCode: 200,
            body: JSON.stringify({ response: aiResponse })
        };
    } catch (error) {
        console.error('Bedrock 호출 오류:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: 'AI 응답 생성 중 오류가 발생했습니다' })
        };
    }
};

김개발 씨는 AI 기능 추가 요청을 받고 처음에는 당황했습니다. AI 모델을 직접 학습시키고 서버에 배포해야 하는 줄 알았기 때문입니다.

그런데 박시니어 씨가 반가운 소식을 전합니다. "AWS Bedrock을 쓰면 API 호출만으로 AI를 사용할 수 있어요." AWS Bedrock이란 무엇일까요?

쉽게 비유하자면, Bedrock은 마치 렌터카 서비스와 같습니다. 자동차를 직접 구매하고 관리하는 대신, 필요할 때 빌려서 사용합니다.

AI 모델도 마찬가지입니다. Claude, Llama, Stable Diffusion 같은 최신 모델을 직접 학습시키거나 관리할 필요 없이, API로 호출해서 사용할 수 있습니다.

전통적인 AI 서비스 구축 방식에는 여러 어려움이 있었습니다. 먼저, 모델을 학습시키려면 대량의 데이터와 고성능 GPU가 필요했습니다.

비용이 수천만 원에서 수억 원까지 들 수 있었습니다. 두 번째 문제는 모델을 서빙하는 인프라였습니다.

GPU 서버를 24시간 운영해야 했고, 트래픽에 따라 확장하는 것도 복잡했습니다. 바로 이런 문제를 해결하기 위해 Bedrock이 등장했습니다.

Bedrock을 사용하면 Anthropic의 Claude, Meta의 Llama, Amazon의 Titan 같은 최신 AI 모델을 API 호출만으로 사용할 수 있습니다. 모델 학습도 필요 없고, 인프라 관리도 필요 없습니다.

사용한 만큼만 비용을 지불합니다. Lambda와 Bedrock의 조합은 매우 강력합니다.

Lambda는 서버리스 컴퓨팅을, Bedrock은 서버리스 AI를 제공합니다. 두 서비스를 결합하면 완전히 서버리스인 AI 애플리케이션을 만들 수 있습니다.

사용자가 없을 때는 비용이 전혀 들지 않고, 트래픽이 증가하면 자동으로 확장됩니다. 위의 코드를 한 줄씩 살펴보겠습니다.

BedrockRuntimeClient는 Bedrock API를 호출하기 위한 클라이언트입니다. region은 us-east-1으로 설정하는데, 현재 Bedrock은 일부 리전에서만 사용 가능합니다.

modelId는 사용할 모델을 지정합니다. Claude 3 Sonnet, Claude 3 Haiku, Claude 3.5 Sonnet 등 다양한 모델을 선택할 수 있습니다.

payload에는 AI 모델에 전달할 메시지와 설정을 담습니다. max_tokens는 생성할 최대 토큰 수이고, messages 배열에는 대화 내용을 담습니다.

사용자 메시지는 **role: "user"**로, AI 응답은 **role: "assistant"**로 표시됩니다. response.body는 Uint8Array 형태로 반환되므로, TextDecoder로 문자열로 변환한 후 JSON.parse해야 합니다.

응답에서 실제 텍스트는 content[0].text에 담겨 있습니다. 실제 현업에서는 어떻게 활용할까요?

예를 들어 고객 지원 챗봇을 만든다고 가정해봅시다. 사용자가 "배송은 언제 되나요?"라고 물으면, Lambda 함수가 Bedrock의 Claude 모델에 질문을 전달합니다.

Claude는 문맥을 이해하고 적절한 답변을 생성합니다. 이 모든 과정이 서버리스로 동작하며, 동시에 1000명이 질문해도 자동으로 확장됩니다.

많은 스타트업이 Bedrock을 활용하여 AI 기능을 빠르게 추가합니다. 문서 요약, 번역, 코드 생성, 이미지 분석 등 다양한 기능을 API 호출만으로 구현할 수 있습니다.

하지만 주의할 점도 있습니다. Bedrock은 토큰당 비용이 발생합니다.

입력 토큰과 출력 토큰 각각에 대해 과금됩니다. Claude 3 Sonnet의 경우 입력 토큰 1000개당 약 $0.003, 출력 토큰 1000개당 약 $0.015입니다.

긴 대화가 많아지면 비용이 증가할 수 있으므로, max_tokens를 적절히 제한하는 것이 좋습니다. 또한 IAM 권한 설정도 필요합니다.

Lambda 함수의 실행 역할에 bedrock:InvokeModel 권한을 추가해야 합니다. 특정 모델만 사용하도록 제한할 수도 있습니다.

응답 시간도 고려해야 합니다. AI 모델 응답은 보통 2-5초 정도 걸립니다.

긴 응답은 더 오래 걸릴 수 있습니다. API Gateway의 30초 타임아웃 안에 완료되도록 max_tokens를 조절해야 합니다.

다시 김개발 씨의 이야기로 돌아가 봅시다. Bedrock을 Lambda에서 호출하는 코드를 작성하고 테스트하니, AI가 자연스러운 답변을 생성합니다.

"이렇게 간단하게 AI 기능을 추가할 수 있다니!" 서버리스 아키텍처와 AI를 결합하면 적은 비용으로 강력한 기능을 만들 수 있습니다. 다음 단계로는 웹 브라우저에서 호출할 수 있도록 CORS 설정을 해보겠습니다.

실전 팁

💡 - Bedrock 모델은 리전마다 사용 가능 여부가 다르므로, us-east-1이나 us-west-2를 추천합니다

  • 비용을 절감하려면 Claude 3 Haiku 같은 경량 모델을 사용하거나 max_tokens를 제한하세요
  • 대화 히스토리를 유지하려면 이전 메시지들을 messages 배열에 포함시켜야 합니다

5. CORS 설정

김개발 씨는 Lambda와 API Gateway를 연결하고 웹 브라우저에서 테스트하려는데, 자꾸 오류가 발생합니다. 개발자 도구를 열어보니 "CORS policy"라는 빨간 글씨가 보입니다.

이게 뭘까요? 박시니어 씨가 웃으며 말합니다.

"아, CORS 설정을 안 했네요."

CORS(Cross-Origin Resource Sharing)는 웹 브라우저가 다른 도메인의 API를 호출할 때 적용되는 보안 정책입니다. 마치 아파트 경비실이 외부 방문자를 확인하는 것처럼, 브라우저는 다른 출처의 요청을 기본적으로 차단하고, 서버가 명시적으로 허용해야만 통과시킵니다.

Lambda에서는 응답 헤더에 특정 값을 추가하여 CORS를 설정합니다.

다음 코드를 살펴봅시다.

// CORS 헤더를 포함한 Lambda 함수 응답
exports.handler = async (event) => {
    // CORS 헤더 정의 (모든 응답에 공통으로 사용)
    const headers = {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*', // 모든 도메인 허용 (운영 환경에서는 특정 도메인으로 제한)
        'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
        'Access-Control-Allow-Headers': 'Content-Type, Authorization'
    };

    // OPTIONS 메서드는 Preflight 요청 - 바로 200 응답
    if (event.requestContext.http.method === 'OPTIONS') {
        return {
            statusCode: 200,
            headers,
            body: ''
        };
    }

    try {
        // 실제 비즈니스 로직
        const result = { message: 'CORS가 적용된 응답입니다' };

        return {
            statusCode: 200,
            headers, // CORS 헤더 포함
            body: JSON.stringify(result)
        };
    } catch (error) {
        return {
            statusCode: 500,
            headers, // 오류 응답에도 CORS 헤더 필요
            body: JSON.stringify({ message: '오류 발생' })
        };
    }
};

김개발 씨는 API를 완성하고 웹 브라우저에서 테스트하려고 했습니다. fetch 함수로 API를 호출했는데, 콘솔에 빨간 글씨로 오류가 출력됩니다.

"Access to fetch at 'https://...' from origin 'http://localhost:3000' has been blocked by CORS policy." 박시니어 씨가 화면을 보더니 고개를 끄덕입니다. "CORS 설정이 안 되어 있네요.

웹 브라우저는 보안상 다른 도메인의 API를 함부로 호출하지 못하게 막거든요." CORS란 정확히 무엇일까요? 쉽게 비유하자면, CORS는 마치 아파트 경비실과 같습니다.

아파트 주민이 자기 집에 들어가는 것은 문제없지만, 외부 방문자가 들어오려면 경비실에서 확인을 받아야 합니다. 웹 브라우저도 마찬가지입니다.

같은 도메인 내의 요청은 자유롭지만, 다른 도메인으로의 요청은 서버의 허락이 필요합니다. CORS가 없던 시절에는 어땠을까요?

사실 CORS는 보안을 강화하기 위해 등장한 개념입니다. 과거에는 악의적인 웹사이트가 사용자 몰래 다른 사이트의 API를 호출하여 정보를 훔치는 공격이 있었습니다.

예를 들어 악성 사이트를 방문한 사용자의 브라우저가 자동으로 은행 API를 호출하여 계좌 정보를 빼낼 수 있었습니다. 바로 이런 위험을 막기 위해 CORS 정책이 등장했습니다.

브라우저는 다른 도메인으로 요청을 보낼 때, 먼저 Preflight 요청을 보냅니다. 이것은 "이 도메인에서 API를 호출해도 되나요?"라고 묻는 것입니다.

서버가 "네, 허용합니다"라고 응답하면, 그때서야 실제 요청을 보냅니다. Preflight 요청OPTIONS 메서드를 사용합니다.

서버는 이 요청에 대해 어떤 도메인, 어떤 메서드, 어떤 헤더를 허용하는지 응답해야 합니다. 위의 코드를 살펴보겠습니다.

Access-Control-Allow-Origin 헤더는 어떤 도메인의 요청을 허용할지 지정합니다. **'*'**는 모든 도메인을 허용한다는 뜻입니다.

하지만 운영 환경에서는 보안을 위해 'https://myapp.com' 같이 특정 도메인만 허용하는 것이 좋습니다. Access-Control-Allow-Methods는 허용할 HTTP 메서드를 나열합니다.

GET, POST는 기본이고, PUT, DELETE도 필요하다면 추가합니다. Access-Control-Allow-Headers는 클라이언트가 보낼 수 있는 헤더를 지정합니다.

Authorization 헤더는 인증 토큰을 담을 때 자주 사용됩니다. OPTIONS 요청 처리가 중요합니다.

Preflight 요청이 오면 비즈니스 로직을 실행하지 않고, 바로 200 상태 코드와 CORS 헤더를 반환해야 합니다. 빈 body를 보내도 됩니다.

실제 현업에서는 어떻게 활용할까요? 예를 들어 **https://myapp.com**에서 호스팅하는 프론트엔드가 **https://api.myapp.com**의 Lambda API를 호출한다고 가정해봅시다.

도메인이 다르므로 CORS 설정이 필요합니다. Access-Control-Allow-Origin을 **'https://myapp.com'**으로 설정하면, 이 도메인에서의 요청만 허용하고 다른 도메인은 차단합니다.

만약 여러 도메인을 허용해야 한다면 어떻게 할까요? 안타깝게도 Access-Control-Allow-Origin 헤더는 하나의 값만 받습니다.

여러 도메인을 허용하려면, Lambda 함수에서 요청의 Origin 헤더를 확인하고, 허용 목록에 있으면 그 도메인을 응답 헤더에 설정해야 합니다. API Gateway에서도 CORS 설정 가능합니다.

API Gateway 콘솔에서 CORS를 활성화하면, OPTIONS 요청을 API Gateway가 자동으로 처리해줍니다. 하지만 Lambda 함수의 응답에도 여전히 CORS 헤더를 포함해야 합니다.

API Gateway 설정만으로는 충분하지 않습니다. 하지만 주의할 점도 있습니다.

**Access-Control-Allow-Origin: '*'**는 편리하지만 보안에 취약합니다. 어떤 웹사이트에서든 API를 호출할 수 있기 때문입니다.

운영 환경에서는 반드시 특정 도메인만 허용해야 합니다. 또한 인증이 필요한 API라면 Access-Control-Allow-Credentials: true 헤더도 추가해야 합니다.

그리고 이 경우 Access-Control-Allow-Origin은 **'*'**를 사용할 수 없고, 명시적인 도메인을 지정해야 합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

CORS 헤더를 추가하고 나니, 웹 브라우저에서 API를 정상적으로 호출할 수 있게 되었습니다. "드디어 프론트엔드와 연결됐어요!" CORS를 제대로 이해하면 웹 애플리케이션의 보안을 유지하면서도 필요한 도메인 간 통신을 허용할 수 있습니다.

다음 단계로는 이 모든 것을 자동으로 배포하는 방법을 배워보겠습니다.

실전 팁

💡 - 개발 환경에서는 '*'로 모든 도메인을 허용하되, 운영 환경에서는 반드시 특정 도메인만 허용하세요

  • 모든 응답(성공, 오류 모두)에 CORS 헤더를 포함해야 브라우저가 응답을 읽을 수 있습니다
  • API Gateway의 CORS 설정만으로는 부족하며, Lambda 응답에도 헤더가 필요합니다

6. 배포 자동화 SAM CDK

김개발 씨는 지금까지 Lambda 함수를 AWS 콘솔에서 수동으로 배포했습니다. 코드를 복사해서 붙여넣고, 환경 변수를 입력하고, IAM 권한을 설정하는 과정이 번거롭습니다.

더 큰 문제는 팀원들과 협업할 때 설정이 달라져서 오류가 발생한다는 것입니다. 박시니어 씨가 조언합니다.

"Infrastructure as Code를 사용해보세요."

"API Gateway URL" Value: !Sub "https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com"

다음 코드를 살펴봅시다.

# AWS SAM template.yaml 예제
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

김개발 씨는 새로운 기능을 추가할 때마다 AWS 콘솔을 클릭하느라 시간을 많이 보냈습니다. Lambda 함수를 만들고, API Gateway를 설정하고, DynamoDB 테이블을 생성하고, IAM 권한을 추가하는 과정을 매번 반복했습니다.

실수로 설정을 빠뜨려서 오류가 발생하기도 했습니다. 박시니어 씨가 새로운 접근법을 제안합니다.

"Infrastructure as Code를 사용하면 모든 리소스를 코드로 관리할 수 있어요. SAM이나 CDK를 한번 배워보세요." Infrastructure as Code란 무엇일까요?

쉽게 비유하자면, 이것은 마치 요리 레시피와 같습니다. 레시피를 보면 누구나 같은 요리를 만들 수 있습니다.

재료의 양, 조리 순서, 온도 설정이 모두 명확하게 적혀 있기 때문입니다. 인프라 코드도 마찬가지입니다.

코드를 보면 어떤 리소스가 어떻게 구성되어 있는지 한눈에 알 수 있고, 누구나 같은 환경을 재현할 수 있습니다. 수동 배포 방식에는 여러 문제가 있었습니다.

먼저, 재현성이 떨어집니다. 개발 환경에서는 잘 되던 것이 운영 환경에서는 안 되는 경우가 많습니다.

어떤 설정이 다른지 찾기가 어렵습니다. 두 번째 문제는 협업의 어려움입니다.

팀원마다 설정 방식이 달라서 혼란이 생깁니다. 바로 이런 문제를 해결하기 위해 SAMCDK가 등장했습니다.

AWS SAM은 YAML 파일로 서버리스 리소스를 정의합니다. CloudFormation의 확장 버전으로, Lambda와 API Gateway를 더 쉽게 정의할 수 있습니다.

sam deploy 명령 하나로 모든 리소스가 배포됩니다. AWS CDK는 TypeScript, Python, Java 같은 프로그래밍 언어로 인프라를 정의합니다.

조건문, 반복문, 함수 같은 프로그래밍 기능을 활용할 수 있어 더 유연합니다. 위의 SAM 템플릿을 살펴보겠습니다.

Transform: AWS::Serverless-2016-10-31은 이 템플릿이 SAM 템플릿임을 나타냅니다. Globals 섹션에서는 모든 Lambda 함수에 공통으로 적용될 설정을 정의합니다.

타임아웃 30초, 런타임 Node.js 20, 환경 변수로 테이블 이름을 주입합니다. UserApiFunction 리소스는 Lambda 함수를 정의합니다.

CodeUri는 소스 코드가 있는 폴더를 가리킵니다. Policies에서 DynamoDB 접근 권한을 부여합니다.

Events 섹션에서 API Gateway 경로를 정의하는데, 이렇게 하면 Lambda 함수와 API Gateway가 자동으로 연결됩니다. UsersTable 리소스는 DynamoDB 테이블을 정의합니다.

BillingMode: PAY_PER_REQUEST는 온디맨드 방식으로, 사용한 만큼만 비용을 지불합니다. AttributeDefinitionsKeySchema로 테이블 구조를 정의합니다.

Outputs 섹션에는 배포 후 출력할 정보를 정의합니다. API Gateway의 URL을 출력하여 바로 테스트할 수 있게 합니다.

배포는 어떻게 할까요? 먼저 sam build 명령으로 코드를 빌드합니다.

의존성 패키지를 설치하고 배포 패키지를 준비합니다. 그다음 sam deploy --guided를 실행하면 대화형으로 배포 설정을 입력할 수 있습니다.

스택 이름, 리전, IAM 권한 생성 여부 등을 물어봅니다. 한 번 설정을 저장하면, 다음부터는 sam deploy만 실행하면 됩니다.

코드나 인프라 변경 사항이 자동으로 감지되어 업데이트됩니다. CDK는 어떻게 다를까요? CDK는 프로그래밍 언어로 작성하므로 더 강력합니다.

예를 들어 환경에 따라 다른 설정을 적용하거나, 여러 Lambda 함수를 반복문으로 생성할 수 있습니다. TypeScript로 작성하면 자동 완성과 타입 체크의 도움을 받을 수 있습니다.

실제 현업에서는 어떻게 활용할까요? 대부분의 기업은 GitCI/CD 파이프라인을 결합합니다.

개발자가 코드를 Git에 푸시하면, GitHub Actions나 AWS CodePipeline이 자동으로 sam deploy 또는 cdk deploy를 실행합니다. 테스트도 자동으로 실행되고, 통과하면 운영 환경에 배포됩니다.

이렇게 하면 배포 실수가 거의 없어집니다. 모든 변경 사항이 코드로 기록되고, Git 히스토리로 추적할 수 있습니다.

문제가 생기면 이전 버전으로 롤백하기도 쉽습니다. 하지만 주의할 점도 있습니다.

SAM과 CDK는 CloudFormation 위에서 동작합니다. CloudFormation 스택을 삭제하면 모든 리소스가 함께 삭제됩니다.

DynamoDB 테이블 같은 중요한 데이터는 삭제 방지 설정을 해야 합니다. 또한 배포 시간이 길 수 있습니다.

CloudFormation이 리소스를 생성하고 업데이트하는 데 몇 분이 걸립니다. 하지만 이것은 수동 배포에 비하면 훨씬 빠르고 안전합니다.

SAM과 CDK 중 무엇을 선택할까요? 간단한 서버리스 애플리케이션이라면 SAM이 더 쉽습니다. 템플릿이 간결하고 학습 곡선이 낮습니다.

복잡한 인프라나 여러 서비스를 통합해야 한다면 CDK가 더 강력합니다. 조건부 로직, 재사용 가능한 컴포넌트 등을 활용할 수 있습니다.

많은 팀이 처음에는 SAM으로 시작하고, 프로젝트가 복잡해지면 CDK로 마이그레이션합니다. 다시 김개발 씨의 이야기로 돌아가 봅시다.

SAM 템플릿을 작성하고 sam deploy를 실행하니, Lambda 함수, API Gateway, DynamoDB 테이블이 모두 자동으로 생성되었습니다. "이제 클릭 한 번에 전체 스택을 배포할 수 있네요!" 팀원들도 같은 템플릿을 사용하니 환경 차이로 인한 문제가 사라졌습니다.

Git으로 버전 관리를 하니 누가 언제 무엇을 변경했는지 투명하게 추적할 수 있습니다. Infrastructure as Code를 제대로 활용하면 배포가 빠르고 안전해지며, 협업도 훨씬 수월해집니다.

이것이 바로 현대적인 서버리스 개발의 핵심입니다.

실전 팁

💡 - SAM은 간단하고 배우기 쉬우므로, 서버리스 입문자에게 추천합니다

  • CDK는 복잡한 인프라에 강력하지만, 학습 곡선이 있으므로 SAM에 익숙해진 후 시도하세요
  • samconfig.toml 파일에 배포 설정을 저장하여 매번 입력하지 않아도 되게 하세요

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

#AWS#Lambda#Serverless#APIGateway#SAM

댓글 (0)

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

함께 보면 좋은 카드 뉴스