Serverless Operations, inc

>_cd /blog/id_44qjd9baq4

title

サーバーレスな OpenCV + AWS Lambda (Layer) 構成をお手軽にデプロイする方法

summary

OpenCV を利用したいけど、わざわざコンテナやインスタンスを利用するほどでもない場合があります。AWS Lambda のコンテナイメージのランタイムで利用すれば大体問題なさそうですが、可能な限り認知負荷を増やさず、管理対象のリソースを最小限にする方法はないのでしょうか。本記事は、OpenCV を利用するために、Lambda を通常の Python ランタイムのサーバーレス構成でデプロイする方法をご案内します。

AWS Lambda の容量制限との向き合い方

サマリーでお話したように、AWS Lambda をなるべくシンプルな形で構成管理したいということを考えているのであれば、利用するモジュールは Lambda パッケージまたは Lambda Layer にまとめてデプロイしてしまいたいという要望もあるかと思います。この時、AWS Lambda にデプロイできるパッケージサイズの制限があれば注意が必要ですが、アップロードするコードのパッケージとレイヤー合わせて、圧縮解凍後のサイズ 250MB の壁にぶち当たることがあります。

※ AWS Lambda クォータ
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html

OpenCV を利用したい場合でも、通常のように opencv-python を入れてしまうとすぐ 200MB を超えてしまうため、もう少しサイズを減らして余裕を持つような方法がないか悩むことがあります。そういった場合は可能な限り OpenCV を含む依存モジュールを Lambda Layer にまとめ、アプリケーションのコードはコードパッケージに含めるオーソドックスなアプローチがおすすめです。

opencv-python-headless を利用する

AWS Lambda のコード内で OpenCV を利用するので、サーバー内での利用を目的とした Headless バージョンをパッケージに入れて利用する方法があります。Headless バージョンは GUI library dependencies を抜いているほか、場合によってはいくつか制約もあるため、事前にある程度以下のリンクで内容を把握しておくと良いです。通常のユースケースではさほど問題にならないかと思います。

https://pypi.org/project/opencv-contrib-python-headless/

ローカル環境でのパッケージ構成の例

パッケージ管理ツールとして poetry を利用している場合の構成例になります。

[tool.poetry]
name = "lambda-backend-opencv"
version = "0.1.0"
description = ""
authors = ["author <author@example.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.11"
opencv-python-headless = "^4.9.0.80"

[tool.poetry.group.dev.dependencies]
boto3 = "^1.34.74"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

AWS Lambda および Lambda Layer へデプロイする(AWS CDK)

それでは、デプロイをしていきます。上記のようなパッケージ構成を、以下のような構成でデプロイします。本記事の構成では、AWS CDK を利用するのが最もお手軽な方法です。CDK も Python で書くことができますが、この記事では型注釈とCDKコードのリファレンスの豊富さから TypeScript で書いていきます。

一つ注意点があり、AWS CDK を使ったデプロイには実行環境で Docker を利用する必要があります。パッケージを作るために Docker イメージを利用するバンドラーを使っており、ローカル環境または CI/CD 環境においては Docker が利用できる状態であることを確認しておいてください。ただし、CDKコードを除き、ビルドやパッケージ作成のための個別設定等は不要です。

ローカル環境のディレクトリ構成は、以下のような形を想定しています。

project/deploy/cdk-deploy.ts
project/deploy/LambdaBackendOpenCVStack.ts

# Lambda Layer にデプロイするパッケージを作成するための temp ディレクトリです。
# .gitignore に登録するなどして管理対象から外してください。
project/deploy/.lambda-layer

まずは、poetry に登録した依存モジュールをインストールして、requirements.txt に出力し、モジュールたちを .lambda-layer にまとめて入れておきます。

$ poetry install --no-interaction
$ poetry export --without=dev --output requirements.txt
$ pip install -r requirements.txt -t deploy/.lambda-layer

CDK のスタックは、以下のように定義します。

import * as cdk from 'aws-cdk-lib'
import { DockerImage } from 'aws-cdk-lib'
import { Function, Architecture, Runtime } from 'aws-cdk-lib/aws-lambda'
import { PythonFunction, PythonLayerVersion } from '@aws-cdk/aws-lambda-python-alpha'
import * as iam from 'aws-cdk-lib/aws-iam'
import { Construct } from 'constructs'

export interface OpenCVStackProps extends cdk.StackProps {
  stage: string
  lambdaEnv: Record<string, string>
}

export class LambdaBackendOpenCVStack extends cdk.Stack {

  public readonly openCvFunction: Function

  constructor(scope: Construct, id: string, props: OpenCVStackProps) {
    super(scope, id, props)

    // ...

    const bundling = {
      image: DockerImage.fromBuild('deploy/bundler', {
        buildArgs: { IMAGE: 'public.ecr.aws/sam/build-python3.9:1.113' }
      }),
    }

    this.openCvFunction = new PythonFunction(this, 'lambdaBackendOpenCvRole', {
      functionName: `lambda-backend-opencv-${props.stage}`,
      runtime: Runtime.PYTHON_3_11,
      entry: 'src/',
      handler: 'lambda_handler',
      memorySize: 512,
      architecture: Architecture.X86_64,
      bundling,
      timeout: cdk.Duration.seconds(180),
      environment: {
        STAGE: props.stage,
        ...props.lambdaEnv
      },
      layers: [
        new PythonLayerVersion(this, 'lambdaBackendOpenCvLayer', {
          compatibleArchitectures: [ Architecture.X86_64 ],
          compatibleRuntimes: [ Runtime.PYTHON_3_11 ],
          bundling,
          entry: 'deploy/.lambda-layer/',
        }),
      ],
      role: lambdaBackendOpenCvRole,
    })
  }
}

その後、 cdk-deploy.ts にも以下のように定義します。

#!/usr/bin/env node
import 'source-map-support/register'
import * as cdk from 'aws-cdk-lib'
import { LambdaBackendOpenCVStack } from './LambdaBackendOpenCVStack'

import * as dotenv from 'dotenv'
dotenv.config()

const { STAGE = 'dev', AWS_REGION = 'us-west-2' } = process.env

const createApp = (): cdk.App => {

  const app = new cdk.App()
  const {
    // no usage at this time
  } = app.node.tryGetContext(STAGE) || app.node.tryGetContext('dev')

  new LambdaBackendOpenCVStack(app, `lambda-backend-opencv-${STAGE}`, {
    stage: STAGE,
    env: {
      account: app.account,
      region: AWS_REGION
    },
    lambdaEnv: { /* ... */ }
  })
  return app
}

createApp()

準備ができたら、以下のようにデプロイします。

$ npx cdk deploy --all --require-approval never

デプロイ後の状態を確認

無事にデプロイに成功したら、AWS マネジメントコンソールを開いて確認していきます。Lambda Layer は以下のようになっており、ダウンロードしてサイズを確認してみると 81.9 MB になります。

Lambda 関数本体の方は以下のようになっています。ソースコードには 100行程度の簡単な処理が含まれています。

デプロイした Lambda 関数を実行し、 import cv2 がエラーなく通せて利用できればOKとなります。

この記事に関する内容を含め、OpenCVとAWSを使った開発全般においてサポートが必要な場合は、お気軽にお問い合わせください。

Written by
COO

金 仙優

Sonu Kim

  • Facebook->
  • X->
  • GitHub->

Share

Facebook->X->
Back
to list
<-