Amazon Web Services 한국 블로그

AWS Lambda의 새로운 기능 — 컨테이너 이미지 지원

AWS Lambda를 사용하면 서버 없이 코드를 업로드해서 실행할 수 있습니다. 많은 고객들이 이와 같은 작업 방식을 사용하고 있지만, 개발 워크플로에 대해 컨테이너화 하는데 투자한 경우에는 동일한 방식으로 Lambda를 사용하는 애플리케이션을 구축하는 작업이 쉽지 않습니다.

이를 지원하기 위해 이제 Lambda 함수를 최대 10GB 크기의 컨테이너 이미지로 패키징 및 배포할 수 있는 기능을 제공합니다. 이렇게 하면 기계 학습 또는 데이터 집약적 워크로드와 같이 상당한 종속성이 수반되는 대규모 워크로드를 쉽게 구축하고 배포할 수 있습니다. ZIP 아카이브로 패키징된 함수와 마찬가지로 컨테이너 이미지로 배포되는 함수는 운영 편의성, 자동 확장, 고가용성, 여러 서비스와의 기본 통합이라는 이점을 동일하게 누릴 수 있습니다.

지원되는 모든 Lambda 런타임(Python, Node.js, Java, .NET, Go, Ruby)에 대해 기본 이미지를 제공하고 있으므로 여러분의 코드와 종속성을 쉽게 추가할 수 있습니다. 또한 Lambda Runtime API로 구현한 자체 런타임을 포함해서 확장할 수 있도록  Amazon Linux를 기반의 사용자 정의 런타임을 위한 기본 이미지도 제공합니다.

여러분이 사용하는 임의의 기본 이미지도 Lambda에 배포할 수 있습니다(예: Alpine 또는 Debian Linux를 기반으로 하는 이미지). Lambda를 사용하려면 이러한 이미지가 Lambda Runtime API를 구현해야 합니다. 사용자 저의 기본 이미지를 더욱 쉽게 만들 수 있도록, 지원되는 모든 런타임에 대해 Runtime API를 구현한 Lambda Runtime Interface Clients가 출시됩니다. 이러한 구현은 네이티브 패키지 관리자를 통해 사용할 수 있으므로 이미지에서 쉽게 선택할 수 있으며 오픈 소스 라이선스를 사용하여 커뮤니티와 공유됩니다.

또한 컨테이너 이미지의 로컬 테스트를 수행하고 Lambda에 배포할 때 실행되는지 확인할 수 있는 Lambda Runtime Interface Emulator가 오픈 소스로 출시됩니다. Lambda Runtime Interface Emulator는 AWS에서 제공하는 모든 기본 이미지에 포함되어 있으며 임의의 이미지와 함께 사용될 수도 있습니다.

컨테이너 이미지는 Lambda Extensions API를 사용하여 모니터링, 보안 및 기타 도구를 Lambda 실행 환경과 통합할 수도 있습니다.

컨테이너 이미지를 배포하려면 Amazon Elastic Container Registry 리포지토리 중에서 하나를 선택합니다. 몇 가지 예제를 통해 실제로 어떻게 작동하는지 살펴보겠습니다. 먼저 Node.js용으로 AWS에서 제공하는 이미지를 사용한 다음 Python용 맞춤형 이미지를 만들어 보겠습니다.

Node.js용으로 AWS에서 제공하는 기본 이미지 사용
다음은 PDFKit 모듈을 사용하여 PDF 파일을 생성하는 간단한 Node.js Lambda 함수의 코드(app.js)입니다. 호출될 때마다 faker.js 모듈에 의해 생성된 임의의 데이터가 포함된 새 메일을 만듭니다. 함수의 출력은 Amazon API Gateway의 구문을 사용하여 PDF 파일을 반환합니다.

const PDFDocument = require('pdfkit');
const faker = require('faker');
const getStream = require('get-stream');

exports.lambdaHandler = async (event) => {

    const doc = new PDFDocument();

    const randomName = faker.name.findName();

    doc.text(randomName, { align: 'right' });
    doc.text(faker.address.streetAddress(), { align: 'right' });
    doc.text(faker.address.secondaryAddress(), { align: 'right' });
    doc.text(faker.address.zipCode() + ' ' + faker.address.city(), { align: 'right' });
    doc.moveDown();
    doc.text('Dear ' + randomName + ',');
    doc.moveDown();
    for(let i = 0; i < 3; i++) {
        doc.text(faker.lorem.paragraph());
        doc.moveDown();
    }
    doc.text(faker.name.findName(), { align: 'right' });
    doc.end();

    pdfBuffer = await getStream.buffer(doc);
    pdfBase64 = pdfBuffer.toString('base64');

    const response = {
        statusCode: 200,
        headers: {
            'Content-Length': Buffer.byteLength(pdfBase64),
            'Content-Type': 'application/pdf',
            'Content-disposition': 'attachment;filename=test.pdf'
        },
        isBase64Encoded: true,
        body: pdfBase64
    };
    return response;
};

npm을 사용하여 패키지를 초기화하고 package.json 파일에 필요한 세 가지 종속성을 추가합니다. 이 방법으로 package-lock.json 파일도 만듭니다. 더 예측 가능한 결과를 얻기 위해 컨테이너 이미지에 추가합니다.

$ npm init
$ npm install pdfkit
$ npm install faker
$ npm install get-stream

이제 Dockerfile을 만들어서 nodejs12.x 런타임용으로 AWS에서 제공하는 기본 이미지부터 시작하여 Lambda 함수의 컨테이너 이미지를 생성합니다. AWS가 제공하는 모든 기본 이미지는 Docker Hub와 퍼블릭 ECR에서 사용할 수 있습니다. 여기서는 Docker Hub에 있는 기본 이미지를 사용하였습니다.

FROM amazon/aws-lambda-nodejs:12
COPY app.js package*.json ./
RUN npm install
CMD [ "app.lambdaHandler" ]

만약 퍼블릭 ECR의 이미지를 사용하려면 첫번째 줄을 다음과 같이 수정합니다.

FROM public.ecr.aws/lambda/nodejs:12

Dockerfile은 소스 코드(app.js)와 패키지 및 종속성을 설명하는 파일(package.jsonpackage-lock.json)을 기본 이미지에 추가합니다. 이제, npm을 실행하여 종속성을 설치합니다. CMD를 함수 처리자로 설정했는데, 이것은 나중에 Lambda 함수를 구성할 때 파라미터 재정의로 수행할 수도 있습니다.

Docker CLI를 사용하여 로컬에서 random-letter 컨테이너 이미지를 만듭니다.

$ docker build -t random-letter .

작동하는지 확인하기 위해 Lambda Runtime Interface Emulator를 사용하여 로컬에서 컨테이너 이미지를 시작합니다.

$ docker run -p 9000:8080 random-letter:latest

이제 curl 명령으로 함수 호출을 테스트합니다. 여기에서는 빈 JSON 페이로드를 전달하고 있습니다.

$ curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

오류가 있으면 로컬에서 수정할 수 있습니다. 작동하면 다음 단계로 넘어갑니다.

컨테이너 이미지를 업로드하기 위해 계정에 새로운 ECR 리포지토리를 만들고 로컬 이미지에 태그를 지정하여 ECR로 푸시합니다. 컨테이너 이미지의 소프트웨어 취약점을 식별하는 데 도움이 되도록 [ECR 이미지 스캐닝]을 활성화합니다.

$ aws ecr create-repository --repository-name random-letter --image-scanning-configuration scanOnPush=true
$ docker tag random-letter:latest 123412341234.dkr.ecr.sa-east-1.amazonaws.com/random-letter:latest
$ aws ecr get-login-password | docker login --username AWS --password-stdin 123412341234.dkr.ecr.sa-east-1.amazonaws.com
$ docker push 123412341234.dkr.ecr.sa-east-1.amazonaws.com/random-letter:latest

여기서는 AWS Management Console을 사용하여 함수 생성을 완료합니다. 컨테이너 이미지에 대한 지원을 추가하도록 업데이트된 AWS Serverless Application Model을 사용할 수도 있습니다.

[Lambda 콘솔]에서 [함수 생성]을 클릭합니다. [컨테이너 이미지]를 선택하고, 함수에 이름을 지정하고, 이미지 찾아보기를 선택하여 ECR 리포지토리에 알맞은 이미지를 찾습니다.

콘솔 스크린샷.

리포지토리를 선택한 후, 업로드한 최신 이미지를 사용합니다. 이미지를 선택하면 Lambda가 이를 기본 이미지 digest(아래 이미지에서 태그 오른쪽에 있음)로 변환합니다. docker images --digests 명령을 사용하여 로컬에서 이미지의 다이제스트를 볼 수 있습니다. 이 방법으로 함수는 latest 태그가 다른 이미지로 넘어가더라도 같은 이미지를 사용하며, 의도하지 않은 배포가 방지됩니다. 함수 코드에서 사용할 이미지를 업데이트할 수 있습니다. 함수 구성을 업데이트해도 사용된 이미지에는 아무런 영향을 주지 않습니다. 그 사이에 태그가 다른 이미지에 재할당된 경우에도 마찬가지입니다.

콘솔 스크린샷.

원하는 경우, 컨테이너 이미지 값 중 일부를 재정의할 수 있습니다. 지금은 재정의하지 않지만, CMD 값의 함수 처리자를 재정의하는 것과 같은 방법으로 다른 함수에 사용할 수 있는 이미지를 만들 수 있습니다.

콘솔 스크린샷.

다른 모든 옵션을 기본값으로 그대로 두고 [함수 생성]을 선택합니다.

함수 코드를 만들거나 업데이트할 때 Lambda 플랫폼은 새 컨테이너 이미지와 업데이트된 컨테이너 이미지를 최적화하여 호출을 받을 수 있게 준비합니다. 이 최적화는 이미지 크기에 따라 몇 초 또는 몇 분 정도 걸립니다. 그 후에 함수는 호출 준비가 완료됩니다. 콘솔에서 함수를 테스트합니다.

콘솔 스크린샷.

제대로 작동합니다! 이제 API Gateway를 트리거로 추가하겠습니다. [트리거 추가]를 선택하고 [HTTP API를 사용하는 API Gateway]를 추가합니다. 간단하게 만들기 위해 API 인증은 선택하지 않습니다.

콘솔 스크린샷.

이제 API 엔드포인트를 몇 번 클릭하고 임의의 메일을 몇 개 다운로드합니다.

콘솔 스크린샷.

예상대로 작동합니다! 다음은 faker.js 모듈로부터 임의의 데이터로 생성한 몇 개의 PDF 파일입니다.

샘플 애플리케이션의 출력.

 

Python용 맞춤형 이미지 만들기
예를 들어 회사 가이드라인을 따르기 위해 또는 지원되지 않는 런타임 버전을 사용하기 위해 때로는 맞춤형 컨테이너 이미지를 사용해야 할 수도 있습니다.

이번에는 Python 3.9를 사용하는 이미지를 만들어 보고자 합니다. 함수의 코드(app.py)는 매우 간단합니다. 인사말을 출력하고 사용 중인 Python 버전을 표시하는 것이 전부입니다.

import sys
def handler(event, context): 
    return 'Hello from AWS Lambda using Python' + sys.version + '!'

앞서 언급했듯이 지원되는 모든 런타임에 대해 Lambda Runtime Interface Clients(Runtime API를 구현함)의 오픈 소스 구현을 공유하고 있습니다. 여기서는 Alpine Linux를 기반으로 하는 Python 이미지에서 시작합니다. 그런 다음 Python용 Lambda Runtime Interface Client(곧 링크를 추가할 예정)를 이미지에 추가합니다. Dockerfile은 다음과 같습니다.

# Define global args
ARG FUNCTION_DIR="/home/app/"
ARG RUNTIME_VERSION="3.9"
ARG DISTRO_VERSION="3.12"

# 1단계 - 번들 기본 이미지 + 런타임
# 새로운 이미지 복사본을 가져오고 GCC를 설치함
FROM python:${RUNTIME_VERSION}-alpine${DISTRO_VERSION} AS python-alpine
# GCC 설치(Alpine은 musl을 사용하지만 여기에서는 종속성을 GCC와 컴파일하고 연결함)
RUN apk add --no-cache \
    libstdc++

# 2단계 - 함수 및 종속성 빌드
FROM python-alpine AS build-image
# aws-lambda-cpp 빌드 종속성 설치
RUN apk add --no-cache \
    build-base \
    libtool \
    autoconf \
    automake \
    libexecinfo-dev \
    make \
    cmake \
    libcurl
# 빌드의 현재 단계에 전역 args 포함
ARG FUNCTION_DIR
ARG RUNTIME_VERSION
# 함수 디렉토리 만들기
RUN mkdir -p ${FUNCTION_DIR}
# 처리자 함수 복사
COPY app/* ${FUNCTION_DIR}
# 선택 사항 – 함수의 종속성 설치
# RUN python${RUNTIME_VERSION} -m pip install -r requirements.txt --target ${FUNCTION_DIR}
# Python용 Lambda Runtime Interface Client 설치
RUN python${RUNTIME_VERSION} -m pip install awslambdaric --target ${FUNCTION_DIR}

# 3단계 - 최종 런타임 이미지
# Python 이미지의 새로운 복사본 가져오기
FROM python-alpine
# 빌드의 현재 단계에 전역 arg 포함
ARG FUNCTION_DIR
# 작업 디렉토리를 함수 루트 디렉토리로 설정
WORKDIR ${FUNCTION_DIR}
# 빌드된 종속성 복사
COPY --from=build-image ${FUNCTION_DIR} ${FUNCTION_DIR}
# (선택 사항) Lambda Runtime Interface Emulator를 추가하고 보다 간단한 로컬 실행을 위해 ENTRYPOINT에서 스크립트 사용
COPY https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie /usr/bin/aws-lambda-rie
RUN chmod 755 /usr/bin/aws-lambda-rie
COPY entry.sh /
ENTRYPOINT [ "/entry.sh" ]
CMD [ "app.handler" ]

이번에는 Dockerfile이 더욱 복잡하게 연결됩니다. 3단계에 걸쳐 최종 이미지를 만드는데 다단계 빌드의 Docker 모범 사례를 따릅니다. 이 3단계 접근 방식을 사용하여 자체 맞춤형 이미지를 만들 수 있습니다.

  • 1단계는 런타임(이 경우 Python 3.9)과 함께 사용할 기본 이미지 그리고 2단계에서 종속성을 컴파일하고 연결하는 데 사용할 GCC를 만듭니다.
  • 2단계는 Lambda Runtime Interface Client를 설치하고 함수와 종속성을 만듭니다.
  • 3단계는 2단계의 출력을 1단계에서 만든 기본 이미지에 추가하는 최종 이미지를 만듭니다. 이 예제에서는 Lambda Runtime Interface Emulator도 추가하지만 이것은 선택 사항입니다(아래 참조).

아래의 entry.sh 스크립트를 작성하여 ENTRYPOINT로 사용합니다. Python용 Lambda Runtime Interface Client를 실행합니다. 로컬에서 실행하는 경우 Runtime Interface Client는 Lambda Runtime Interface Emulator에 의해 래핑됩니다.

#!/bin/sh
if [ -z "${AWS_LAMBDA_RUNTIME_API}" ]; then
    exec /usr/bin/aws-lambda-rie /usr/local/bin/python -m awslambdaric
else
    exec /usr/local/bin/python -m awslambdaric
fi

이제 Lambda Runtime Interface Emulator를 사용하여 함수와 컨테이너 이미지가 올바르게 작동하는지를 로컬에서 확인할 수 있습니다.

$ docker run -p 9000:8080 lambda/python:3.9-alpine3.12

컨테이너 이미지에 Lambda Runtime Interface Emulator 포함 안 함
Lambda Runtime Interface Emulator를 맞춤형 컨테이너 이미지에 추가하는 것은 선택 사항입니다. 포함하지 않는 경우에는 다음 단계를 따라 로컬 컴퓨터에 Lambda Runtime Interface Emulator를 설치하여 로컬에서 테스트할 수 있습니다.

  • Dockerfile의 3단계에서 Lambda Runtime Interface Emulator를 복사하는 명령(aws-lambda-rie)과 entry.sh 스크립트를 제거합니다. 이 경우에는 entry.sh 스크립트가 필요하지 않습니다.
  • ENTRYPOINT를 사용하여 기본적으로 Lambda Runtime Interface Client를 시작합니다.
    ENTRYPOINT [ "/usr/local/bin/python", “-m”, “awslambdaric” ]
  • 다음 명령을 실행하여 Lambda Runtime Interface Emulator를 로컬 컴퓨터에 설치합니다(예: ~/.aws-lambda-rie 아래에).
mkdir -p ~/.aws-lambda-rie
curl -Lo ~/.aws-lambda-rie/aws-lambda-rie https://github.com/aws/aws-lambda-runtime-interface-emulator/releases/latest/download/aws-lambda-rie
chmod +x ~/.aws-lambda-rie/aws-lambda-rie

Lambda Runtime Interface Emulator가 로컬 컴퓨터에 설치되면 컨테이너를 시작할 때 마운트할 수 있습니다. 로컬에서 컨테이너를 시작하는 명령은 이제 다음과 같습니다(Lambda Runtime Interface Emulator가 ~/.aws-lambda-rie에 있다고 가정할 때).

docker run -d -v ~/.aws-lambda-rie:/aws-lambda -p 9000:8080 \
       --entrypoint /aws-lambda/aws-lambda-rie lambda/python:3.9-alpine3.12
       /lambda-entrypoint.sh app.handler

Python용 맞춤형 이미지 테스트
어느 쪽이든 컨테이너가 로컬에서 실행될 때 curl 명령을 사용하여 함수 호출을 테스트할 수 있습니다.

curl -XPOST "http://localhost:9000/2015-03-31/functions/function/invocations" -d '{}'

출력 내용이 예상했던 대로입니다!

"Hello from AWS Lambda using Python3.9.0 (default, Oct 22 2020, 05:03:39) \n[GCC 9.3.0]!"

이미지를 ECR로 푸시하고 앞에서와 같이 함수를 만듭니다. 다음은 콘솔에서 직접 테스트해 본 결과입니다.

콘솔 스크린샷.

Alpine 기반의 맞춤형 컨테이너 이미지가 Lambda에서 Python 3.9를 실행하고 있습니다!

지금 이용 가능
지금 바로 미국 동부(버지니아 북부), 미국 동부(오하이오), 미국 서부(오레곤), 아시아 태평양(도쿄), 아시아 태평양(싱가포르), EU(아일랜드), EU(프랑크푸르트), 남아메리카(상파울루)에서 컨테이너 이미지를 사용하여 Lambda 함수를 배포할 수 있습니다. 곧 더 많은 리전에서 지원될 수 있도록 노력하고 있습니다. 컨테이너 이미지 지원은 ZIP 아카이브 방식에 추가적으로 제공되는 것이며 ZIP 패키징 형식도 계속 지원될 것입니다.

이 기능은 추가 요금 없이 사용할 수 있습니다. ECR 리포지토리 요금과 일반적인 Lambda 요금만 지불하면 됩니다.

AWS Lambda에서의 컨테이너 이미지 지원은 콘솔, AWS 명령줄 인터페이스(CLI), AWS SDK, AWS Serverless Application Model 그리고 AWS Partners, including Aqua Security, Datadog, Epsagon, HashiCorp Terraform, Honeycomb, Lumigo, Pulumi, Stackery, Sumo Logic, Thundra가 제공하는 솔루션에서 사용할 수 있습니다.

이 새로운 기능은 새로운 시나리오를 가능하게 만들고, 개발 파이프라인과의 통합을 단순화하고, 좀 더 쉽게 맞춤형 이미지와 선호하는 프로그래밍 플랫폼을 사용하여 서버리스 애플리케이션을 만들 수 있게 해줍니다.

AWS Lambda에서 컨테이너 이미지를 사용하는 방법을 자세히 알아보고 직접 사용해 보세요.

Danilo