どこにでもいる30代SEの学習ブログ

主にプログラミング関連の学習内容。読んだ本の感想や株式投資についても書いてます。

【python】OpenCVでImportErrorが出たときの対処法(Amazon Linux2)

「ImportError: libGL.so.1: cannot open shared object file: No such file or directory」が出たときの対応策です。

環境によって対応策は異なります。紹介するのはAmazon Linux2の対応策です。

環境

事象発生時の状況

import cv2

OpenCVをインポートすると、次のエラーが発生します。

---------------------------------------------------------------------------
ImportError                               Traceback (most recent call last)
/tmp/ipykernel_22658/3138839957.py in <module>
----> 1 import cv2

~/.local/lib/python3.7/site-packages/cv2/__init__.py in <module>
      3 import sys
      4 
----> 5 from .cv2 import *
      6 from .data import *
      7 

ImportError: libGL.so.1: cannot open shared object file: No such file or directory

対応策

「mesa-libGL」をyumでインストールすると解決します。

sudo yum -y install mesa-libGL

参考文献

mesa - ImportError: libGL.so.1 on CentOS - Stack Overflow

【AWS】pythonでS3のファイルを操作する手順(Boto3)

pythonのプログラムからS3を操作する手順をまとめました。ファイルのアップロード/ダウンロードなど、基本的な手順を書いています。

[1] 前提条件

AWS EC2環境で実行する際の手順です。ライブラリとして「AWS SDK for Python (Boto3)」を使用しています。

  • EC2
  • Amazon Linux2
  • Python3
  • Boto3

[2] 準備

[2-1] boto3のインストール

pipを使ってインストールします。

pip3 install boto3 --user

Quickstart — Boto3 Docs 1.17.102 documentation

[2-2] EC2にIAMロールを付与する

詳細は末尾の補足に記載しますが、IAMロールを作成し、以下のIAMポリシーを適用します。作成したIAMロールをEC2インスタンスに設定します。

IAMポリシー以下の権限を付与しています。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "ListAllBuckets",
            "Effect":"Allow",
            "Action": "s3:ListAllMyBuckets",
            "Resource":"arn:aws:s3:::*"
        },
        {
            "Sid": "ListObjectsInBucket",
            "Effect": "Allow",
            "Action": ["s3:ListBucket","s3:GetBucketLocation"],
            "Resource": ["arn:aws:s3:::bucket-name"]
        },
        {
            "Sid": "ObjectActions",
            "Effect": "Allow",
            "Action": ["s3:GetObject","s3:PutObject","s3:DeleteObject"],
            "Resource": ["arn:aws:s3:::bucket-name/*"]
        }
    ]
}

IAMポリシーは下記資料を参考にしています。

[3] Boto3を使った操作の概略

やり方は色々とありますが、以下のソースコードで基本的なことは出来ます。

import boto3

BUCKET_NAME = 'bucket_name'

# 全バケットを表示
s3 = boto3.resource('s3')
for bucket in s3.buckets.all():
    print(bucket.name)

# バケット内の全オブジェクトを表示
bucket = s3.Bucket(BUCKET_NAME)
objects = bucket.objects.all()
for obj in objects:
    print(obj.key)
    
# オブジェクトをダウンロード
bucket.download_file('input/file.txt', 'file.txt')

# オブジェクトをアップロード
bucket.upload_file('file.txt', 'output/file.txt')

# オブジェクトを削除
s3.Object(BUCKET_NAME, 'output/file.txt').delete()

前半は全バケットの表示と、バケット内オブジェクトの表示です。後半はバケットからファイルをダウンロードし、別名でアップロードしたのち削除しています。

[4] Boto3を使った操作の詳細

boto3.resourceboto3.client、2つの書き方があります。両者とも概ね同じことができるようになっています。

[4-1] 全バケットを表示

QuickStartに書いてあるソースコードです。非常に簡単です。

import boto3

s3 = boto3.resource('s3')
for bucket in s3.buckets.all():
    print(bucket.name)

Quickstart — Boto3 Docs 1.17.102 documentation

以下の書き方でも、同じことが実現できます。

s3 = boto3.client('s3')
response = s3.list_buckets()

print('Existing buckets:')
for bucket in response['Buckets']:
    print(f'  {bucket["Name"]}')

Amazon S3 buckets — Boto3 Docs 1.17.102 documentation

[4-2] バケット内のオブジェクトを表示

[4-2-1] 全オブジェクトを表示

boto3.resourceを使用した書き方は次の通りです。

BUCKET_NAME = 'bucket_name'

s3 = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
for obj in bucket.objects.all():
    print(obj.key)

S3 — Boto3 Docs 1.17.102 documentation

boto3.clientを使用した書き方は次の通りです。どちらを使用しても同じ出力結果が得られます。

BUCKET_NAME = 'bucket_name'

s3 = boto3.client('s3')
response = s3.list_objects_v2(Bucket=BUCKET_NAME)
for obj in response['Contents']:
    print(obj['Key'])

S3 — Boto3 Docs 1.17.102 documentation

例として以下のような出力結果が得られます。

# dir1/
# dir1/file1.txt
# dir1/file2.txt
# dir2/
# dir2/file3.csv
# dir2/file4.txt
# file5.csv

下記ファイル構成の場合の出力結果です。

bucket_name
├── dir1
│   ├── file1.txt
│   └── file2.txt
├── dir2
│   ├── file3.csv
│   └── file4.txt
└── file5.csv

[4-2-2] Prefixに該当するオブジェクトを表示

指定したPrefixに該当するオブジェクトを表示することも可能です。

boto3.resourceを使用した書き方は以下の通りです。

s3 = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
objects = bucket.objects.filter(Prefix='dir1')
for obj in objects:
    print(obj.key)
    # dir1/
    # dir1/file1.txt
    # dir1/file2.txt

boto3.clientを使用した書き方は以下の通りです。

s3 = boto3.client('s3')
response = s3.list_objects_v2(Bucket=BUCKET_NAME, Prefix='dir2')
for obj in response['Contents']:
    print(obj['Key'])
    # dir2/
    # dir2/file3.csv
    # dir2/file4.txt

[4-2-3] 階層化のオブジェクトのみ表示

Delimiterを使用することで階層直下のオブジェクトのみ表示します(再帰表示しない)。以下の例ではバケットの直下のみ表示しています。

boto3.resourceを使用した書き方は以下の通りです。

s3 = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
objects = bucket.objects.filter(Delimiter='/')
for obj in objects:
    print(obj.key)
    # file5.csv

boto3.clientを使用した書き方は以下の通りです。

s3 = boto3.client('s3')
response = s3.list_objects_v2(Bucket=BUCKET_NAME, Delimiter='/')
for obj in response['Contents']:
    print(obj['Key'])
    # file5.csv

[4-2-4] Keyが1,000件以上になる場合の対処法

オブジェクト情報取得には制限があります。ワンオペレーションで取得できるKeyは最大で1,000件までです。1,000件を超えることが想定される場合、それを見越した処理が必要になります。

以下の処理では、テスト用に一度で5件までしか取得できないようにしています。MaxKeys=5です。Markerにキーを指定すると、そのキーの次のオブジェクトから取得します。

s3 = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
marker = ''
while  True:

    # オブジェクト取得
    objects = bucket.objects.filter(Marker=marker, MaxKeys=5)
    
    # オブジェクト表示
    last_key = None
    for obj in objects:
        print(obj.key)
        last_key = obj.key
    
    # 最後のキーをMarkerにセットし次のオブジェクト取得を行う。
    # オブジェクト取得が完了していたら終了。
    if last_key is None:
        break
    else:
        marker = last_key

boto3.clientの場合、MarkerではなくContinuationTokenを使用します。

s3 = boto3.client('s3')

# オブジェクト取得
response = s3.list_objects_v2(Bucket=BUCKET_NAME, MaxKeys=5)
while True:

    # オブジェクト表示
    for obj in response['Contents']:
        print(obj['Key'])
    
    # 'NextContinuationToken'が存在する場合は、次のデータ取得。
    if 'NextContinuationToken' in response:
        token = response['NextContinuationToken']
        response = s3.list_objects_v2(Bucket=BUCKET_NAME, MaxKeys=5, ContinuationToken=token)
    else:
        break

[4-2-5] resourceとclientで得られる情報の違い

上の例ではオブジェクトの「key」情報のみを取得していますが、他にも含まれる情報があります。そして、boto3.resourceboto3.clientで持つ情報が若干異なります。

boto3.resourceで得られる情報は次の通りです。

# 〜〜〜(省略)〜〜〜
for obj in bucket.objects.all():
    print(obj)
    # s3.ObjectSummary(bucket_name='bucket_name', key='dir1/')
    print(obj.bucket_name)
    # bucket_name
    print(obj.key)
    # dir1/
    print(obj.e_tag)
    # "d41d8cd98f00b204e9800998ecf8427e"
    print(obj.last_modified)
    # 2021-06-30 09:28:47+00:00
    print(obj.owner)
    # None
    print(obj.size)
    # 0
    print(obj.storage_class)
    # STANDARD

boto3.clientで得られる情報は次の通りです。

# 〜〜〜(省略)〜〜〜
for obj in response['Contents']:
    print(obj)
    # {'Key': 'dir1/', 
    #  'LastModified': datetime.datetime(2021, 6, 30, 9, 28, 47, tzinfo=tzlocal()), 
    #  'ETag': '"d41d8cd98f00b204e9800998ecf8427e"', 
    #  'Size': 0, 
    #  'StorageClass': 'STANDARD'}

[4-3] バケットからファイルをダウンロード

[4-3-1] ファイルにダウンロード

boto3.resourceの手順は以下の通りです。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME1 = 'dir1/file1.txt'
FILE_NAME1 = 'file1.txt'

s3 = boto3.resource('s3')
s3.Bucket(BUCKET_NAME).download_file(OBJECT_NAME1, FILE_NAME1)

S3 — Boto3 Docs 1.17.102 documentation

boto3.clientの手順は以下の通りです。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME2 = 'dir1/file2.txt'
FILE_NAME2 = 'file2.txt'

s3 = boto3.client('s3')
s3.download_file(BUCKET_NAME, OBJECT_NAME2, FILE_NAME2)

S3 — Boto3 Docs 1.17.103 documentation

[4-3-2] ファイルライクオブジェクトにダウンロード

ファイルを直接ダウンロードするだけで無く、ファイルライクオブジェクトへのダウンロードも可能です。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME3 = 'dir2/file3.csv'
FILE_NAME3 = 'file3.csv'

s3 = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
with open(FILE_NAME3, 'wb') as f:
    bucket.download_fileobj(OBJECT_NAME3, f)

あまり使いどころが無いかもしれませんが、以下のように一時ファイルに出力する等が可能です。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME3 = 'dir2/file3.csv'
FILE_NAME3 = 'file3.csv'

s3 = boto3.client('s3')
with tempfile.NamedTemporaryFile(mode='wb') as f:
    s3.download_fileobj(BUCKET_NAME, OBJECT_NAME4, f)
    print(f.name)
    # /tmp/tmppjvqnyf5
    print(f.tell)
    # <function BufferedWriter.tell at 0xffff90ff9290>

[4-4] バケットにファイルをアップロード

アップロードもダウンロードと同様の手順です。

boto3.resourceの手順は以下の通りです。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME1 = 'dir3/file1.txt'
FILE_NAME1 = 'file1.txt'
OBJECT_NAME2 = 'dir3/file2.txt'
FILE_NAME2 = 'file2.txt'

s3 = boto3.resource('s3')
s3.Bucket(BUCKET_NAME).upload_file(FILE_NAME1, OBJECT_NAME1)

s3  = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
with open(FILE_NAME2, 'rb') as f:
    bucket.upload_fileobj(f, OBJECT_NAME2)

S3 — Boto3 Docs 1.17.102 documentation

boto3.clientの手順は以下の通りです。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME3 = 'dir3/file3.csv'
FILE_NAME3 = 'file3.csv'
OBJECT_NAME4 = 'dir3/file4.txt'
FILE_NAME4 = 'file4.txt'
    
s3 = boto3.resource('s3')
s3.meta.client.upload_file(FILE_NAME3, BUCKET_NAME, OBJECT_NAME3)

s3 = boto3.client('s3')
with open(FILE_NAME4, 'rb') as f:
    s3.upload_fileobj(f, BUCKET_NAME, OBJECT_NAME4)

S3 — Boto3 Docs 1.17.103 documentation

[4-5] バケットのオブジェクトを削除

[4-5-1] 単一オブジェクトの削除

boto3.resourceの手順は以下の通りです。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME1 = 'dir3/file1.txt'

s3 = boto3.resource('s3')
s3.Object(BUCKET_NAME, OBJECT_NAME1).delete()

S3 — Boto3 Docs 1.17.105 documentation

boto3.clientの手順は以下の通りです。

BUCKET_NAME = 'bucket_name'
OBJECT_NAME2 = 'dir3/file2.txt'

s3 = boto3.client('s3')
s3.delete_object(Bucket=BUCKET_NAME, Key=OBJECT_NAME2)

S3 — Boto3 Docs 1.17.105 documentation

[4-5-2] 複数オブジェクトの削除

BUCKET_NAME = 'bucket_name'
OBJECT_NAME3 = 'dir3/file3.csv'
OBJECT_NAME4 = 'dir3/file4.txt'

s3  = boto3.resource('s3')
bucket = s3.Bucket(BUCKET_NAME)
bucket.delete_objects(
    Delete={
        'Objects': [
            {'Key': OBJECT_NAME3},
            {'Key': OBJECT_NAME4}
        ]
    },
)

S3 — Boto3 Docs 1.17.105 documentation

BUCKET_NAME = 'bucket_name'
OBJECT_NAME3 = 'dir3/file3.csv'
OBJECT_NAME4 = 'dir3/file4.txt'

s3 = boto3.client('s3')
s3.delete_objects(
    Bucket=BUCKET_NAME,
    Delete={
        'Objects': [
            {'Key': OBJECT_NAME3},
            {'Key': OBJECT_NAME4}
        ]
    },
)

S3 — Boto3 Docs 1.17.105 documentation

終わりに

同じことをやるにも色々なやり方があり混乱しました。ですが、どの手順でも大きな差はありませんでした。手順自体も簡単なため、自分が使いやすいと思うものを使えばいいようです。

補足

[補足1] IAMロールをマネージメントコンソールで作成する手順

IAMロールを作成します。

f:id:predora005:20210629223939p:plain

ユースケースの選択で[S3]を選びます。

f:id:predora005:20210629223944p:plain

[ポリシーの作成]を選びます。

f:id:predora005:20210629223949p:plain

本文に記載したJSONを貼り付けます。バケット名はご自身のバケットの名称に置き換えます。

f:id:predora005:20210629223953p:plain

ポリシー名は任意の名称で構いません。

f:id:predora005:20210629223959p:plain

ロールの作成画面に戻ったら、作成したポリシーを割り当てます。更新ボタンを押してポリシー名を検索ボックスに入力すると、選択肢に出てきます。

f:id:predora005:20210629224004p:plain

ロール名は任意の名称で構いません。

f:id:predora005:20210629224007p:plain

[補足2] IAMロールをCloudFormationで作成する場合

テンプレートファイルに以下の記述を追加します(Resourcesは元々書いてあるはずなので追加不要です)。長ったらしく感じますが「バケット名」以外は決まりきった書き方です。

Resources:
  # (...途中省略...)  
  S3AccessRole:
    Type: "AWS::IAM::Role"
    Properties:
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          -
            Effect: "Allow"
            Principal:
              Service:
                - "ec2.amazonaws.com"
            Action:
              - "sts:AssumeRole"
      Path: "/"
  S3AccessPolicies:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: s3access
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
        - Effect: Allow
          Action:
            - "s3:ListAllMyBuckets"
            - "s3:GetBucketLocation"
          Resource: "arn:aws:s3:::*"
        - Effect: Allow
          Action:
            - "s3:ListBucket"
          Resource:
            - "arn:aws:s3:::{バケット名}"
        - Effect: Allow
          Action:
            - "s3:GetObject"
            - "s3:PutObject"
            - "s3:DeleteObject"
          Resource:
            - "arn:aws:s3:::{バケット名}/*"
      Roles:
      - !Ref S3AccessRole
  S3AccessInstanceProfile:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Path: "/"
      Roles:
      - !Ref S3AccessRole

追加した「S3AccessInstanceProfile」をEC2インスタンスと紐づければ終わりです。

  # (...途中省略...)  
  Instance:
    Type: AWS::EC2::Instance
    Properties:
      # (...途中省略...)  
      IamInstanceProfile:
        !Ref S3AccessInstanceProfile

出典

アイキャッチOpenClipart-VectorsによるPixabayからの画像

【AWS】Lambdaをローカル環境でテストするのにSAMを使う

ローカル環境でLambdaをテストするにはAWS SAM」を使います。Lambdaのテスト方法は主には以下の2択です。

  1. 本物のLambda関数でテスト
  2. ローカル環境でSAMでテスト

本物のLambda関数をテストしたくない場合、ローカル環境でSAMの方を選択することになります。本記事ではローカル環境でSAMを使用し、Lambda関数をテストする方法を紹介します。

[1] AWS SAMのインストール

SAMのインストール方法とチュートリアルの実行例は下記記事で紹介しています。

https://predora005.hatenablog.com/entry/2021/06/01/190000predora005.hatenablog.com

[2] プロジェクトの作成

sam initでプロジェクトを作成します。ここでは、Python3.8のHello, Worldプロジェクトを例に説明していきます。詳細は別記事で紹介しています。

$ sam init
...(途中省略)...
    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.8
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .

sam initは対話形式ですが、以下のようにパラメータを指定することも可能です。

# パラメータを指定
$ sam init --name sam-app --runtime nodejs10.x --dependency-manager npm --app-template hello-world
$ sam init --name sam-app --package-type image --base-image nodejs10.x-bas

# GitHubに保存したテンプレートから作成
$ sam init --location gh:aws-samples/cookiecutter-aws-sam-python
$ sam init --location git+ssh://git@github.com/aws-samples/cookiecutter-aws-sam-python.git
$ sam init --location hg+ssh://hg@bitbucket.org/repo/template-name

# zip化したテンプレートから作成
$ sam init --location /path/to/template.zip
$ sam init --location https://example.com/path/to/template.zip

# ローカルに保存されたテンプレートから作成
$ sam init --location /path/to/template/folder

[3] ビルド

sam buildでビルドします。

$ cd sam-app/
$ sam build
Building codeuri: /home/ec2-user/sam-app/hello_world runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided

[4] テスト

[4-1] 関数を直接呼び出し

sam local invokeで関数をローカルで呼び出せます。

$ sam local invoke
Invoking app.lambda_handler (python3.8)
Image was not found.
Building image........................
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.23.0.

Mounting /home/ec2-user/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
END RequestId: e3e79ef0-1db0-4713-a3bb-434dd4022e4f
REPORT RequestId: e3e79ef0-1db0-4713-a3bb-434dd4022e4f  Init Duration: 0.34 ms    Duration: 307.81 mBilled Duration: 400 ms    Memory Size: 128 MB    Max Memory Used: 128 MB    
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

-eオプションで、イベントファイルを読み込ませることも可能です。

$ sam local invoke "HelloWorldFunction" -e events/event.json

[4-2] API Gatewayをローカルで起動

sam local start-apiで、API Gatewayのローカルインスタンスを起動できます。

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2021-05-01 14:38:53  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

表示されたURL(エンドポイント)にアクセスすると、応答が返ってきます。

$ curl http://127.0.0.1:3000/hello
{"message": "hello world"}

[4-3] Lambda関数のエンドポイントをローカルに作成

[4-3-1] 実行方法

sam local start-lambdaでLambda関数のエンドポイントを作成できます。単発でテストする分にはsam local invokeでも問題ありませんが、AWS SDKを用いてプログラムから起動する場合などで有効活用できます。

$ sam local start-lambda
Starting the Local Lambda Service. You can now invoke your Lambda Functions defined in your template through the endpoint.
2021-05-01 14:39:41  * Running on http://127.0.0.1:3001/ (Press CTRL+C to quit)

aws lambda invokeを使い、エンドポイント経由で起動できます。

$ aws lambda invoke --function-name "HelloWorldFunction" --endpoint-url "http://127.0.0.1:3001" --payload file://events/event.json response.json
{
    "StatusCode": 200
}

[4-3-2] コマンドのオプション

aws lambda invokeの主なオプションは次の通りです。

オプション 内容
function-name 関数名
endpoint-url エンドポイント
invocation-type RequestResponse,Event, DryRunのいずれか。
RequestResponseは同期的呼出、Eventは非同期呼出。
payload Lambda関数へ渡す入力
log-type TailLogResultを出力、Noneで出力無し。
ローカルではTailは使えないよう。
no-verify-ssl SSL 証明書認証をスキップ

詳細は公式のリファレンスに記載があります。

invoke — AWS CLI 1.19.62 Command Reference

sam local start-lambdaのオプションについても公式に一覧が載っています。

sam local start-lambda - AWS Serverless Application Model

[4-4] サンプルイベントを使う

sam local generate-eventを使うと、サンプルイベントを作成してくれます。helpオプションでイベントの種類が確認できます。

$ sam local generate-event --help
...(途中省略)...
Commands:
  alexa-skills-kit
  alexa-smart-home
  apigateway
  appsync
  batch
  cloudformation
  cloudfront
  cloudwatch
  codecommit
  codepipeline
  cognito
  config
  connect
  dynamodb
  kinesis
  lex
  rekognition
  s3
  sagemaker
  ses
  sns
  sqs
  stepfunctions

以下はAPI Gateway(apigateway)の例です。まずはヘルプで使い方を確認します。

$ sam local generate-event apigateway -h
Usage: sam local generate-event apigateway [OPTIONS] COMMAND [ARGS]...

Options:
  -h, --help  Show this message and exit.

Commands:
  authorizer  Generates an Amazon API Gateway Authorizer Event
  aws-proxy   Generates an Amazon API Gateway AWS Proxy Event

イベントを出力し、出力したイベントをLambda関数に渡すことができます。

$ sam local generate-event apigateway aws-proxy > events/apigateway-event.json
$ sam local invoke "HelloWorldFunction" -e events/apigateway-event.json 

[4-5] より詳細を知りたい場合は

公式(下記)を参照してください。

docs.aws.amazon.com

[5] ユニットテスト(pytest)

AWS SAMの機能ではありませんが、Hello, Worldテンプレートにユニットテスト用のソースが格納されています。pytestで実行すると以下のようになります。

$ pytest tests/unit/
======================================= test session starts =======================================
platform linux -- Python 3.8.5, pytest-6.2.3, py-1.10.0, pluggy-0.13.1
rootdir: /home/ec2-user/sam-app
plugins: mock-3.6.0
collected 1 item                                                                                  

tests/unit/test_handler.py .                                                                [100%]

======================================== 1 passed in 0.03s ========================================

pytestのインストールはpipで可能です。

$ pip3 install -U pytest pytest-mock --user

Installation and Getting Started — pytest documentation

終わりに

「SAM」は「Cloud Formation」と似た機能(公式は拡張機能と言っている)であり、学習コストがかかるのも「CloudFormation」と同じです。しかし、「CloudFormation」は現在フル活用している程に便利な機能なので、SAMも少しずつ使いこなせるようにしたいと思います。

参考文献

出典

補足

[補足1] ローカルエンドポイントに対してAWS SDKから呼出

AWS SDK for Python (Boto3) のインストールはpipコマンドで行います。

$ pip3 install boto3 --user

下記公式に掲載されているソースコードをそのまま実行すると、次の結果が得られます。

$ python3 lambda-endpoint.py 
Traceback (most recent call last):
  File "lambda-endpoint.py", line 30, in <module>
    assert response == "Hello World"
AssertionError

responseは"Hello World"ではないので、この動作は正常です。assertで例外を出さないためには、数行の追加とassertの行を変更が必要です。

import json

...(途中省略)...

payload = json.load(response['Payload'])
body = json.loads(payload['body'])
# (変更前) assert response == "Hello World"
assert body['message'] == "hello world"

Integrating with automated tests - AWS Serverless Application Model

[補足2] デプロイ時に生成されるsamconfig.toml

version = 0.1
[default]
[default.deploy]
[default.deploy.parameters]
stack_name = "sam-app"
s3_bucket = "aws-sam-cli-managed-default-samclisourcebucket-xgx2dia4ru2p"
s3_prefix = "sam-app"
region = "ap-northeast-1"
confirm_changeset = true
capabilities = "CAPABILITY_IAM"

[補足3] パッケージ

作成したアプリをパッケージ化してS3バケットにアップロードします。

$ sam package --template-file template.yaml --s3-bucket {バケット名} \
--s3-prefix hello_world --output-template-file template_packaged.yaml

--s3-prefixはzipを直接バケット直下に置いてもいい場合は指定不要です。上の例ではhello_worldフォルダ下にzipが格納されます。

--output-template-fileには変更後のテンプレート出力先をセットします。テンプレート内のCodeUriがzipが格納されたS3のURLに変更されています。

このパッケージを使って、デプロイが可能になります。

$ sam deploy --template-file /home/ec2-user/sam-app/package_template.yaml --stack-name <YOUR STACK NAME>

【AWS】ウェブアプリケーションの構築(Amplify+Cognito+Lambda)

AWS公式のハンズオン「サーバーレスのウェブアプリケーションを構築」に習って、簡単なウェブアプリの構築を行いました。

このハンズオンを通して、以下の要素に触れることができます。

  • CodeCommit
  • Amplify
  • Cognito
  • DynamoDB
  • IAM
  • Lambda
  • API Gateway

AWSのハンズオンやチュートリアルはモノによって分かりやすさに差があります。このハンズオンは分かりやすい方だったのでオススメです。

aws.amazon.com

[1] Gitリポジトリの作成(CodeCommit)

ウェブアプリケーションのソース(index.html等)は「CodeCommit」で管理します。

[1-1] リポジトリの作成

まずはリポジトリを作成します。

f:id:predora005:20210418204713p:plain

「wildrydes-site」というリポジトリを作成します。

f:id:predora005:20210418204717p:plain

リポジトリが作成できたら、URLをコピーしてクローンします。

f:id:predora005:20210418204727p:plain

[1-2] IAMでGit認証情報を生成

Git認証情報を作成していない場合は、クローンする前に作成しておきます。IAMで使用するユーザを選択し、下にスクロールすると[認証情報を生成]があります。

f:id:predora005:20210418205050p:plain

ボタンを押すと、ユーザー名とパスワードが表示されます。これらをCodeCommitでの認証に使用します。

f:id:predora005:20210418205055p:plain

[1-3] AWSが用意してくれたサイトをコピーする

git cloneで先程作成したリポジトリをローカル環境に複製します。{ユーザー名}と{パスワード}はIAMで取得した認証情報を入力します。

git clone https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/wildrydes-site
Cloning into 'wildrydes-site'...
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': {ユーザー名}
Password for 'https://{ユーザー名}@git-codecommit.ap-northeast-1.amazonaws.com': {パスワード}
warning: You appear to have cloned an empty repository.

AWS CLIからAWS側で用意されたサイトをコピーします。AWS CLIを初めて使用する場合はaws configureで初期設定を事前に行います。

$ cd wildrydes-site/
$ aws s3 cp s3://wildrydes-us-east-1/WebApplication/1_StaticWebHosting/website ./ --recursive

git addで変更を追加して、コミット・プッシュを行います。

$ git add .
$ git commit -m "Initial commit"
$ git push
Username for 'https://git-codecommit.ap-northeast-1.amazonaws.com': {ユーザー名}
Password for 'https://{ユーザー名}@git-codecommit.ap-northeast-1.amazonaws.com': {パスワード}
Enumerating objects: 95, done.
Counting objects: 100% (95/95), done.
Delta compression using up to 2 threads
Compressing objects: 100% (94/94), done.
Writing objects: 100% (95/95), 9.44 MiB | 16.08 MiB/s, done.
Total 95 (delta 2), reused 0 (delta 0)
To https://git-codecommit.ap-northeast-1.amazonaws.com/v1/repos/wildrydes-site
 * [new branch]      master -> master

[2] ウェブサイトをデプロイする(Amplify)

ウェブサイトの構築・デプロイは「Amplify」で行います。

[2-1] デプロイ

Amplifyの画面からウェブアプリケーションを作成していきます。

f:id:predora005:20210421062205p:plain

[AWS CodeCommit]を選択します。

f:id:predora005:20210421062209p:plain

先程作成したリポジトリを選択します。

f:id:predora005:20210421062214p:plain

アプリの名前は任意で問題ありません。ここではチュートリアル通りの名称としています。

f:id:predora005:20210422000656p:plain

作成してから検証が終わるまで数分かかります。

f:id:predora005:20210422000702p:plain

検証が終わったのちURLをクリックすると、ウェブサイトが表示されます。

f:id:predora005:20210422000707p:plain

[2-2] サイトの変更

index.htmlを変更・コミットして、Webサイトに反映されるか確認します。index.htmlを開いてtitleを変更します。

f:id:predora005:20210422181739p:plain

変更後、git add, git commit, git pushします。

$ git add index.html 
$ git commit -m "updated title"
$ git push

git pushするとAmplifyでデプロイと検証が自動で行われます

f:id:predora005:20210422184647p:plain

検証まで終わった後、Webサイトを表示するとtitle変更が反映されています。

f:id:predora005:20210422181745p:plain

[3] ユーザー管理(Cognito)

ユーザー認証は「Cognito」で行います。

[3-1] ユーザープールを作成

f:id:predora005:20210422185932p:plain

f:id:predora005:20210422185936p:plain

[デフォルトを確認する]の方を選択します。プール名は任意の名称で構いません。

f:id:predora005:20210422185940p:plain

デフォルト設定のままで作成を完了します。

f:id:predora005:20210422185944p:plain

以上でユーザープールの作成は完了です。プールIDはこの後使用します。

f:id:predora005:20210422210223p:plain

[3-2] ユーザープールにアプリクライアントを追加

f:id:predora005:20210422210514p:plain

アプリクライアント名は任意で問題ありません。[クライアントシークレットを生成]のチェックは外します。

f:id:predora005:20210422210520p:plain

今回作成するブラウザベースのアプリでは、クライアントシークレットがサポートされていないためです。ハンズオンに以下の記載がありました。

クライアントシークレットは、現在ブラウザベースのアプリケーションでの使用はサポートされていません。

出典:モジュール 2 – Amazon Cognito でのユーザー管理

表示されたアプリクライアントIDはこの後使用します。

f:id:predora005:20210422211455p:plain

[3-3] ウェブサイトの設定を更新

js/config.jsを開き、先述の手順で取得したプールID、アプリクライアントIDを入力します。

  • userPoolId : プールID
  • userPoolClientId: アプリクライアントID
  • region: リージョン

f:id:predora005:20210422224053p:plain

編集が終わったら変更をリポジトリに送信します。

$ git add js/config.js 
$ git commit -m "update cognito config"
$ git push

[3-4] テスト

Amplifyでデプロイ・検証が終わったら、再度ウェブサイトにアクセスします。[GIDDY UP!]を選択すると登録画面に移動します。

f:id:predora005:20210422224059p:plain

メールアドレスとパスワードを入力します。メールアドレスは、実在のアドレス・ダミーアドレスどちらの入力も可能です。

f:id:predora005:20210422224112p:plain

実在のアドレスの場合は送信されたメールに記載の認証コードを入力します。ダミーアドレスの場合はCognitoコンソールから確認できるそうです。私は実在のアドレスを使用しました。

f:id:predora005:20210422224118p:plain

認証に成功するとダイアログが表示されます。

f:id:predora005:20210422224124p:plain

認証画面で再度メールアドレス・パスワードを入力します。

f:id:predora005:20210422224127p:plain

認証には成功しますが、APIが無いというメッセージが表示されます。APIはこの後設定します。

f:id:predora005:20210422224134p:plain

[4] DynamoDBテーブルを作成

「RideId」をプライマリキーに持つテーブルを作成します。

f:id:predora005:20210422231118p:plain

[5] Lambda関数の作成

[5-1] IAMロールの作成

Lambda関数作成の前に、DynamoDBにアクセスする権限を持つロールを作成します。

f:id:predora005:20210422231738p:plain

検索ボックスに「AWSLambdaBasicExecutionRole」を入力し、チェックボックスをONにします。

f:id:predora005:20210422231743p:plain

ロール名は任意の名称で問題ありません。

f:id:predora005:20210422231747p:plain

[5-2] インラインポリシーの追加

ロールにインラインポリシーを追加し、先程作成したDynamoDBへのアクセス権限を追加します。

f:id:predora005:20210423221108p:plain

サービス「DynamoDB」、アクション「PutItem」、リソースは先程作成したDynamoDBテーブルを指定します。

f:id:predora005:20210423221113p:plain

名前は任意で問題ありません。

f:id:predora005:20210423221119p:plain

[5-3] Lambda関数の作成

ランタイムは「Node.js」を選択します。

f:id:predora005:20210423223040p:plain

実行ロールは「既存のロールを使用する」を選択し、先程作成した IAMロールを選択します。

f:id:predora005:20210423223045p:plain

関数の作成完了後 「requestUnicorn.js」をindex.jsにコピーします。

[5-4] テスト

f:id:predora005:20210423225903p:plain

テストイベントを作成し、入力欄に下記JSONのコードをコピーします。

{
    "path": "/ride",
    "httpMethod": "POST",
    "headers": {
        "Accept": "*/*",
        "Authorization": "eyJraWQiOiJLTzRVMWZs",
        "content-type": "application/json; charset=UTF-8"
    },
    "queryStringParameters": null,
    "pathParameters": null,
    "requestContext": {
        "authorizer": {
            "claims": {
                "cognito:username": "the_username"
            }
        }
    },
    "body": "{\"PickupLocation\":{\"Latitude\":47.6174755835663,\"Longitude\":-122.28837066650185}}"
}

テストを実行し成功すると、以下のような結果が得られます。

f:id:predora005:20210423225908p:plain

[6] REST APIの作成(API Gateway)

[6-1] REST APIの作成

Lambda関数を呼ぶためのエンドポイントとなるREST APIを作成します。

f:id:predora005:20210423230543p:plain

「新しいAPIを選択し、エンドポイントタイプは「エッジ最適化」を選択します。

f:id:predora005:20210423230548p:plain

[6-2] Cognito ユーザープールオーソライザーを作成

f:id:predora005:20210423233517p:plain

タイプを「Cognito」にし、Cognitoユーザープールは作成したものを選択します。トークンのソースは「Authorization」にします。

f:id:predora005:20210423233522p:plain

作成後、テストを行います。

f:id:predora005:20210423233528p:plain

ブラウザからAmplifyで作成したページに飛び、/ride.htmlにアクセスします。認証を行うとトークンが表示されるのでコピーします。

f:id:predora005:20210423233533p:plain

コピーしたトークンを認可トークンの入力欄にペーストします。

f:id:predora005:20210423233537p:plain

テストが成功すると、Response Code: 200が返ってきます。

f:id:predora005:20210423233542p:plain

[6-3] POSTメソッド追加

[リソースの作成]を選択し、まずはrideリソースを作成します。

f:id:predora005:20210423235910p:plain

名前は「ride」としAPI Gateway CORSを有効にする」にチェックを付けます。

f:id:predora005:20210423235914p:plain

[メソッドの作成]を選択し、rideリソースにPOSTメソッドに追加します。

f:id:predora005:20210424000625p:plain

POSTを選択しチェックマークをクリックします。

f:id:predora005:20210424000628p:plain

統合タイプは「Lambda関数」にし、「Lambdaプロキシ統合の使用」にチェックします。Lambda関数は先程作成した関数の名称を入力します。

f:id:predora005:20210424000633p:plain

Lambda関数に権限を追加してよいか確認ダイアログが表示されるので[OK]をクリックします。

f:id:predora005:20210424001645p:plain

[メソッドリクエスト]を選択します。

f:id:predora005:20210424001650p:plain

認可横の鉛筆マークを選択するとコンボボックスが表示されるので、Cognitoで追加したオーソライザー「WildRydes」を選択します。

f:id:predora005:20210424001703p:plain

f:id:predora005:20210424001708p:plain

[6-4] APIをデプロイ

アクションから[APIのデプロイ]を選択します。

f:id:predora005:20210424002642p:plain

「新しいステージ」を選択し、ステージ名を「prod」とします。

f:id:predora005:20210424002647p:plain

URLが表示されるので、この後ウェブサイトの設定ファイルに反映します。

f:id:predora005:20210424002651p:plain

[6-5] ウェブサイトの設定更新

js/config.jsを開き「invokeURL」にAPIのURLを設定します。

f:id:predora005:20210424003215p:plain

設定をCodeCommitのリポジトリに反映します。

$ git add js/config.js 
$ git commit -m "Add invokeUrl to config.js"
$ git push

Amplifyの画面でデプロイ・検証が終わったことを確認し、ウェブサイトにアクセスすると地図が表示されます。

f:id:predora005:20210424003733p:plain

地図上の任意の位置をクリックすると、どこからともなく馬が現れクリックした位置で停止します。

f:id:predora005:20210424003738p:plain

[7] リソースの削除

  • Amplify
  • Cognitoユーザープール
  • Lambda関数
  • IAMロール (WildRydesLambda)
  • IAMロール(AWSAmplifyExecutionRole〜)
  • IAMロール (AWSAmplifyExecutionPolicy〜)
  • DynamoDBテーブル
  • API Gateway
  • CloudWatchロググループ
  • CodeCommitリポジトリ

終わりに

分量は多いですが、ハンズオンの説明が分かりやすいこともあり、途中で詰まることなく進められました。AWSの色々なサービスに触れることができ、良いハンズオンでした。

出典

【AWS】SAMの使い方とチュートリアルの解説

f:id:predora005:20210418113643p:plain AWS SAMの使い方について、チュートリアルの実行手順を解説しつつ紹介します。紹介する手順は、Amazon Linux2上で試したものです。

SAMServerless Application Modelの略です。サーバレスと言えば「Lambda」が思い浮かびますが、Lambda含め様々なリソースとの連携も出来るのがSAMです。Cloud Formationをサーバレスに拡張したサービスと言ったところです。

[1] SAMのインストール

下記手順を参考にインストールします。

Installing the AWS SAM CLI - AWS Serverless Application Model

[1-1] Dockerのインストール

まずは、Dockerをインストールします。以下は、Amazon Linux2でのインストール手順です。

$ sudo yum update -y
$ sudo amazon-linux-extras install docker

インストール後、Dockerを起動します。また、ec2-userでもDockerコマンドが起動できるようにするために、ec2-userをdockerグループに追加します。

$ sudo service docker start
$ sudo usermod -a -G docker ec2-user

一度ログアウトし再ログインしたのち、ec2-userでdockerコマンドが使用できることを確認します。

$ docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS               NAMES

[1-2] AWS SAM CLI のインストール

インストーラをダウンロードします。

curl -OL https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip

ハッシュ値を生成し、表示されたハッシュ値リリースノートの値と比較します。一致していれば問題ありません。4/15当時はv1.22.0で56d5d3701e6c4e7565dccff3300af072d476c0e7e36299f47565229efec0e139でした。

$ sha256sum aws-sam-cli-linux-x86_64.zip
[ハッシュ値] aws-sam-cli-linux-x86_64.zip

zipを解凍しインストールします。

$ unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
$ sudo ./sam-installation/install

無事インストールが完了していれば、以下のようにバージョンが表示されます。

$ sam --version
SAM CLI, version 1.22.0

[2] 認証情報の設定

aws configureで認証情報を設定します。

$ aws configure
AWS Access Key ID [None]: {アクセスキー}
AWS Secret Access Key [None]: {シークレットアクセスキー}
Default region name [None]: ap-northeast-1
Default output format [None]: json

[3] Hello Worldチュートリアル

以下のチュートリアルで使用方法を確認します。

Tutorial: Deploying a Hello World application - AWS Serverless Application Model

[3-1] プロジェクトの作成

対話式のコマンドになっています。次のように選択しました。

  • テンプレートソース: - AWS Quick Start Templates
  • パッケージタイプ: 1 - Zip (artifact is a zip uploaded to S3)
  • ランタイム: 2 - python3.8
  • Project name [sam-app]: (空欄)
  • テンプレート: 1 - Hello World Example
$ sam init

    SAM CLI now collects telemetry to better understand customer needs.

    You can OPT OUT and disable telemetry collection by setting the
    environment variable SAM_CLI_TELEMETRY=0 in your shell.
    Thanks for your help!

    Learn More: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-telemetry.html

Which template source would you like to use?
    1 - AWS Quick Start Templates
    2 - Custom Template Location
Choice: 1
What package type would you like to use?
    1 - Zip (artifact is a zip uploaded to S3) 
    2 - Image (artifact is an image uploaded to an ECR image repository)
Package type: 1

Which runtime would you like to use?
    1 - nodejs14.x
    2 - python3.8
    3 - ruby2.7
    4 - go1.x
    5 - java11
    6 - dotnetcore3.1
    7 - nodejs12.x
    8 - nodejs10.x
    9 - python3.7
    10 - python3.6
    11 - python2.7
    12 - ruby2.5
    13 - java8.al2
    14 - java8
    15 - dotnetcore2.1
Runtime: 2

Project name [sam-app]: 

Cloning app templates from https://github.com/aws/aws-sam-cli-app-templates

AWS quick start application templates:
    1 - Hello World Example
    2 - EventBridge Hello World
    3 - EventBridge App from scratch (100+ Event Schemas)
    4 - Step Functions Sample App (Stock Trader)
    5 - Elastic File System Sample App
Template selection: 1

    -----------------------
    Generating application:
    -----------------------
    Name: sam-app
    Runtime: python3.8
    Dependency Manager: pip
    Application Template: hello-world
    Output Directory: .
    
    Next steps can be found in the README file at ./sam-app/README.md
       
$

[3-2] ファイル構成

treeで確認すると以下のディレクトリ・ファイルが作成されています。

$ tree sam-app/
sam-app/
├── README.md
├── __init__.py
├── events
│   └── event.json
├── hello_world
│   ├── __init__.py
│   ├── app.py
│   └── requirements.txt
├── template.yaml
└── tests
    ├── __init__.py
    ├── integration
    │   ├── __init__.py
    │   └── test_api_gateway.py
    ├── requirements.txt
    └── unit
        ├── __init__.py
        └── test_handler.py

5 directories, 13 files

sam-app直下に3つのディレクトリが作成されています。

  • 「hello_world」Lambda関数のコード。
  • 「events」呼び出しイベントの定義。
  • 「tests」ユニットテストのコード。

重要なファイルは、チュートリアルに記載の通りですが下記ファイルです。

  • template.yamlAWSリソースを定義するテンプレートファイル。
  • 「hello_world/app.py」Lambda関数のエントリーポイント。
  • 「hello_world/requirements.txt」Pythonの依存関係。ビルド時に参照される。

[3-3] ビルド

sam buildでビルドします。

$ cd sam-app/
$ sam build
Building codeuri: /home/ec2-user/sam-app/hello_world runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']

Build Failed
Error: PythonPipBuilder:Validation - Binary validation failed for python, searched for python in following locations  : ['/usr/bin/python'] which did not satisfy constraints for runtime: python3.8. Do you have python for runtime: python3.8 on your PATH?

python 3.8が無いと怒られたのでインストールします。

$ sudo amazon-linux-extras install python3.8

再度ビルドすると成功しました。

$ sam build
Building codeuri: /home/ec2-user/sam-app/hello_world runtime: python3.8 metadata: {} functions: ['HelloWorldFunction']
Running PythonPipBuilder:ResolveDependencies
Running PythonPipBuilder:CopySource

Build Succeeded

Built Artifacts  : .aws-sam/build
Built Template   : .aws-sam/build/template.yaml

Commands you can use next
=========================
[*] Invoke Function: sam local invoke
[*] Deploy: sam deploy --guided
    

[3-4] デプロイ

[3-4-1] デプロイ内容の設定

sam deployでデプロイします。--guidedを付与すると対話形式で設定できます。

$ sam deploy --guided

Configuring SAM deploy
======================

    Looking for config file [samconfig.toml] :  Not found

    Setting default arguments for 'sam deploy'
    =========================================
    Stack Name [sam-app]: 
    AWS Region [us-east-1]: ap-northeast-1
    #Shows you resources changes to be deployed and require a 'Y' to initiate deploy
    Confirm changes before deploy [y/N]: y
    #SAM needs permission to be able to create roles to connect to the resources in your template
    Allow SAM CLI IAM role creation [Y/n]: y
    HelloWorldFunction may not have authorization defined, Is this okay? [y/N]: y
    Save arguments to configuration file [Y/n]: y
    SAM configuration file [samconfig.toml]: 
    SAM configuration environment [default]: 

    Looking for resources needed for deployment: Not found.
    Creating the required resources...

"Shows ..."以降の対話内容を日本語訳すると次の通りです。

#デプロイされるリソースの変更が表示され、デプロイを開始するには「Y」が必要です。
デプロイ前に変更を確認する [y/N]: 
#SAMは、テンプレートのリソースに接続するためのロールを作成する権限が必要です。
SAM CLIのIAMロール作成を許可する [Y/n]: 
HelloWorldFunctionに権限が定義されていない可能性がありますが、これは大丈夫ですか?[Y/N]: 
引数を設定ファイルに保存する [Y/n]: 
SAM設定ファイル [samconfig.toml]: 
SAMの設定環境 [default]:

少し待つと、以下のメッセージが表示されます。

   Successfully created!

        Managed S3 bucket: {アップロード先のS3バケット}
        A different default S3 bucket can be set in samconfig.toml

    Saved arguments to config file
    Running 'sam deploy' for future deployments will use the parameters saved above.
    The above parameters can be changed by modifying samconfig.toml
    Learn more about samconfig.toml syntax at 
    https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-cli-config.html

Uploading to sam-app/{zipファイル名}  600353 / 600353  (100.00%)

    Deploying with following values
    ===============================
    Stack name                   : sam-app
    Region                       : ap-northeast-1
    Confirm changeset            : True
    Deployment s3 bucket         : {アップロード先のS3バケット}
    Capabilities                 : ["CAPABILITY_IAM"]
    Parameter overrides          : {}
    Signing Profiles             : {}

Initiating deployment
=====================
Uploading to sam-app/{SAMのテンプレート名}.template  1090 / 1090  (100.00%)

このとき、CloudFormatinで新しいスタックが作られ、S3バケットが生成されます。S3バケットの中には、SAMのテンプレートファイルとzipファイルが作られています。zipの中身はhello_world以下の__init__.py, app.py,requirements.txt`です。

マネージメントコンソールで見るとaws-sam-cli-managed-default」というスタックが作成されています。

f:id:predora005:20210417193433p:plain

S3バケットの中身を確認すると、メッセージに表示されていたテンプレートファイルとzipファイル(拡張子はzipになっていない)が作成されています。

f:id:predora005:20210417193531p:plain

[3-4-2] デプロイ実行

内容に問題がなければ、デプロイ前にデプロイ内容が表示されます。

また、デプロイはCloud Formationを使用して行われます。"CloudFormation stack changeset"の項で、作成されるリソースを確認できます。

Waiting for changeset to be created..

CloudFormation stack changeset
-----------------------------------------------------------------------------------------------------------------------------
Operation                       LogicalResourceId               ResourceType                    Replacement                   
-----------------------------------------------------------------------------------------------------------------------------
+ Add                           HelloWorldFunctionHelloWorldP   AWS::Lambda::Permission         N/A                           
                                ermissionProd                                                                                 
+ Add                           HelloWorldFunctionRole          AWS::IAM::Role                  N/A                           
+ Add                           HelloWorldFunction              AWS::Lambda::Function           N/A                           
+ Add                           ServerlessRestApiDeployment47   AWS::ApiGateway::Deployment     N/A                           
                                fc2d5f9d                                                                                      
+ Add                           ServerlessRestApiProdStage      AWS::ApiGateway::Stage          N/A                           
+ Add                           ServerlessRestApi               AWS::ApiGateway::RestApi        N/A                           
-----------------------------------------------------------------------------------------------------------------------------

Changeset created successfully. arn:aws:cloudformation:ap-northeast-1:{アカウントID}:changeSet/{Change Setのリソース名}


Previewing CloudFormation changeset before deployment
======================================================

「Changeset」はCloud Formationでスタックを作成・変更する事前に、リソースがどう変わるのかを確認できる機能です。マネージメントコンソールからも確認できます。

f:id:predora005:20210417194518p:plain

f:id:predora005:20210417194522p:plain

yを入力するとデプロイが実行され、"sam-app"という名称のCloud Formationスタックが作成されます。

Deploy this changeset? [y/N]: y

2021-04-15 13:29:48 - Waiting for stack create/update to complete

CloudFormation events from changeset
-----------------------------------------------------------------------------------------------------------------------------
ResourceStatus                  ResourceType                    LogicalResourceId               ResourceStatusReason          
-----------------------------------------------------------------------------------------------------------------------------
CREATE_IN_PROGRESS              AWS::IAM::Role                  HelloWorldFunctionRole          Resource creation Initiated   
CREATE_IN_PROGRESS              AWS::IAM::Role                  HelloWorldFunctionRole          -                             
CREATE_COMPLETE                 AWS::IAM::Role                  HelloWorldFunctionRole          -                             
CREATE_IN_PROGRESS              AWS::Lambda::Function           HelloWorldFunction              -                             
CREATE_IN_PROGRESS              AWS::Lambda::Function           HelloWorldFunction              Resource creation Initiated   
CREATE_COMPLETE                 AWS::Lambda::Function           HelloWorldFunction              -                             
CREATE_IN_PROGRESS              AWS::ApiGateway::RestApi        ServerlessRestApi               -                             
CREATE_IN_PROGRESS              AWS::ApiGateway::RestApi        ServerlessRestApi               Resource creation Initiated   
CREATE_COMPLETE                 AWS::ApiGateway::RestApi        ServerlessRestApi               -                             
CREATE_IN_PROGRESS              AWS::Lambda::Permission         HelloWorldFunctionHelloWorldP   -                             
                                                                ermissionProd                                                 
CREATE_IN_PROGRESS              AWS::ApiGateway::Deployment     ServerlessRestApiDeployment47   -                             
                                                                fc2d5f9d                                                      
CREATE_IN_PROGRESS              AWS::ApiGateway::Deployment     ServerlessRestApiDeployment47   Resource creation Initiated   
                                                                fc2d5f9d                                                      
CREATE_IN_PROGRESS              AWS::Lambda::Permission         HelloWorldFunctionHelloWorldP   Resource creation Initiated   
                                                                ermissionProd                                                 
CREATE_COMPLETE                 AWS::ApiGateway::Deployment     ServerlessRestApiDeployment47   -                             
                                                                fc2d5f9d                                                      
CREATE_IN_PROGRESS              AWS::ApiGateway::Stage          ServerlessRestApiProdStage      -                             
CREATE_IN_PROGRESS              AWS::ApiGateway::Stage          ServerlessRestApiProdStage      Resource creation Initiated   
CREATE_COMPLETE                 AWS::ApiGateway::Stage          ServerlessRestApiProdStage      -                             
CREATE_COMPLETE                 AWS::Lambda::Permission         HelloWorldFunctionHelloWorldP   -                             
                                                                ermissionProd                                                 
CREATE_COMPLETE                 AWS::CloudFormation::Stack      sam-app                         -                             
-----------------------------------------------------------------------------------------------------------------------------

CloudFormation outputs from deployed stack
--------------------------------------------------------------------------------------------------------------------------------
Outputs                                                                                                                        
--------------------------------------------------------------------------------------------------------------------------------
Key                 HelloWorldFunctionIamRole                                                                                  
Description         Implicit IAM Role created for Hello World function                                                         
Value               arn:aws:iam::{IAMロールのリソース名}               

Key                 HelloWorldApi                                                                                              
Description         API Gateway endpoint URL for Prod stage for Hello World function                                           
Value               https://{自動で生成されるリクエストID}.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/                                    

Key                 HelloWorldFunction                                                                                         
Description         Hello World Lambda Function ARN                                                                            
Value               arn:aws:lambda:{Lambda関数のリソース名}              
--------------------------------------------------------------------------------------------------------------------------------

Successfully created/updated stack - sam-app in ap-northeast-1

[4] 動作確認

作成されたAPIcurlでリクエストを送信すると{"message": "hello world"}が返ってきます。

$ curl https://{自動で生成されるリクエストID}.execute-api.ap-northeast-1.amazonaws.com/Prod/hello/
{"message": "hello world"}

[5] ローカルでの動作確認

[5-1] APIをローカルでホスト

sam local start-apiAPIをローカルでホストします。

$ sam local start-api
Mounting HelloWorldFunction at http://127.0.0.1:3000/hello [GET]
You can now browse to the above endpoints to invoke your functions. You do not need to restart/reload SAM CLI while working on your functions, changes will be reflected instantly/automatically. You only need to restart SAM CLI if you update your AWS SAM template
2021-04-15 13:57:07  * Running on http://127.0.0.1:3000/ (Press CTRL+C to quit)

別のコマンドウィンドウを開いてcurlでローカルホストにアクセスします。すると、グローバルのAPIにアクセスした時と同様に{"message": "hello world"}が返ってきます。

$ curl http://127.0.0.1:3000/hello
{"message": "hello world"}

ホスト側にもリクエストの結果が出力されています。

Invoking app.lambda_handler (python3.8)
Image was not found.
Building image................................................................................................................................
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.22.0.

Mounting /home/ec2-user/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
END RequestId:  {リクエストID}
REPORT RequestId: {リクエストID}   Init Duration: 0.33 ms    Duration: 171.38 ms   Billed Duration: 200 ms    Memory Size: 128 MB    Max Memory Used: 128 MB    
No Content-Type given. Defaulting to 'application/json'.
2021-04-15 13:58:32 127.0.0.1 - - [15/Apr/2021 13:58:32] "GET /hello HTTP/1.1" 200 -

[5-2] Lambda関数を直接呼び出す

ローカルでホストした場合と同様の結果が得られます。

$ sam local invoke "HelloWorldFunction" -e events/event.json
Invoking app.lambda_handler (python3.8)
Skip pulling image and use local one: amazon/aws-sam-cli-emulation-image-python3.8:rapid-1.22.0.

Mounting /home/ec2-user/sam-app/.aws-sam/build/HelloWorldFunction as /var/task:ro,delegated inside runtime container
START RequestId: {リクエストID} Version: $LATEST
END RequestId: {リクエストID}
REPORT RequestId: {リクエストID}   Init Duration: 2.10 ms    Duration: 67.14 ms    Billed Duration: 100 ms    Memory Size: 128 MB    Max Memory Used: 128 MB    
{"statusCode": 200, "body": "{\"message\": \"hello world\"}"}

[6] 削除

スタックを削除します。{スタック名}はデフォルトのままであれば"sam-app"です。

$ aws cloudformation delete-stack --stack-name {スタック名} --region {リージョン名}

aws-sam-cli-managed-default」スタックを削除する際は、まずS3バケットの中身を空にします。空にしないとバケットが削除できず、スタック削除の途中でエラーになるからです。

このバケットはバージョンニングされているので、AWS CLIを使用する場合はバージョンの削除が必要です。単なるオブジェクトの削除だけだと、パッと見は削除されていてもオブジェクトは残っています。

jqコマンドを使用するシェルスクリプトを作成・実行して削除します。コマンドは下記記事を参考にさせていただきました。{バケット名}はご自分のものに置き換えてください。

バージョニングが有効なS3バケットをAWS CLIで空にする手順(オブジェクト1000個以下) - のぴぴのメモ

#!/bin/bash

BUCKET_NAME= {バケット名}

# jqインストール
sudo yum -y install jq

# バケット内のバージョン情報を表示
# aws s3api list-object-versions --bucket ${BUCKET_NAME}

# バケット内の全オブジェクト削除
aws s3 rm s3://${BUCKET_NAME}/ --recursive

# DeleteMarkersの削除
aws s3api list-object-versions --bucket ${BUCKET_NAME} \
        | jq -r -c '.["DeleteMarkers"][] | [.Key,.VersionId]' \
        | while read line
do
        key=`echo $line | jq -r .[0]`
        versionid=`echo $line | jq -r .[1]`
        aws s3api delete-object --bucket ${BUCKET_NAME} \
               --key ${key} --version-id ${versionid}
done

# Versionsの削除
aws s3api list-object-versions --bucket ${BUCKET_NAME} \
        | jq -r -c '.["Versions"][] | [.Key,.VersionId]' \
        | while read line
do
        key=`echo $line | jq -r .[0]`
        versionid=`echo $line | jq -r .[1]`
        aws s3api delete-object --bucket ${BUCKET_NAME} \
               --key ${key} --version-id ${versionid}
done

# バケットの削除
aws s3 rb s3://${BUCKET_NAME} --force

バケットを完全に削除したら、スタックを削除します。

$ aws cloudformation delete-stack --stack-name aws-sam-cli-managed-default --region {リージョン名}

終わりに

SAMは初見だとなかなかに難しく、チュートリアルを行うだけでも不明点で立ち止まることが何度もありました。この記事が少しでも、SAMを使おうと思っている方の手助けになれば幸いです。

出典

補足

[補足1] template.yamlの中身

sam initで作成されるtemplate.yamlの中身について。コメント部を割愛すると以下の内容です。

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31

# 説明
Description: >

# Lambda関数の設定。以下はタイムアウト時間=3秒を設定。
Globals:
  Function:
    Timeout: 3

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function

    # Lambda関数のソース格納場所とランタイム
    Properties:
      CodeUri: hello_world/
      Handler: app.lambda_handler
      Runtime: python3.8

      # Lambda関数を起動するイベントの設定。以下はAPIイベント。
      # (注) API, HTTP APIイベントを設定すると、デプロイ時にAPIが自動で作られる。詳細は下記URL参照。
      # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#api
      Events:
        HelloWorld:
          Type: Api 
          Properties:
            Path: /hello
            Method: get

# スタックのプロパティに表示する内容
Outputs:
  # API
  HelloWorldApi:
    Description: "API Gateway endpoint URL for Prod stage for Hello World function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/hello/"
  # Lambda関数
  HelloWorldFunction:
    Description: "Hello World Lambda Function ARN"
    Value: !GetAtt HelloWorldFunction.Arn
  # IAMロール
  HelloWorldFunctionIamRole:
    Description: "Implicit IAM Role created for Hello World function"
    Value: !GetAtt HelloWorldFunctionRole.Arn

[補足2] ビルド時に生成されるもの

プロジェクト直下に.aws-samディレクトリが作成されます。requirements.txtで指定したライブラリも含め、デプロイ時に必要なものがbuildディレクトリ化に出力されています。

$ tree -da
.
├── .aws-sam
│   └── build
│       └── HelloWorldFunction
│           ├── certifi
│           ├── certifi-2020.12.5.dist-info
│           ├── chardet
│           │   ├── cli
│           │   └── metadata
│           ├── chardet-4.0.0.dist-info
│           ├── idna
│           ├── idna-2.10.dist-info
│           ├── requests
│           ├── requests-2.25.1.dist-info
│           ├── urllib3
│           │   ├── contrib
│           │   │   └── _securetransport
│           │   ├── packages
│           │   │   ├── backports
│           │   │   └── ssl_match_hostname
│           │   └── util
│           └── urllib3-1.26.4.dist-info
├── events
├── hello_world
└── tests
    ├── integration
    └── unit

26 directories

【AWS】LambdaをS3トリガーで起動しサムネイル画像を作成するチュートリアルをやってみた

以前、AWS Lambda関数をS3トリガーで起動する方法を確認しました。

https://predora005.hatenablog.com/entry/2021/05/23/190000predora005.hatenablog.com

今回もAWS公式のチュートリアルに沿って、S3トリガーでのLambda関数の使い方を紹介します。チュートリアルではAWS CLIを用いていますが、ここではマネージメントコンソールを用いる方法を紹介します。

docs.aws.amazon.com

[1] S3バケットを作成

バケットを2つ作成します。1つ目は任意の名前のバケット、2つ目は「{1つ目のバケット名}-resized」にします。

f:id:predora005:20210418130847p:plain

f:id:predora005:20210418130851p:plain

1つ目のバケットに「HappyFace.jpg」をアップロードします。

f:id:predora005:20210418130857p:plain

[2] IAMポリシーとロールの作成

[2-1] IAMポリシーの作成

以下の権限を持つIAMポリシーを作成します。

  • 1つ目のS3バケットからオブジェクト取得
  • 2つ目(resized)のS3バケットにオブジェクト格納
  • CloudWatchへのログ書込

f:id:predora005:20210418131357p:plain

JSON欄を以下の内容にします。{バケット名}は自身が作成したバケットの名称に置き換えます。

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogGroup",
                "logs:CreateLogStream"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::{バケット名}/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject"
            ],
            "Resource": "arn:aws:s3:::{バケット名}-resized/*"
        }
    ]
}         

名前はチュートリアル通り「AWSLambdaS3Policy」としていますが、変更しても問題ありません。

f:id:predora005:20210418131730p:plain

[2-2] IAMロールの作成

続いて、IAMロールを作成します。

f:id:predora005:20210418132326p:plain

先程作成したポリシーをアタッチします。テキストボックスにポリシーの名称を入力すると表示されます。チェックを付けて次に進めます。

f:id:predora005:20210418132330p:plain

ロール名はチュートリアル通り「lambda-s3-role」としていますが、変更しても問題ありません。

f:id:predora005:20210418132334p:plain

[3] Lambda関数の作成

[3-1] 関数の作成

ランタイムは「Python3.8」としていますが、3.7でも構いません。後述のPillowインストール問題があるため、各々の都合や環境に合わせて変更しましょう。

f:id:predora005:20210418134257p:plain

アクセス権限欄から先程作成したIAMロールを選択します。

f:id:predora005:20210418134302p:plain

ソースはチュートリアルに記載のコードを貼り付けます。

f:id:predora005:20210418134306p:plain

チュートリアル: Amazon S3 トリガーを使用してサムネイル画像を作成する - AWS Lambda

[3-2] レイヤーの追加

Pillowを使用するためにレイヤーを追加します。PillowはLambda関数の環境にはインストールされていないため、自前でインストールが必要です。詳細は下記記事で紹介しています。

https://predora005.hatenablog.com/entry/2021/05/26/190000predora005.hatenablog.com

[4] Lambda関数のテスト

テストイベントを作成します。内容はチュートリアル通りです。{バケット名}を作成したS3バケット名、{リージョン名}をバケットを作成したリージョンに置き換えます。

{
  "Records":[
    {
      "eventVersion":"2.0",
      "eventSource":"aws:s3",
      "awsRegion":"{リージョン名}",
      "eventTime":"1970-01-01T00:00:00.000Z",
      "eventName":"ObjectCreated:Put",
      "userIdentity":{
        "principalId":"AIDAJDPLRKLG7UEXAMPLE"
      },
      "requestParameters":{
        "sourceIPAddress":"127.0.0.1"
      },
      "responseElements":{
        "x-amz-request-id":"C3D13FE58DE4C810",
        "x-amz-id-2":"FMyUVURIY8/IgAtTv8xRjskZQpcIZ9KG4V5Wp6S7S/JRWeUWerMUE5JgHvANOjpD"
      },
      "s3":{
        "s3SchemaVersion":"1.0",
        "configurationId":"testConfigRule",
        "bucket":{
          "name":"{バケット名}",
          "ownerIdentity":{
            "principalId":"A3NL1KOZZKExample"
          },
          "arn":"arn:aws:s3:::{バケット名}"
        },
        "object":{
          "key":"HappyFace.jpg",
          "size":1024,
          "eTag":"d41d8cd98f00b204e9800998ecf8427e",
          "versionId":"096fKKXTRTtl3on89fVO.nfljtsv6qko"
        }
      }
    }
  ]
}

変更を保存して、テストを実行します。

f:id:predora005:20210418145639p:plain

実行後「〜-rezied」のバケットを確認すると、1つ目のバケットと同名のファイルが作成されています。

f:id:predora005:20210418145935p:plain

一見ファイルがコピーされただけのように見えますが、ファイルの詳細を確認すると縦横が1/2のサイズになっています。

[5] S3トリガーでLambda関数起動

[5-1] S3にLambda関数のアクセス権限追加

S3からLambda関数にアクセスする権限を追加します。Lambda関数の[設定][アクセス権限]から追加します。

f:id:predora005:20210418151931p:plain

設定は以下の通りです。ソースアカウントにはアカウントID、ソースARNはS3のリソース名(arn:aws:s3:::{バケット名}を入力します。

f:id:predora005:20210418151936p:plain

アクションは「Lambda:InvokeFunction」を選択します。ステートメントIDは任意の名称で問題ありません。

f:id:predora005:20210418151942p:plain

[5-2] S3バケットからLambdaへの通知設定

S3バケットにファイルが登録された際、Lambda関数に通知する設定を追加します。S3の[プロパティ]から追加します。

f:id:predora005:20210418153001p:plain

イベント名は任意の名称で問題ありません。

f:id:predora005:20210418153006p:plain

イベントタイプは「すべてのオブジェクト作成イベント」を選択します。

f:id:predora005:20210418153010p:plain

送信先は先程作成したLambda関数を設定します。

f:id:predora005:20210418153015p:plain

[5-3] S3バケットに画像ファイルをアップロード

準備が整ったので、S3バケットに画像ファイルをアップロードします。

f:id:predora005:20210418154027p:plain

「〜-rezied」のバケットを確認すると、リサイズされた画像ファイルが作成されています。

f:id:predora005:20210418154033p:plain

[6] リソースの削除

以下のリソースを削除します。

  • Lambda関数
  • Lambdaレイヤー
  • CloudWatchのロググループ
  • IAMロールとIAMポリシー
  • S3バケット

終わりに

Pillowのインストールに躓きましたが、それ以外はチュートリアルに沿って順調に進めることができました。

リソースの数が多いため、CloudFormationやSAMで作成するのがよいかもしれません。

機械学習では大量の画像を扱うことがありますが、画像をリサイズして統一することが多いです。S3に画像をアップロードしたとき、機械学習用にリサイズするようにしたら便利だなと思いました。

出典

【AWS】LambdaでPillowを使う方法(Lambda Layer)

AWS LambdaでPillowを使おうと思ったら次のエラーが表示されました。

[ERROR] Runtime.ImportModuleError: Unable to import module 'lambda_function': No module named 'PIL'
Traceback (most recent call last):

Lambdaのデフォルト環境にはPillowが入っていないので、インストールする必要がありました。今回は「Lambda Layer」を使った方法を紹介します。

方法を調べるにあたり下記記事を大変参考にさせていただき、とても助かりました。ありがとうございます。

michimani.net

qiita.com

[1] 必要な環境

Amazon Linuxを用意する必要があります。Amazon Linux上でPillowをローカルにインストールしzip圧縮してLayerに追加します。

用意する環境は、Lambda関数のPythonバージョンによって異なります。Python3.8を使いたい場合はAmazon Linux2Python3.7以下ならAmazon Linuxになります。

Building Lambda functions with Python - AWS Lambda

Amazon LinuxでDockerイメージが用意されています。Dockerを使えば、Amazon LinuxのEC2をわざわざ用意せずに済みます。ここでは、Dockerを用いた方法を紹介していきます。

ただし、Amazon Linux2を使用する場合、 Dockerは使えません。イメージが用意されていないので、Amazon Linux2のEC2を構築して、Dokcerコンテナで行ったのと同様のことを行います。

[2] Dockerインストール

$ sudo yum -y install docker

Dockerを起動し、自動起動の有効化も行います。

$ sudo systemctl start docker
$ sudo systemctl enable docker

Amazon ECS における Docker の基本 - Amazon Elastic Container Service

[2] docker-composeインストール

docker-composeもインストールします。

$ sudo curl -L "https://github.com/docker/compose/releases/download/1.29.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
$ sudo chmod +x /usr/local/bin/docker-compose

docker-compose --versionで実行できることを確認します。

$ docker-compose --version
docker-compose version 1.29.0, build 07737305

Install Docker Compose | Docker Documentation

[3] Amazon Linuxコンテナの起動確認

Amazon Linuxのコンテナを取得します。

$ docker pull amazonlinux

docker imagesAmazon Linuxのコンテナが取得できたことを確認します。

$ docker images
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
amazonlinux         latest              e2c0b9bec08e        12 days ago         163MB

Amazon Linuxコンテナを試しに起動してみます。

$ docker run -it amazonlinux
bash-4.2# exit

起動が確認できたら削除します。

$ docker ps -a
CONTAINER ID        IMAGE               COMMAND             CREATED              STATUS                          PORTS               NAMES
8b36a23fb991        amazonlinux         "/bin/bash"         About a minute ago   Exited (0) About a minute ago
$ docker rm 8b36a23fb991
8b36a23fb991

[4] パッケージ作成

いよいよPillowを含むパッケージの作成に取り掛かります。

[4-1] Dockerfileの用意

「Dockerfile」と「docker-compose.yml」を用意します。

  • Dockerfile
FROM amazonlinux:latest

RUN yum install -y python3 zip
RUN mkdir /home/deploy
  • docker-compose.yml
version: '2'
services:
  app:
    build: .
    volumes:
      - './deploy:/home/deploy'
    command: >
      bash -c "pip3 install -r /home/deploy/requirements.txt -t /home/deploy/python &&
      cd /home/deploy &&
      /usr/bin/zip -r PIL.zip python"

Lambda Layersに追加する場合、pythonディレクトリの下にパッケージをインストールする必要があります。詳細は以下の記事を参考にさせていただきました。

qiita.com

[4-2] ディレクトリの準備

$ mkdir deploy
$ mkdir deploy/python

「requirements.txt」を準備しdeployディレクトリ下に置きます。「requirements.txt」にはインストールするパッケージ名を記載します。今回はPillowのみです。

  • requirements.txt
Pillow

treeコマンドで確認すると次のようなディレクトリ構成です。

$ tree
├── Dockerfile
├── deploy
│   ├── python
│   └── requirements.txt
└── docker-compose.yml

[4-3] パッケージ作成

準備ができたら下記コマンドを実行します。

docker-compose up --build

lsで確認すると無事にzipファイルが作成されていました。

$ ls deploy/
PIL.zip  python  requirements.txt

[5] Lambda Layerに追加

作成したzipをLambda Layerに追加していきます。

[5-1] レイヤーの作成

f:id:predora005:20210413232416p:plain

「.zipファイルをアップロード」で先ほど作成したzipをアップロードします。ランタイムは「Python 3.7」を選択します。

f:id:predora005:20210413232420p:plain

Lambda関数自体のランタイムも「Python 3.7」にし揃える必要があります。ランタイムを「Python3.8」にする場合は前述の通り、zipファイルをAmazon Linux2で作成します。

[5-2] レイヤーの追加

作成したレイヤーをLambda関数に追加します。

f:id:predora005:20210413233354p:plain

f:id:predora005:20210413232442p:plain

「カスタムレイヤー」を選択し、先程作成したレイヤーを選択します。バージョンは「1」しか無い状態なので「1」を選びます。

f:id:predora005:20210413232446p:plain

レイヤーが追加できたので、再度テストを実行します。

f:id:predora005:20210413232506p:plain

今度はエラーが出ずに関数が実行されます。

f:id:predora005:20210413232511p:plain

終わりに

調べるのにだいぶ時間を要しましたが、冒頭で紹介した記事のおかげでどうにかなりました。この方法は今回紹介したPillowだけでなく、他のパッケージを追加する際にも使用できます。やり方さえ分かれば時間はそれほどかからないので、気軽にパッケージの追加が行えそうです。

[備考1] Amazon LinuxでDockerを使用する場合

Amazon LinuxでDockerを使用する場合は、ec2-userをdockerグループに追加します。追加後にログアウト・再ログインが必要です。

$ sudo usermod -a -G docker ec2-user

dockerグループへの追加を忘れると、以下のエラーが表示されます。

$ docker pull amazonlinux
Using default tag: latest
Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/images/create?fromImage=amazonlinux&tag=latest: dial unix /var/run/docker.sock: connect: permission denied

[備考2] Python3.8を使いたい場合

Amazon Linux2のEC2を構築して、Dokcerコンテナで行ったのと同様のことを行います。

まずは、python3.8をインストールします。

$ sudo amazon-linux-extras install python3.8

次にpythonディレクトリを作成し、Pillowをインストールします。

$ mkdir python
$ pip3.8 install Pillow -t python

pythonディレクトリにインストールしたPillowをzip化します。

$ zip -r PIL.zip python

レイヤー追加以降の手順は、Amazon Linuxの場合と同様です。

出典