poppler-utilsを使用するためDockerImageFunctionでlabmdaをデプロイした話

#AWS
#CDK
#Python
#DockerImageFunction

 

PDF画像変換をするため「pdf2image」をpipでインストールし、「poppler-utils」をyum installをしたlambdaの実行環境を作る必要があります

 

yum installを反映させたlambdaをCDKでデプロイするためにDockerImageFunctionを使用する方法でうまく動いたので備忘録としてまとめました

DockerImageFunction は、AWS Lambda 関数を作成するための CDK モジュールの一部です。Lambda 関数の実行環境として Docker イメージを使用できます。

 

通常の Lambda 関数では、ランタイムが提供され、関数コードはソースコードの形で提供されます。一方、DockerImageFunction を使用すると、Docker イメージ内に関数の実行環境と依存関係を含めることができます。このため、複雑なライブラリや環境変数のセットアップが必要な場合に便利です。

lambdaのコードを管理するディレクトリ構成は以下のように作ります。

 

今回、docker_image_functionというlambdaのディレクトリの他にcommonというディレクトリを作り、

 

docker_image_function/app.pyからa_module.pyのモジュールを読み込むという実装も行なっていきたいと思います。

root/ └ functions/ ├ common/ | └ common_modules/ | ├ a_module.py | └ b_module.py └ docker_image_function/ ├ app.py Dockerfile └ requirements.txt

Dockerfileは以下のように定義しました

 

今回の場合、poppler-utilsをインストールし、lambda上で実行できることが目標となります

 

"${LAMBDA_TASK_ROOT}"という予約された変数にソースコードをCOPYすることでlambdaで実行することが可能になります

 

以下4点をポイントに作成しました

 

1、rootユーザーでの実行を避けるため、appuserというユーザーを作成し、appuserで実行する

 

2、requirements.txtをソースからコピーしてpipライブラリをインストールする

 

3、lambdaソースコードの取り込み

 

4、上位ディレクトリのモジュールの取り込み

 

  • functions/docker_image_function/Dockerfile
FROM public.ecr.aws/lambda/python:3.10 # ここでyum installが必要なライブラリをインスールする RUN yum install -y shadow-utils-4.1.5.1 poppler-utils-0.26.5 && yum clean all ENV PATH="/usr/sbin:${PATH}" RUN useradd appuser && chown -R appuser /var/task # rootユーザーでの実行を避けるための処置 USER appuser COPY functions/docker_image_function/requirements.txt ${LAMBDA_TASK_ROOT} RUN pip install -r requirements.txt -t ${LAMBDA_TASK_ROOT} --no-cache-dir # lambdaコードのディレクトリをコピー COPY functions/docker_image_function ${LAMBDA_TASK_ROOT} # 上位ディレクトリのモジュールを使用したい場合はこのようにコピーする COPY functions/common ${LAMBDA_TASK_ROOT} CMD ["app.lambda_handler"]

 

上位ディレクトリにモジュールを定義します

 

例としてpdf2image.convert_from_pathを使った例を書きましたが、好きな処理を書いてください

 

  • functions/common/common_modules/a_module.py
from pdf2image import convert_from_path class A_MODULE: @staticmethod def do_something(): source_path = "<source_path>" image_path = "<image_path>" pages = convert_from_path(pdf_path=source_path, dpi=150) pages[0].save(str(image_path), "JPEG")

 

lambdaの実行の定義ファイルです

 

functions/docker_image_function/app.py

import os from common_modules.a_module import A_MODULE # Dockerfileでコピーしていれば上位ディレクトリを呼び出せる def lambda_handler(event, context): return A_MODULE.do_something()

イメージをビルドする際に使用するpipライブラリをここで定義します

 

functions/docker_image_function/requirements.txt

pdf2image

 

DockerImageFunctionのリソースを作成する例です

 

aws_lambda.DockerImageFunctionを使用します

 

codeにaws_lambda.DockerImageCodeを割り当てます

 

DockerImageCode.from_image_assetにて

 

directoryではdockerイメージに使用するディレクトリを指定します

 

fileにDockerfileのパスの指定

 

excludeなしでもビルドは可能ですが、キャッシュファイルなどが残っているとビルドに時間がかかり、容量が圧迫されます

 

gitignoreの対象になるようなファイルや、コードの実行に不要なテストファイルなどは除外するとよいと思います

 

from aws_cdk import Duration, RemovalPolicy from aws_cdk import aws_lambda as lambda_ from aws_cdk import aws_logs as logs class LambdaModule: def __init__( self, scope, name: str, handler: str, environment: dict, timeout: int, memory_size: int, src_dirname: str = None, # ex) ~/functions docker_file_path: str = None, # ex) ~/functions/docker_image_function/Dockerfile ): self.scope = scope self.name = name self.handler = handler self.environment = environment self.timeout = timeout self.memory_size = memory_size self.src_dirname = src_dirname self.docker_file_path = docker_file_path def create_docker_image_lambda_function(self): lambda_function = lambda_.DockerImageFunction( self.scope, f"{self.name}Function", code=lambda_.DockerImageCode.from_image_asset( directory=self.src_dirname, # 上位階層のモジュールを読み込みたい場合は上位からパス指定する必要がある file=self.docker_file_path, # Dockerfileのパス exclude=[ # gitignore対象のような不要なコード、テストファイル等を除外する "**.venv", "**.pytest_cache", "**cdk.out", "**node_modules", "**__pycache__", "/tests/" ], ), environment=self.environment, timeout=Duration.seconds(self.timeout), memory_size=self.memory_size, current_version_options={"removal_policy": RemovalPolicy.RETAIN}, log_retention=logs.RetentionDays.TWO_MONTHS, architecture=lambda_.Architecture.X86_64, ) self.lambda_function = lambda_function

DockerImageFunctionを使用する場合は、実行環境にDockerを使用できる状態にしておく必要があります。

 

yum installを必須となる処理には便利なリソースと思います

 

しかし、lambda_layerが使えないのとlambdaごとにビルド処理が実行されるので、lambdaリソースが増えるごとにデプロイに時間がかかってしまうため特別な処理が必要でない場合は避けた方が良さそうです

 

メリット:

  • yum installに必要な処理のlambdaを柔軟に作成できる

 

デメリット:

  • lambdaリソースの量が増えるごとにデプロイの時間がかかる
  • Dockerfileの作成など、考慮するソースコードが増える