##はじめに
Lambda関数を作成するにあたり、必要な手順をざっくりまとめました。
以下も試しました。
- 1つのアプリケーションで複数の関数の定義
- S3の操作周りをLambdaで行う
- SAM CLIのローカルテストを試す
##開発者のIAMユーザ作成
開発とデプロイで使用します。
あとは、デフォルトのまま作成し、アクセスキー、シークレットアクセスキーを保存
#####必要な場合アクセス権を追加
ユーザ作成時に追加した「AWSLambda_FullAccess」のみなので必要なものがあれば追加します。
権限 | 概要 |
---|---|
AWSLambda_FullAccess | Lambda、CloudWatchなど |
AmazonS3FullAccess | S3 |
AmazonAPIGatewayAdministrator | API Gatewayの作成やデプロイに必要なポリシー |
AmazonEC2ContainerRegistryFullAccess | ECRの作成やデプロイに必要なポリシー |
IAMFullAccess | AWSCloudFormationの作成やデプロイに必要なポリシー |
AWSCloudFormationFullAccess | AWSCloudFormationの作成やデプロイに必要なポリシー |
##実行ロールの作成
Lambda関数を実行するロールです。デプロイ時に指定しますのでしばらく使いません。
#####実行ロールにも必要な場合アクセス権を追加
Lambda実行時の権限で必要なものがあれば設定します。インラインポリシーを使うと最低限のポリシーを設定できます。
例)Lambdaから特定のS3のファイル取得を行う場合
※実際には、S3アップロードを行う必要があるのでAmazonS3FullAccess
をつけています。
##まずはLambdaとは関係ないプログラムを書いてみる
S3を操作するPythonプログラムのサンプルです。
#####テスト読み取り用バケットにテスト用画像をアップしておく
ローカル実行用にテスト読み取り用バケットとテスト用画像をアップしておきます。
Lambda関数とトリガーで紐づけられるS3は新たに作成する必要があるため、実際にはこのバケットは使いません。
環境はこちらの記事のosイメージをpythonに変えたものですが、なんでも構いません。
aws認証情報には、先ほど作成した開発者のIAMユーザを使います。
$ pip install boto3
単に指定したS3バケットの画像「port.png」を取得してリネームし、書き込み用バケットにアップするプログラムです。
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
##Lambda関数にする
lambda_handler
関数を追加します。
トリガーから渡されるevent
はS3オブジェクトの更新です。
中身については、「ローカルテスト」にて後述します。
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」のものを取得します。
取得した情報を自身の環境に変更したものをfixtureにし、テストプログラムを作成しました。
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も含まれます。
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 .
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イベントの指定の仕方について参考にさせていただきました。
$ sam build
$ sam deploy
デプロイが完了したら動作確認をします。
コンソールからS3サービス画面に行き、作成されているS3バケットでファイルをアップロードします。
書き込み用バケットにzipファイルが作成されました。
##おわりに
Lambda関数、慣れたら便利ですが、ログがわかりづらく慣れるまで戸惑いがありました。
config情報の環境変数化、レイヤーイメージやライブラリ同梱まで手が回りませんでした。
以下覚書です。
-
Command
を定義しないと2つ目の関数ハンドラが1つ目になる(関数名は2つ目だがハンドラがDockerfileのCMDにある1つ目になる) -
Handler
はImage
と併用できない -
Bucket
は、同じ定義ファイル内のものを!Ref
で呼ぶ必要がある -
Role
は、指定しないと特に権限がない実行ロールが作成され関数に紐づく - S3トリガーは、コンソール画面のLambdaからは見えないがS3の「イベント通知」でLambdaと紐づいているのが確認できる
- S3の「イベント通知」でコンソールから編集(何も変えずOK)するとLambda側のトリガーとして表示される
- 動作確認は、Lambdaの「テスト」か、本来のイベントを実行しCloudWatch log
- デプロイ時、おかしくなったらコンソールからCloudFormationの関連スタックを削除
-
template.yaml
を修正したらsam build
からやり直す -
boto3
は、イメージに最初から含まれているのでinstall不要。
お疲れ様でした。