404 not found

備忘録です

AWS SAMでs3 eventをトリガーにしたlambdaを実装する

sfender.hatenablog.com の続き

やりたいこと

前回記事で作成したlambdaの親lambdaをs3eventをトリガーにして起動したい。 s3イベントの条件は、pngデータがuploadされたとき。

template.yaml

変更したのはtemplate.yamlのみ。

sam-example-put-objectというバケットを作成して、 同バケットpngファイルが保存されたのをトリガーにHelloWorldFunctionを起動するようにしている。

lambdaの処理は適当。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  Sample SAM Template for multiple lambda
Globals:
  Function:
    Timeout: 3
    Environment:
      Variables:
        SAMPLACE: "AWS"
Parameters:
  BucketName:
    Type: String
    Default: sam-example-put-object
  FuncParentName:
    Type: String
    Default: HelloWorldFunctionParent
  FuncChildName:
    Type: String
    Default: HelloWorldFunctionChild
  DataSuffix:
    Type: String
    Default: png
Resources:
  DataBucket:
    Type: "AWS::S3::Bucket"
    Properties:
      BucketName: !Sub "${BucketName}"
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      FunctionName: !Sub "${FuncParentName}"
      Environment:
        Variables:
          CHILDLAMBDAARN: !GetAtt HelloWorldFunctionChild.Arn
      Events:
        BucketEvent:
          Type: S3
          Properties:
            Bucket:
              Ref: DataBucket
            Events:
              - "s3:ObjectCreated:*"
            Filter:
              S3Key:
                Rules:
                  - Name: suffix
                    Value: !Sub "${DataSuffix}"
      Policies:
        - CloudWatchPutMetricPolicy: {}
        - LambdaInvokePolicy:
            FunctionName: !Sub "${FuncChildName}"
        - S3CrudPolicy:
            BucketName: !Sub "${BucketName}"
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.8-v1
  HelloWorldFunctionChild:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      FunctionName: !Sub "${FuncChildName}"
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world_child
      DockerTag: python3.6-v1
Outputs:
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionChild:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunctionChild.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn
  HelloWorldFunctionChildIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionChildRole.Arn

tips

  • resouce外でバケット名を作っておく
    • こうしておかないとpolicyのリソース指定とかで循環参照エラーが発生する。
  • 親lambdaにpolicyを設定
    • 子lambdaを叩けるようにpolicyをアタッチ。
    • この時、cloudwatchのput metricsをつけておかないとログが見れない
  • parameterの設定
    • 関数名とかは外だししてみた。

AWS SAMで複数lambdaを作成する

やりたいこと

AWS SAMで複数のlambdaをコンテナイメージから作成&管理したい。

また、親labmdaから子lambdaをinvokeしたい。

動機

処理の都合上、lambdaが複数必要だった。 こういう場合、STEP FUNCTIONSでやるべきでは?とは思っているが、 とりあえずLAMBDAでやってみる。

ディレクトリ構成

各lambdaのdockerfileやapp.pyを各ディレクトリで管理しているだけ。

.
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── Dockerfile
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── hello_world_child
│   ├── Dockerfile
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── local-enviroment.json
├── samconfig.toml
├── template.yaml
└── tests
    ├── __init__.py
    └── unit
        ├── __init__.py
        └── test_handler.py

template.yaml

HelloWorldFunction側でHelloWorldFunctionChildのArnと名前を取得するようにしている。

また、childをinvokeするroleも設定している。

各lambdaでどのdockerfileを参照するかは、dockercontextで簡単に指定可能。

AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description: >
  Sample SAM Template for multiple lambda

Globals:
  Function:
    Timeout: 3
    Environment:
      Variables:
        SAMPLACE: "AWS"

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      Role: !GetAtt InvokeLambdaIamRole.Arn
      Environment:
        Variables:
          CHILDLAMBDAARN: !GetAtt HelloWorldFunctionChild.Arn
          CHILDLAMBDANAME: !Ref HelloWorldFunctionChild
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.8-v1
  HelloWorldFunctionChild:
    Type: AWS::Serverless::Function
    Properties:
      PackageType: Image
      FunctionName: "HelloWorldFunctionChild"
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world_child
      DockerTag: python3.6-v1
  InvokeLambdaIamRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: lambda.amazonaws.com
            Action: "sts:AssumeRole"
      Policies:
        - PolicyName: "invoke_lambda"
          PolicyDocument:
            Version: "2012-10-17"
            Statement:
              - Effect: "Allow"
                Action: "lambda:InvokeFunction"
                Resource: !GetAtt HelloWorldFunctionChild.Arn

Outputs:
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  HelloWorldFunctionChild:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunctionChild.Arn
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt InvokeLambdaIamRole.Arn
  HelloWorldFunctionChildIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionChildRole.Arn

local-enviroment.json

これは、sam local invoke --env-vars local-enviroment.jsonとして与える設定ファイル。

これを元にlambda呼び出しのlocal invokeを実行したかったが、よくわからなかった。

CHILDLAMBDANAMEもそのために環境変数として設定下が、結局使っていない。

mockでやるしかないかな。

{
    "Parameters": {
        "SAMPLACE": "LOCAL"
    }
}

lambda

dockerfileはほとんどsam initそのままなので割愛。 一応、pythonのversionをかえてみたりしているが、問題はない。

hello_world

app.py

import json
import os
import boto3

place = os.environ["SAMPLACE"]
child_lambda_arn = os.environ['CHILDLAMBDAARN']
child_lambda_name = os.environ['CHILDLAMBDANAME']

print('place is:', place)
print('child lambda arn is:', child_lambda_arn)
print('child lambda name is:', child_lambda_name)
if place == "AWS":
    lambda_client = boto3.client('lambda')
else:
    # mock client for local test
    lambda_client = boto3.client('lambda')


def lambda_handler(event, context):

    input_event = {
        "param1": 1,
        "param2": 2,
        "param3": 3
    }
    Payload = json.dumps(input_event)
    res = lambda_client.invoke(
        FunctionName=child_lambda_arn,
        InvocationType='RequestResponse',
        Payload=Payload
    )

    print("---02: response:", res)

    body = json.loads(res['Payload'].read())
    print("---03: body:", body)

    return {
        "statusCode": 200,
        "body": json.dumps(
            {
                "message": "hello world",
            }
        ),
    }

hello_world_child

app.py

import json

def lambda_handler(event, context):
    return {
        "statusCode": 200,
        "body": json.dumps(
            {
                "message": "hello world from child",
            }
        ),
    }

build

sam build

local invoke

sam local invoke HelloWorldFunction

sam local invoke HelloWorldFunctionChild

が、親の方はlocalでlambda実行できない問題のためエラーが出る。

deploy

sam deploy

最初だけは sam deploy --guided

この時、ECRのリポジトリはちゃんと個別に入力しないといけない。親切。

できなかったこと

親lambdaのlocal invokeができない。

mockじゃなくて直接invokeしたいけど、正直よくわからない。

参考

AWS LambdaからLambdaを非同期で呼び出す(Python) - Qiita

【AWS SAM 入門⑥】IAM ロールの作成と Lambda Function への割り当て - log4ketancho

AWS SAMでローカル環境でS3とDynamoDBを扱うLambdaを実行する - Qiita

python - How to invoke AWS lambda from another lambda within SAM local? - Stack Overflow

DVCとkedro

背景

自分でMLを開発している時に、ワークフロー管理(データ準備、前処置、保存、学習、評価)までが蛸壷状態になっていることが多い。

DVCは前から知っていたのだが、kedroを知ったので、改めて調べて見た。

まとめ

DVCはデータバージョン管理が主であり、ワークフロー管理だけするのは違う気がした。 比較することが間違いだったかもしれない。

個人でワークフロー管理するだけなら、ワークフロー管理が主のkedroを利用するのが良いと思った。 しかし、まだv0.16.4なのでその辺りが気がかり。

DVCは、データがコロコロ変わるような環境でMLopsをしっかり行っていく場合に良いツールだと思う。 CMLと組み合わせることで、良いのだろう。

前提

MLOps

機械学習システムの開発でも、CI/CD適用しましょうね、という話。

詳しくは下記ブログを参照。 - データ、モデル、コードの3要素がMLシステムには含まれている - データサイエンティスト、データエンジニア、デベロッパーそれぞれでMLシステムで担保箇所が異なる - 有用なツール(例:DVC、mlflow)

などなどいろんなことが載っている。

martinfowler.com

上記、ワークフローがごちゃごちゃ、みたいな状態も再現性が担保できないという意味で、MLOpsとしてよくない状態。

DVC

概要

その名の通り、Data Version Controlのためのツール。

youtu.be

データバージョン管理だけでなく、ワークフローの管理から、metricsの可視化までできる。

  • data versioning
    • 文字通りデータのバージョン管理。キャッシュ利用している。また、DVCリポジトリを設定することで、データ共有も可能。
  • data access
    • S3, GCS, sshなど多様な接続先からデータの取得が可能
  • data pipeline
    • ワークフロー管理。コマンドで設定すると自動でyamlが生成されるぽい。
  • experiments
    • 実験管理。ブランチ間で指標比較が可能。

後述の同コミュニティがCMLというツールも開発している。 こちらは、プルリク時にmasterとtarget branch間との指標比較の自動化をしてくれるっぽい。 同コミュニティということもあり、dvc pullを利用してgitリポジトリ外のデータを引っ張ってくることもできる。

コミュニティ

dvc.org

バックは企業で、DVCとCML の開発を行っている。 というかこれのための企業?

活発な印象でOSS選定する時に懸念するようなことはない気がする。

所感

結構、使うのが厳しそう。 gitと密に連携しているのがエンジニアに取っては良いところでもあり、非gitユーザーにとってのハードルの高さでもある気がする。

また、data versioningは果たしてどこまで使うのだろうか。 結構な頻度かつ、知らぬ間に学習データが変わっている、ということがなければ必要ないのかもしれない。 それこそ私の職場では、学習データがほとんど更新されない(年2回程度)ので、調べながら実際のユースケースが少ないと思った。 しかし、webアプリ系の企業の場合は日毎に学習と評価を繰り返すことがあって、それに対してdata versioningが有用なのかもしれない。

リポジトリ間を使って、データ共有できる、というのは結構メリットあると思うけど、多人数でMLシステムを開発したことがないので、メリットの実感が沸かない。 s3もあるし。 再現性という意味では一番適していいるのかもしれないが。

ロールバック機能もある(多分。)

さらに言うと、gitの開発も頻繁なのになぜかワークフロー管理の日本語ブログ記事が出てこない。大体は使ってみた程度。 ただの予想だが、DVCは個人用途に向かないからそもそもブログで書かない、エンジニアよりすぎるツールでとっつきにくい、と言うのが背景にあるのではないだろうか。

kedro

github.com kedro.readthedocs.io

概要

MLシステムのワークフロー管理のためのpythonフレームワーク。 ワークフロー管理に特化しているので、実験管理などは含まれない。 データカタログという機能があって、データ取得は様々なストレージに対応、管理できる。 独自のパイプラインも定義することができるので拡張性も高い。

使い方

これらのブログ見てもらった方が良い。とてもよく纏まっていて参考になります。

小さく始めて大きく育てるMLOps2020 | AI tech studio

medium.com

コミュニティ

こちらも背景にあるのは、企業。 DVCよりは少ないけども、gtiの開発も安定している。 OSS選定としては問題ない気もする。

所感

pythonフレームワークで拡張性が高いという点、ディレクトリテンプレートがある点などいろいろと使いやすそう。 DVCと比較すると開発コミュニティが小さい気もするけど、許容範囲だとは思う。 残念なのはまだversion 0.16.4だということ(2020/08/14時点)。

今度使ってみようと思う。

参考