Pulumi 시작하기(4) - S3와 Lambda를 이용한 썸네일 추출기
Cloud

Pulumi 시작하기(4) - S3와 Lambda를 이용한 썸네일 추출기

일시불
Pulumi 시작하기(1) - Introduction
Pulumi 시작하기(2) - 코드 작성 및 배포
Pulumi 시작하기(3) - API Gateway v2 + Lambda 예제
Pulumi 시작하기(4) - S3와 Lambda를 이용한 썸네일 추출기

프로젝트 시작

프로젝트 구성

Video thumbnail diagram

.mp4 파일에서 썸네일을 추출하는 서버리스 앱을 만드는 예제이다. 프로젝트 구조를 보면, S3에 새로운 .mp4가 업로드되면, onNewVideo 함수가 이를 감지한다. 그리고 Fargate에서 Node로 작성된 컨테이너를 실행시킨다. 이 컨테이너는 썸네일을 추출해서 버킷에 업로드한다.

프로젝트 설정

예제 프로젝트를 다운받아 시작한다.

pulumi new https://github.com/pulumi/pulumitv/tree/master/modern-infrastructure-wednesday/2020-12-09/lambda-containers

프로젝트 구성은 다음과 같다.

.
├── Pulumi.dev.yaml
├── Pulumi.yaml
├── README.md
├── __main__.py
├── __pycache__
├── app
├── build-ffmpeg
├── requirements.txt
└── venv

소스코드

먼저 리소스를 정의하고 있는 __main__.py를 살펴보자.

bucket = aws.s3.Bucket("bucket")

repo = aws.ecr.Repository("sampleapp")
ecr_creds = aws.ecr.get_authorization_token()

image = docker.Image("sampleapp",
    build="./build-ffmpeg",
    image_name=repo.repository_url,
    registry=docker.ImageRegistry(server=repo.repository_url, username=ecr_creds.user_name, password=ecr_creds.password))

버킷, ECR 저장소를 만들었다. 그리고 ECR에 접근하기 위한 접근 권한을 ecr_creds에 저장하고, 이를 도커 이미지를 ECR에 업로드할 때 registry=docker.ImageRegistry에 인자로 넣어주었다.

여기서 build 파라미터를 보면, 로컬 경로를 참조하고 있는 것을 알 수 있다. 이는 루트 경로의 build-ffmpeg 폴더를 확인하면 알 수 있는데, index.jsDockerfile이 있는 것을 알 수 있다. index.js는 ffmpeg 관련 작업을 수행하는 파일이므로 패스하고, Dockerfile을 확인해보자.

FROM public.ecr.aws/lambda/nodejs:10.2020.12.01.12
ARG FUNCTION_DIR="/var/task"

...

# Install ffmpeg
RUN curl https://johnvansickle.com/ffmpeg/releases/ffmpeg-release-amd64-static.tar.xz -o ffmpeg.tar.xz -s
RUN tar -xf ffmpeg.tar.xz
RUN mv ffmpeg-*-amd64-static/ffmpeg /usr/bin

...

# Set the CMD to your handler (could also be done as a parameter override outside of the Dockerfile)
CMD [ "index.handler" ]

여기서 알 수 있는 사실은 이 도커 이미지는 index.handler 핸들을 통해 함수가 실행된다는 것이고, node 런타임에서 작동한다는 것이다.

Pulumi는 Python으로 작성했지만, 실제 컨테이너 런타임은 Node이다.

Dockerfile에 ffmpeg 버전 관련 버그가 있는데, RUN mv ffmpeg-4.3.1-amd64-static/ffmpeg /usr/bin 라인의 4.3.1*로 수정한다.

role = aws.iam.Role("thumbnailerRole", assume_role_policy=f"""{{
  "Version": "2012-10-17",
  "Statement": [
    {{
      "Effect": "Allow",
      "Principal": {{ "Service": "lambda.amazonaws.com" }},
      "Action": "sts:AssumeRole"
    }}
  ]
}}""")

aws.iam.RolePolicyAttachment("lambdaFullAccess", role=role.name, policy_arn=aws.iam.ManagedPolicy.AWS_LAMBDA_FULL_ACCESS)

다음으로는 thumbnailerRole라는 이름의 iam Role을 하나 선언하고, 람다의 Full Access 권한을 부여한다. 여기서 aws.iam.ManagedPolicy.AWS_LAMBDA_FULL_ACCESSenum으로 미리 정의되어 있는(pre-defined) 리터럴이다.

aws.iam.RolePolicyAttachment(
    "lambdaFullAccess",
    role=role.name,
    policy_arn=aws.iam.ManagedPolicy.LAMBDA_FULL_ACCESS,
)

aws.iam.RolePolicyAttachment(
    "lambdaBasicRole",
    role=role.name,
    policy_arn=aws.iam.ManagedPolicy.AWS_LAMBDA_BASIC_EXECUTION_ROLE,
)

aws.iam.RolePolicyAttachment(
    "lambdaS3Role",
    role=role.name,
    policy_arn=aws.iam.ManagedPolicy.AMAZON_S3_FULL_ACCESS,
)

하지만 AWS에서 해당 Policy를 deprecate 시켰기 때문에 이 부분을 위 코드로 수정해준다.

thumbnailer = aws.lambda_.Function("thumbnailer", package_type="Image", image_uri=image.image_name, role=role.arn, timeout=60);

allow_bucket = aws.lambda_.Permission("allowBucket",
    action="lambda:InvokeFunction",
    function=thumbnailer.arn,
    principal="s3.amazonaws.com",
    source_arn=bucket.arn)

bucket_notification = aws.s3.BucketNotification("bucketNotification",
    bucket=bucket.id,
    lambda_functions=[aws.s3.BucketNotificationLambdaFunctionArgs(
        lambda_function_arn=thumbnailer.arn,
        events=["s3:ObjectCreated:*"],
        filter_suffix=".mp4",
    )],
    opts=pulumi.ResourceOptions(depends_on=[allow_bucket]))

pulumi.export("bucketName", bucket.bucket)

이제 Lambda 함수 thumbnailer를 선언하고, 아까 정의한 Role의 arn을 넣어준다.

다음으로 버킷에 파일이 추가될 경우 람다를 trigger하도록 allow_bucket 권한을 S3 버킷에 추가한다. 이 권한의 action이 action="lambda:InvokeFunction"인 것을 확인할 수 있다. 이제 버킷에 .mp4 파일이 추가될 경우 람다 함수가 trigger된다.

프로젝트 배포

이제 pulumi up을 입력해 프로젝트를 AWS에 배포해보자.

$ pulumi up
     Type                             Name                   Plan       
 +   pulumi:pulumi:Stack              lambda-containers-dev  create     
 +   ├─ aws:ecr:Repository            sampleapp              create     
 +   ├─ aws:s3:Bucket                 bucket                 create     
 +   ├─ docker:image:Image            sampleapp              create     
 +   ├─ aws:iam:Role                  thumbnailerRole        create     
 +   ├─ aws:iam:RolePolicyAttachment  lambdaFullAccess       create     
 +   ├─ aws:lambda:Function           thumbnailer            create     
 +   ├─ aws:lambda:Permission         allowBucket            create     
 +   └─ aws:s3:BucketNotification     bucketNotification     create

lambda-containers-dev 스택에 정의된 리소스 리스트가 출력된다. 하나하나 맞는지 확인을 해보면,

  • ECR 레포지터리
  • S3 버킷
  • Docker image
  • S3 Permission & Lambda trigger
  • Lambda IAM Role

이렇게 9개 리소스가 생성된다고 한다. yes를 선택해 계속 진행한다.

테스트

이제 샘플 mp4 파일을 다운받은 다음 S3 업로드해, 우리가 생각한대로 작동하는지 보자.

wget https://file-examples-com.github.io/uploads/2017/04/file_example_MP4_480_1_5MG.mp4 -O sample_00-01.mp4
pulumi logs --follow

위 명령어를 입력한 다음, 다른 셸에서 S3에 파일을 업로드한다.

aws s3 cp sample_00-01.mp4 s3://$(pulumi stack output bucketName)

그 다음 s3에서 썸네일을 다운로드 받아보자.

aws s3 cp s3://$(pulumi stack output bucketName)/sample.jpg sample.jpg

sample

정상적으로 썸네일이 열리는 것을 확인할 수 있다.

리소스 정리

pulumi destroy

테스트를 끝낸 다음엔 관련 리소스를 모두 삭제해준다.