[AWS] AWS CDK를 이용한 Lambda Docker 함수 구현

0. INTRO

람다 서비스는 서버리스라 간편하게 코드를 작동시켜 간단한 서비스를 구현할 수 있다는 장점이 있다. 만약 기본적으로 지원하는 라이브러리들만 사용해서 코드를 구현할 수 있으면 제일 좋겠지만 외부 모듈을 끌어와야 동작되는 코드라면 일반적으로는 Lambda Layer를 추가하거나 Library 및 코드 파일이 함께 있는 zip 파일을 만들어 Lambda에 업로드하는 방법으로 해결이 가능하다.

위의 과정들이 조금 번거롭긴 하지만 그렇게 추가해서 해결이 되면 다행이다. 하지만 만약 실행시간이 오래 걸리거나 리소스를 많이 잡아먹거나 또 설치되어야 할 라이브러리들이 많을 때는 이 모든 것들을 최적화하여 코드를 돌리는 과정이 상당히 번거롭다.

이럴 때 Lambda Docker Image 기능을 사용할 수 있다. 이는 코드와 그에 필요한 requirements들을 Dockerfile을 통해 이미지로 만들어 ECR에 저장한 후 해당 이미지를 바탕으로 람다 함수를 실행해준다. 이렇게 되면 기존에는 무거워서 실행시키지 못했던 작업들을 큰 제약없이(이미지 최대 10GB) 실행시킬 수 있으며 사용하는 라이브러리들이 많아도 손쉽게 코드를 구현시킬 수 있다.


1. 본문

  • 전체적인 과정은 아래와 같다.
    1. ECR Repository 생성
    2. Dockerfile, requirement.txt, function.py 등 Docker Image 만들 때 필요한 것들을 디렉토리에 넣고 Docker Image 생성 후 Push
    3. Lambda에서 Container Image 선택하여 Function 생성 후 실행
    4. 위의 과정들을 자동화 할 수 있도록 AWS CDK로 기능 구현
  • 실습 코드
    1. Dockerfile 디렉토리 구성 및 코드

       lambda_image
       |
       ├── Dockerfile
       ├── lambda_function.py
       ├── requirements.txt
       └── titanic.csv
      

      Dockerfile

       FROM public.ecr.aws/lambda/python:3.8
              
       WORKDIR ${LAMBDA_TASK_ROOT}
              
       COPY . ${LAMBDA_TASK_ROOT}
              
       RUN  pip3 install -r requirements.txt --target "${LAMBDA_TASK_ROOT}"
              
       CMD [ "app.handler" ]
      

      lambda_function.py

       import json
       import pandas as pd
              
       def handler(event, context):
           df = pd.read_csv("titanic.csv")
           cols = df.columns
           print(cols)
           return {
               'statusCode': 200,
               'body': json.dumps(f"hello everyone!\n\n{cols}")
           }
      

      requirements.txt

       pandas==2.0.3
      
    2. lambdaContainer_csk_stack.py(Lambda Docker CDK 코드)

       import os
       import typing
       from aws_cdk import (
           aws_lambda,
           aws_ecr,
           Aws, Duration, Stack
       )
       from constructs import Construct
              
       class LambdaContainerFunctionStack(Stack):
           def __init__(self, scope: Construct, id: str, **kwargs) -> None:
               super().__init__(scope, id, **kwargs)
      
               image_name    = "lambdaContainerFunction"
               use_pre_existing_image = False
      
      
               if (use_pre_existing_image):
      
                   ecr_repository = aws_ecr.Repository.from_repository_attributes(self,
                       id              = "ECR",
                       repository_arn  ='arn:aws:ecr:{0}:{1}'.format(Aws.REGION, Aws.ACCOUNT_ID),
                       repository_name = image_name
                   ) ## aws_ecr.Repository.from_repository_attributes
      
                   ecr_image = typing.cast("aws_lambda.Code", aws_lambda.EcrImageCode(
                       repository = ecr_repository,
                       tag='latest'
                   )) ## aws_lambda.EcrImageCode
      
               else:
                   ecr_image = aws_lambda.EcrImageCode.from_asset_image(
                       directory = "/home/ubuntu/AWS-Training/CDK-Python/cdk_python/lambda-image",
                   )
                      
               # Lambda 함수의 IAM Role 정의   
               role_arn = 'arn:aws:iam::646664498184:role/LambdaEC2FullAccessRole'
               role = iam.Role.from_role_arn(
                                           self, 
                                           "Role", 
                                           role_arn, 
                                           # mutable=False
                                           )
      
               # 람다 함수 부분
               aws_lambda.Function(self,
               id            = "lambdaContainerFunction",
               description   = "Lambda Container Function",
               role          = role,
               code          = ecr_image,
               handler       = aws_lambda.Handler.FROM_IMAGE,
               runtime       = aws_lambda.Runtime.FROM_IMAGE,
               environment   = {"hello":"world"},
               function_name = "LambdaDockerImage",
               memory_size   = 128,
               reserved_concurrent_executions = 10,
               timeout       = Duration.seconds(30)
               )
      
    3. 코드 실행

        cdk synth  >  Cloudformation Template으로 변환
        cdk bootstrap  >  코드 에러를 확인하고 변환된 cloudformation JSON이 저장될 S3 경로 생성
        cdk deploy  >  배포 시작
      
  • 결과 확인
    • 아래와 같이 함수가 잘 만들어졌다. image
    • 테스트 결과 역시 아래와 같이 문제 없이 잘 나오는 것을 확인할 수 있다. image

2. 결론

  • 라이브러리들의 설치 과정 없이 Lambda로 원하는 코드를 실행시킬 수 있는건 아주 매력적인 기능인 것 같다. 최초 실행시 시간이 약간 걸리긴하고 Lambda 함수의 Timeout 시간인 15분 후에 또 실행하면 최초실행과 마찬가지로 시간이 조금 걸리겠지만 그래도 큰 리소스를 필요로 하거나 여러개의 라이브러리를 사용해야 하는 함수를 실행시킬 때는 상당히 유용할 듯하다.
  • 코드를 수정하기 위해서는 매 번 Docker Image를 다시 올려줘야하는 번거로움이 있기 때문에 디버깅이나 코드 수정을 모두 끝낸 코드를 업로드해주는 것이 좋겠다.

Leave a comment