LoginSignup
1
0

More than 1 year has passed since last update.

S3操作をするLambda関数(Python-image)をSAM CLIを使ってデプロイする手順をまとめてみる

Posted at

はじめに

Lambda関数を作成するにあたり、必要な手順をざっくりまとめました。
以下も試しました。

  • 1つのアプリケーションで複数の関数の定義
  • S3の操作周りをLambdaで行う
  • SAM CLIのローカルテストを試す

開発者のIAMユーザ作成

開発とデプロイで使用します。
スクリーンショット 2021-09-04 3.33.38.png
スクリーンショット 2021-09-04 3.38.21.png
あとは、デフォルトのまま作成し、アクセスキー、シークレットアクセスキーを保存

必要な場合アクセス権を追加

ユーザ作成時に追加した「AWSLambda_FullAccess」のみなので必要なものがあれば追加します。

権限 概要
AWSLambda_FullAccess Lambda、CloudWatchなど
AmazonS3FullAccess S3
AmazonAPIGatewayAdministrator API Gatewayの作成やデプロイに必要なポリシー
AmazonEC2ContainerRegistryFullAccess ECRの作成やデプロイに必要なポリシー 
IAMFullAccess AWSCloudFormationの作成やデプロイに必要なポリシー
AWSCloudFormationFullAccess AWSCloudFormationの作成やデプロイに必要なポリシー

実行ロールの作成

Lambda関数を実行するロールです。デプロイ時に指定しますのでしばらく使いません。
スクリーンショット 2021-09-04 4.21.07.png
スクリーンショット 2021-09-04 4.22.37.png
スクリーンショット 2021-09-04 4.25.47.png
スクリーンショット 2021-09-04 4.28.37.png

実行ロールにも必要な場合アクセス権を追加

Lambda実行時の権限で必要なものがあれば設定します。インラインポリシーを使うと最低限のポリシーを設定できます。
例)Lambdaから特定のS3のファイル取得を行う場合
スクリーンショット 2021-09-06 16.17.58.png

※実際には、S3アップロードを行う必要があるのでAmazonS3FullAccessをつけています。

まずはLambdaとは関係ないプログラムを書いてみる

S3を操作するPythonプログラムのサンプルです。

テスト読み取り用バケットにテスト用画像をアップしておく

ローカル実行用にテスト読み取り用バケットとテスト用画像をアップしておきます。
スクリーンショット 2021-09-06 19.36.09.png
Lambda関数とトリガーで紐づけられるS3は新たに作成する必要があるため、実際にはこのバケットは使いません。

環境はこちらの記事のosイメージをpythonに変えたものですが、なんでも構いません。
aws認証情報には、先ほど作成した開発者のIAMユーザを使います。

必要なライブラリをインストールする
$ pip install boto3

単に指定したS3バケットの画像「port.png」を取得してリネームし、書き込み用バケットにアップするプログラムです。

s3example.py
import boto3
import tempfile

s3 = boto3.resource('s3')
s3_write_bucket = '{書き込み用バケット}'


def hello(s3_read_bucket, filename):
    # ファイルの読み込み
    obj = s3.Object(s3_read_bucket, filename)
    response = obj.get()
    tmpdir = tempfile.TemporaryDirectory()
    fp = open(tmpdir.name + '/' + filename, 'wb')
    fp.write(response['Body'].read())
    fp.close;

    # ファイル名に.zipをつけてS3にアップロード
    zipname = tempfile.mkstemp()[1]
    obj = s3.Object(s3_write_bucket, filename + '.zip')
    response = obj.put(
        Body=open(tmpdir.name + '/' + filename, 'rb')
    )

    tmpdir.cleanup()
    return response


if __name__ == '__main__':
    print(hello("{テスト読み取り用バケット}", "port.png"))

動作確認します。

$ python s3example.py

アップされました。
スクリーンショット 2021-09-07 0.23.04.png

Lambda関数にする

lambda_handler関数を追加します。
トリガーから渡されるeventはS3オブジェクトの更新です。
中身については、「ローカルテスト」にて後述します。

s3example.py
import boto3
import tempfile

s3 = boto3.resource('s3')
s3_write_bucket = '{書き込み用バケット}' #循環参照防止のためreadとは別のバケット

############# 追加部分 #############
def lambda_handler(event, context):
    for rec in event['Records']:
        filename = rec['s3']['object']['key']
        s3_read_bucket = rec['s3']['bucket']['name']
        hello(s3_read_bucket, filename)
    return {
        "statusCode": 200,
    }
############# 追加部分 #############


def hello(s3_read_bucket, filename):
    # ファイルの読み込み
    obj = s3.Object(s3_read_bucket, filename)
    response = obj.get()
    tmpdir = tempfile.TemporaryDirectory()
    fp = open(tmpdir.name + '/' + filename, 'wb')
    fp.write(response['Body'].read())
    fp.close;

    # ファイル名に.zipをつけてS3にアップロード
    zipname = tempfile.mkstemp()[1]
    obj = s3.Object(s3_write_bucket, filename + '.zip')
    response = obj.put(
        Body=open(tmpdir.name + '/' + filename, 'rb')
    )

    tmpdir.cleanup()
    return response


if __name__ == '__main__':
    print(hello("{テスト読み取り用バケット}", "port.png"))
ローカルテスト

まず、コンソールのLambdaの「テスト」から「s3-put」のものを取得します。
スクリーンショット 2021-09-07 3.24.28.png

取得した情報を自身の環境に変更したものをfixtureにし、テストプログラムを作成しました。

tests/unit/test_s3example.py
import pytest

from hello_world import s3example


@pytest.fixture()
def apigw_event():
    return {
        "Records": [
            {
            "eventVersion": "2.0",
            "eventSource": "aws:s3",
            "awsRegion": "ap-northeast-1", # 変更部分
            "eventTime": "1970-01-01T00:00:00.000Z",
            "eventName": "ObjectCreated:Put",
            "userIdentity": {
                "principalId": "EXAMPLE"
            },
            "requestParameters": {
                "sourceIPAddress": "127.0.0.1"
            },
            "responseElements": {
                "x-amz-request-id": "EXAMPLE123456789",
                "x-amz-id-2": "EXAMPLE123/5678abcdefghijklambdaisawesome/mnopqrstuvwxyzABCDEFGH"
            },
            "s3": {
                "s3SchemaVersion": "1.0",
                "configurationId": "testConfigRule",
                "bucket": {
                "name": "{テスト読み取り用バケット}", # 変更部分
                "ownerIdentity": {
                    "principalId": "EXAMPLE"
                },
                "arn": "arn:aws:s3:::{テスト読み取り用バケット}" # 変更部分
                },
                "object": {
                "key": "port.png", # 変更部分
                "size": 1024,
                "eTag": "0123456789abcdef0123456789abcdef",
                "sequencer": "0A1B2C3D4E5F678901"
                }
            }
            }
        ]
    }


def test_lambda_handler(apigw_event, mocker):
    ret = s3example.lambda_handler(apigw_event, "")
    assert ret["statusCode"] == 200

pytestをインストールし、実行します。

$ pip install pytest pytest-mock --user
$ python -m pytest tests/ -v
tests/unit/test_handler.py::test_lambda_handler PASSED      [ 50%]
tests/unit/test_s3example.py::test_lambda_handler PASSED    [100%]
デプロイ

デプロイの準備を進めます。
複数の関数のデプロイを試したかったためこれまで触れていなかったHelloWorldFunctionも含まれます。

hello_world/Dockerfile
FROM public.ecr.aws/lambda/python:3.9
COPY app.py s3example.py requirements.txt ./
RUN python3.9 -m pip install -r requirements.txt -t .
template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  python3.9

  Sample SAM Template for lambda-python3.9

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3

Resources:
  MyS3Bucket:
    Type: AWS::S3::Bucket

  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      #Handler: app.lambda_handler
      ImageConfig:
        Command:
          - "app.lambda_handler"
      Role: {先の手順で作成したLambda 実行ロールの ARN}
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.9-v1

  S3exampleFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      #Handler: s3example.lambda_handler
      ImageConfig:
        Command:
          - "s3example.lambda_handler"
      Role: {先の手順で作成したLambda 実行ロールの ARN}
      Events:
        S3exampleEvent:
          Type: S3
          Properties:
            Bucket: !Ref MyS3Bucket
            Events: s3:ObjectCreated:*
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.9-v1

S3イベントの指定の仕方について参考にさせていただきました。

(samconfig.toml作成済み)
$ sam build
$ sam deploy

デプロイが完了したら動作確認をします。
コンソールからS3サービス画面に行き、作成されているS3バケットでファイルをアップロードします。
スクリーンショット 2021-09-07 3.42.50.png
書き込み用バケットにzipファイルが作成されました。
スクリーンショット 2021-09-07 3.43.37.png

おわりに

Lambda関数、慣れたら便利ですが、ログがわかりづらく慣れるまで戸惑いがありました。
config情報の環境変数化、レイヤーイメージやライブラリ同梱まで手が回りませんでした。
以下覚書です。

  • Commandを定義しないと2つ目の関数ハンドラが1つ目になる(関数名は2つ目だがハンドラがDockerfileのCMDにある1つ目になる)
  • HandlerImageと併用できない
  • Bucketは、同じ定義ファイル内のものを!Refで呼ぶ必要がある
  • Roleは、指定しないと特に権限がない実行ロールが作成され関数に紐づく
  • S3トリガーは、コンソール画面のLambdaからは見えないがS3の「イベント通知」でLambdaと紐づいているのが確認できる
  • S3の「イベント通知」でコンソールから編集(何も変えずOK)するとLambda側のトリガーとして表示される
  • 動作確認は、Lambdaの「テスト」か、本来のイベントを実行しCloudWatch log
  • デプロイ時、おかしくなったらコンソールからCloudFormationの関連スタックを削除
  • template.yamlを修正したらsam buildからやり直す
  • boto3は、イメージに最初から含まれているのでinstall不要。

お疲れ様でした。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0