AWS LambdaとServerless Advent Calendar 2021の12日目の記事です
背景
Pythonでテキストからカテゴリを推定する関数をLambdaで作った際に、mecabの辞書ライブラリやpandas, numpyがでかすぎてデプロイできずに右往左往したのですが、serverless frameworkとLambdaコンテナのおかげで簡単にデプロイができたので、感動をお伝えするためにこの記事を書いていこうと思います。
やりたかったこと
テキストがどのカテゴリに属するか、tf-idfとコサイン類似度を使って算出するというのが、今回やりたいことになります。
テキストは、S3のPUTされ、PUTイベントをトリガーに関数を走らせるようなイメージです。
以下はカテゴリ推定方法です。
- テキストをmecabを使ってわかち書き
- わかち書きされたテキストをtf-idfでベクトル化
- ベクタライザはカテゴリに含まれる単語のリスト化したものをつ使って事前計算したものを使う
- tf-idfベクトルをコサイン類似度を使って最もマッチするカテゴリを算出
できなかったこと
ライブラリ郡が大すぎて、デプロイできませんでした。
Lambdaには、250MB以上のパッケージをデプロイすることはできません。
(これは、Lambda Layerも同様です)
https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/gettingstarted-limits.html
特に、わかち書きのためにmecabを採用したため、日本語辞書が必要になり、ライブラリがかなり大きくなってしまいました。
これは、機械学習系のライブラリを利用していると、起りやすいことかと思います。
どう解決したか
Lambda Layerを使う
最初はLambda Layerを使ってデプロイしようと試みました。
究極、Layerを250MBづつ分けて作成すれば、デプロイすること自体は可能そうではありましたが、Layerの管理がかなり煩雑になる危険性がありました (何をLayerに含めるのか、どこまでLayerに含めるのか)
また、1つの関数に紐付けられるLambda Layerの数には5つまでという制限があり、今回は引っかかりはしませんでしたが、この点も懸念だったので、Lambda Layerを使う方法は断念しました。
Lambdaコンテナを使う
ちょうど去年のre:InventでLambdaのパッケージフォーマットにコンテナイメージがサポートされる発表がありました。
https://aws.amazon.com/jp/blogs/news/new-for-aws-lambda-container-image-support/
このLambdaコンテナは、LambdaでサポートしていないPHPなどのランタイムが動かせるようになるという点も大きいですが、10GBのコンテナイメージまでであればLambda上で動かすことができる点が今回のやりたかったことに非常にマッチしました。
そのため、今回はLambdaコンテナを使ったデプロイを採用しました。
どうデプロイしたのか
Lambdaコンテナを使ってデプロイすることは決まったものの、Lambdaにデプロイするツールはいくつか存在していたため、それぞれのツールについて検討しました。
Terraformを使う
既存のアプリケーションでは、Terraformを使っていたため、Terraformでデプロイすることを第1に検討しました。
しかし、TerraformだとDockerイメージをECRにpushするために新しく.tf
ファイルを作成する必要があったり、実行順序を考える必要があるなど、他のツールと比較すると初回の構築が面倒そうでした。
そのため、SAMや他の方式を使ったほうが楽そうという結論に至り、断念しました。
SAMを使う
次点で触り始めたのが、SAMでした。
SAMは以前に利用したことがあったため、まぁイケるだろうと思っていたのですが、思わぬ罠がありました。
今回の要件には「テキストは、S3のPUTされ、PUTイベントをトリガーに関数を走らせる」というものがあります。
ここがCDKでは、引っかかってしまいました。
S3 バケット名。このバケットは、同じテンプレートに存在する必要があります。
https://docs.aws.amazon.com/ja_jp/serverless-application-model/latest/developerguide/sam-property-function-s3.html
というのも、PUTされるS3が既に作成済みのS3ということが決まっており、SAMでは上記にあるように、同じテンプレートで作成したS3しかトリガーに指定できないという仕様が存在していたのです。
まさかの仕様に絶句しながらも、どうしようもないので新たなツールを探すべくAmazonの奥地にむか...(ってないです)。
serverless frameworkを使う
ということで、最後にたどり着いたのがserverless frameworkでした。
これまでserverless frameworkを触ってこなかった人間だったのですが、もっと早くに出会いたかったと思うくらい最高のツールでした。
良かった点を列挙します。
Lambdaコンテナのデプロイが簡単
TerraformやSAMでも、Docker Imageのデプロイが可能ですが、serverless frameworkもかなり簡単にコンテナイメージをLambdaと一気通貫でデプロイすることが可能です。
なんと、functions.{関数名}.image
を指定するだけで、image.name
で指定したディレクトリにあるDockerfileを勝手にビルドしてECRにpushして、そのECRにpushしたイメージを使ってLambdaを作成してくれます。
functions:
Predictor:
image:
name: predictor
たったこれだけの記述で至れり尽くせり感が半端ないです。
※ おまじないの記述をfunctionsの上に書く必要がありますが、簡単のため省略しています。
Triggerの指定が可能
これはSAMでできなかったことですが、serverless frameworkではテンプレートにない既存のバケットもトリガーとして指定することが可能です。
↓のように、existing: true
を指定することで、新しくS3バケットを作成せずとも、トリガーだけ作成してくれます。
functions:
Predictor:
image:
name: predictor
events:
- s3:
bucket: sample-bucket-name
existing: true
event: s3:ObjectCreated:*
rules:
- suffix: .csv
環境変数の管理がSAMより優れている
SAMやCloudFormationでは、環境別の変数を入れ込もうと思うと、かなり複雑な記法を使う必要がありました。
Parameters:
Env:
AllowedValues: ["dev", "stg", "prd"]
Type: String
Conditions:
IsProduction: { "Fn::Equals" : [ { "Ref" : "Env" }, "prd" ] }
Resources:
Instance:
Type: AWS::EC2::Instance
IamInstanceProfile:
Fn:If:
- IsProduction
- iamprofile
- !Ref AWS::NoValue
これが、serverless fremeworkを使うと簡単な記法で記述することができ、デプロイ時にパラメータを一つ指定するだけで、簡単に環境を切り替えることが可能となります。
custom:
defaultStage: stg
bucketName:
stg: stg-bucket-name
prd: prd-bucket-name
functions:
Predictor:
image:
name: predictor
environment:
S3_BUCKET_NAME: ${self:custom.bucketName.${self:provider.stage}}
sls deploy --stage prd
まとめ
機械学習をLambda上で実行させるために、色々と試行錯誤してきた過程を書いてきました。
機械学習のような巨大なライブラリを使うような関数ではLambdaコンテナを使うのが簡単でした。
また、Lambdaコンテナのデプロイに関してもSAMを使うよりserverless frameworkを使ったほうが圧倒的に簡単に書くことができました。
結論: AWS Lambdaで簡単機械学習 = Lambdaコンテナ + serverless framework
ということでした〜〜〜