【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
参考文献
【AWS】pythonでS3のファイルを操作する手順(Boto3)
pythonのプログラムからS3を操作する手順をまとめました。ファイルのアップロード/ダウンロードなど、基本的な手順を書いています。
- [1] 前提条件
- [2] 準備
- [3] Boto3を使った操作の概略
- [4] Boto3を使った操作の詳細
- 終わりに
- 補足
- 出典
[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ポリシーは下記資料を参考にしています。
- ユーザーポリシーの例 - Amazon Simple Storage Service
- Amazon S3: S3 バケットのオブジェクトへの読み取りおよび書き込みアクセスを許可する - AWS Identity and Access Management
[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.resource
とboto3.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.resource
とboto3.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ロールを作成します。
ユースケースの選択で[S3]を選びます。
[ポリシーの作成]を選びます。
本文に記載したJSONを貼り付けます。バケット名はご自身のバケットの名称に置き換えます。
ポリシー名は任意の名称で構いません。
ロールの作成画面に戻ったら、作成したポリシーを割り当てます。更新ボタンを押してポリシー名を検索ボックスに入力すると、選択肢に出てきます。
ロール名は任意の名称で構いません。
[補足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択です。
- 本物のLambda関数でテスト
- ローカル環境で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 | Tail でLogResult を出力、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] より詳細を知りたい場合は
公式(下記)を参照してください。
[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公式のハンズオン「サーバーレスのウェブアプリケーションを構築」に習って、簡単なウェブアプリの構築を行いました。
このハンズオンを通して、以下の要素に触れることができます。
AWSのハンズオンやチュートリアルはモノによって分かりやすさに差があります。このハンズオンは分かりやすい方だったのでオススメです。
- [1] Gitリポジトリの作成(CodeCommit)
- [2] ウェブサイトをデプロイする(Amplify)
- [3] ユーザー管理(Cognito)
- [4] DynamoDBテーブルを作成
- [5] Lambda関数の作成
- [6] REST APIの作成(API Gateway)
- [7] リソースの削除
- 終わりに
- 出典
[1] Gitリポジトリの作成(CodeCommit)
ウェブアプリケーションのソース(index.html等)は「CodeCommit」で管理します。
[1-1] リポジトリの作成
まずはリポジトリを作成します。
「wildrydes-site」というリポジトリを作成します。
リポジトリが作成できたら、URLをコピーしてクローンします。
[1-2] IAMでGit認証情報を生成
Git認証情報を作成していない場合は、クローンする前に作成しておきます。IAMで使用するユーザを選択し、下にスクロールすると[認証情報を生成]があります。
ボタンを押すと、ユーザー名とパスワードが表示されます。これらをCodeCommitでの認証に使用します。
[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の画面からウェブアプリケーションを作成していきます。
[AWS CodeCommit]を選択します。
先程作成したリポジトリを選択します。
アプリの名前は任意で問題ありません。ここではチュートリアル通りの名称としています。
作成してから検証が終わるまで数分かかります。
検証が終わったのちURLをクリックすると、ウェブサイトが表示されます。
[2-2] サイトの変更
index.html
を変更・コミットして、Webサイトに反映されるか確認します。index.html
を開いてtitleを変更します。
変更後、git add
, git commit
, git push
します。
$ git add index.html $ git commit -m "updated title" $ git push
git push
するとAmplifyでデプロイと検証が自動で行われます。
検証まで終わった後、Webサイトを表示するとtitle変更が反映されています。
[3] ユーザー管理(Cognito)
ユーザー認証は「Cognito」で行います。
[3-1] ユーザープールを作成
[デフォルトを確認する]の方を選択します。プール名は任意の名称で構いません。
デフォルト設定のままで作成を完了します。
以上でユーザープールの作成は完了です。プールIDはこの後使用します。
[3-2] ユーザープールにアプリクライアントを追加
アプリクライアント名は任意で問題ありません。[クライアントシークレットを生成]のチェックは外します。
今回作成するブラウザベースのアプリでは、クライアントシークレットがサポートされていないためです。ハンズオンに以下の記載がありました。
クライアントシークレットは、現在ブラウザベースのアプリケーションでの使用はサポートされていません。
表示されたアプリクライアントIDはこの後使用します。
[3-3] ウェブサイトの設定を更新
js/config.js
を開き、先述の手順で取得したプールID、アプリクライアントIDを入力します。
- userPoolId : プールID
- userPoolClientId: アプリクライアントID
- region: リージョン
編集が終わったら変更をリポジトリに送信します。
$ git add js/config.js $ git commit -m "update cognito config" $ git push
[3-4] テスト
Amplifyでデプロイ・検証が終わったら、再度ウェブサイトにアクセスします。[GIDDY UP!]を選択すると登録画面に移動します。
メールアドレスとパスワードを入力します。メールアドレスは、実在のアドレス・ダミーアドレスどちらの入力も可能です。
実在のアドレスの場合は送信されたメールに記載の認証コードを入力します。ダミーアドレスの場合はCognitoコンソールから確認できるそうです。私は実在のアドレスを使用しました。
認証に成功するとダイアログが表示されます。
認証画面で再度メールアドレス・パスワードを入力します。
認証には成功しますが、APIが無いというメッセージが表示されます。APIはこの後設定します。
[4] DynamoDBテーブルを作成
「RideId」をプライマリキーに持つテーブルを作成します。
[5] Lambda関数の作成
[5-1] IAMロールの作成
Lambda関数作成の前に、DynamoDBにアクセスする権限を持つロールを作成します。
検索ボックスに「AWSLambdaBasicExecutionRole」を入力し、チェックボックスをONにします。
ロール名は任意の名称で問題ありません。
[5-2] インラインポリシーの追加
ロールにインラインポリシーを追加し、先程作成したDynamoDBへのアクセス権限を追加します。
サービス「DynamoDB」、アクション「PutItem」、リソースは先程作成したDynamoDBテーブルを指定します。
名前は任意で問題ありません。
[5-3] Lambda関数の作成
ランタイムは「Node.js」を選択します。
実行ロールは「既存のロールを使用する」を選択し、先程作成した IAMロールを選択します。
関数の作成完了後
「requestUnicorn.js」をindex.js
にコピーします。
[5-4] テスト
テストイベントを作成し、入力欄に下記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}}" }
テストを実行し成功すると、以下のような結果が得られます。
[6] REST APIの作成(API Gateway)
[6-1] REST APIの作成
Lambda関数を呼ぶためのエンドポイントとなるREST APIを作成します。
「新しいAPI」を選択し、エンドポイントタイプは「エッジ最適化」を選択します。
[6-2] Cognito ユーザープールオーソライザーを作成
タイプを「Cognito」にし、Cognitoユーザープールは作成したものを選択します。トークンのソースは「Authorization」にします。
作成後、テストを行います。
ブラウザからAmplifyで作成したページに飛び、/ride.html
にアクセスします。認証を行うとトークンが表示されるのでコピーします。
テストが成功すると、Response Code: 200が返ってきます。
[6-3] POSTメソッド追加
[リソースの作成]を選択し、まずはrideリソースを作成します。
名前は「ride」とし「API Gateway CORSを有効にする」にチェックを付けます。
[メソッドの作成]を選択し、rideリソースにPOSTメソッドに追加します。
POSTを選択しチェックマークをクリックします。
統合タイプは「Lambda関数」にし、「Lambdaプロキシ統合の使用」にチェックします。Lambda関数は先程作成した関数の名称を入力します。
Lambda関数に権限を追加してよいか確認ダイアログが表示されるので[OK]をクリックします。
[メソッドリクエスト]を選択します。
認可横の鉛筆マークを選択するとコンボボックスが表示されるので、Cognitoで追加したオーソライザー「WildRydes」を選択します。
[6-4] APIをデプロイ
アクションから[APIのデプロイ]を選択します。
「新しいステージ」を選択し、ステージ名を「prod」とします。
URLが表示されるので、この後ウェブサイトの設定ファイルに反映します。
[6-5] ウェブサイトの設定更新
js/config.js
を開き「invokeURL」にAPIのURLを設定します。
設定をCodeCommitのリポジトリに反映します。
$ git add js/config.js $ git commit -m "Add invokeUrl to config.js" $ git push
Amplifyの画面でデプロイ・検証が終わったことを確認し、ウェブサイトにアクセスすると地図が表示されます。
地図上の任意の位置をクリックすると、どこからともなく馬が現れクリックした位置で停止します。
[7] リソースの削除
- Amplify
- Cognitoユーザープール
- Lambda関数
- IAMロール (WildRydesLambda)
- IAMロール(AWSAmplifyExecutionRole〜)
- IAMロール (AWSAmplifyExecutionPolicy〜)
- DynamoDBテーブル
- API Gateway
- CloudWatchロググループ
- CodeCommitリポジトリ
終わりに
分量は多いですが、ハンズオンの説明が分かりやすいこともあり、途中で詰まることなく進められました。AWSの色々なサービスに触れることができ、良いハンズオンでした。
出典
- アイキャッチはGerd AltmannによるPixabayからの画像
【AWS】SAMの使い方とチュートリアルの解説
AWS SAMの使い方について、チュートリアルの実行手順を解説しつつ紹介します。紹介する手順は、Amazon Linux2上で試したものです。
SAMはServerless 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.yaml」AWSリソースを定義するテンプレートファイル。
- 「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」というスタックが作成されています。
S3バケットの中身を確認すると、メッセージに表示されていたテンプレートファイルとzipファイル(拡張子はzipになっていない)が作成されています。
[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でスタックを作成・変更する事前に、リソースがどう変わるのかを確認できる機能です。マネージメントコンソールからも確認できます。
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] 動作確認
作成されたAPIにcurl
でリクエストを送信すると{"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-api
でAPIをローカルでホストします。
$ 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を用いていますが、ここではマネージメントコンソールを用いる方法を紹介します。
- [1] S3バケットを作成
- [2] IAMポリシーとロールの作成
- [3] Lambda関数の作成
- [4] Lambda関数のテスト
- [5] S3トリガーでLambda関数起動
- [6] リソースの削除
- 終わりに
- 出典
[1] S3バケットを作成
バケットを2つ作成します。1つ目は任意の名前のバケット、2つ目は「{1つ目のバケット名}-resized」にします。
1つ目のバケットに「HappyFace.jpg」をアップロードします。
[2] IAMポリシーとロールの作成
[2-1] IAMポリシーの作成
以下の権限を持つIAMポリシーを作成します。
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」としていますが、変更しても問題ありません。
[2-2] IAMロールの作成
続いて、IAMロールを作成します。
先程作成したポリシーをアタッチします。テキストボックスにポリシーの名称を入力すると表示されます。チェックを付けて次に進めます。
ロール名はチュートリアル通り「lambda-s3-role」としていますが、変更しても問題ありません。
[3] Lambda関数の作成
[3-1] 関数の作成
ランタイムは「Python3.8」としていますが、3.7でも構いません。後述のPillowインストール問題があるため、各々の都合や環境に合わせて変更しましょう。
アクセス権限欄から先程作成したIAMロールを選択します。
ソースはチュートリアルに記載のコードを貼り付けます。
チュートリアル: 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" } } } ] }
変更を保存して、テストを実行します。
実行後「〜-rezied」のバケットを確認すると、1つ目のバケットと同名のファイルが作成されています。
一見ファイルがコピーされただけのように見えますが、ファイルの詳細を確認すると縦横が1/2のサイズになっています。
[5] S3トリガーでLambda関数起動
[5-1] S3にLambda関数のアクセス権限追加
S3からLambda関数にアクセスする権限を追加します。Lambda関数の[設定][アクセス権限]から追加します。
設定は以下の通りです。ソースアカウントにはアカウントID、ソースARNはS3のリソース名(arn:aws:s3:::{バケット名}を入力します。
アクションは「Lambda:InvokeFunction」を選択します。ステートメントIDは任意の名称で問題ありません。
[5-2] S3バケットからLambdaへの通知設定
S3バケットにファイルが登録された際、Lambda関数に通知する設定を追加します。S3の[プロパティ]から追加します。
イベント名は任意の名称で問題ありません。
イベントタイプは「すべてのオブジェクト作成イベント」を選択します。
送信先は先程作成したLambda関数を設定します。
[5-3] S3バケットに画像ファイルをアップロード
準備が整ったので、S3バケットに画像ファイルをアップロードします。
「〜-rezied」のバケットを確認すると、リサイズされた画像ファイルが作成されています。
[6] リソースの削除
以下のリソースを削除します。
- Lambda関数
- Lambdaレイヤー
- CloudWatchのロググループ
- IAMロールとIAMポリシー
- S3バケット
終わりに
Pillowのインストールに躓きましたが、それ以外はチュートリアルに沿って順調に進めることができました。
リソースの数が多いため、CloudFormationやSAMで作成するのがよいかもしれません。
機械学習では大量の画像を扱うことがありますが、画像をリサイズして統一することが多いです。S3に画像をアップロードしたとき、機械学習用にリサイズするようにしたら便利だなと思いました。
出典
- アイキャッチはGerd AltmannによるPixabayからの画像
- PublicDomainPicturesによるPixabayからの画像をサムネイル作成に使用
- KM NEHAによるPixabayからの画像をサムネイル作成に使用
【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」を使った方法を紹介します。
方法を調べるにあたり下記記事を大変参考にさせていただき、とても助かりました。ありがとうございます。
- [1] 必要な環境
- [2] Dockerインストール
- [2] docker-composeインストール
- [3] Amazon Linuxコンテナの起動確認
- [4] パッケージ作成
- [5] Lambda Layerに追加
- 終わりに
- [備考1] Amazon LinuxでDockerを使用する場合
- [備考2] Python3.8を使いたい場合
- 出典
[1] 必要な環境
Amazon Linuxを用意する必要があります。Amazon Linux上でPillowをローカルにインストールしzip圧縮してLayerに追加します。
用意する環境は、Lambda関数のPythonバージョンによって異なります。Python3.8を使いたい場合はAmazon Linux2、Python3.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コンテナの起動確認
$ docker pull amazonlinux
docker images
でAmazon Linuxのコンテナが取得できたことを確認します。
$ docker images
REPOSITORY TAG IMAGE ID CREATED SIZE
amazonlinux latest e2c0b9bec08e 12 days ago 163MB
$ 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ディレクトリの下にパッケージをインストールする必要があります。詳細は以下の記事を参考にさせていただきました。
[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] レイヤーの作成
「.zipファイルをアップロード」で先ほど作成したzipをアップロードします。ランタイムは「Python 3.7」を選択します。
Lambda関数自体のランタイムも「Python 3.7」にし揃える必要があります。ランタイムを「Python3.8」にする場合は前述の通り、zipファイルをAmazon Linux2で作成します。
[5-2] レイヤーの追加
作成したレイヤーをLambda関数に追加します。
「カスタムレイヤー」を選択し、先程作成したレイヤーを選択します。バージョンは「1」しか無い状態なので「1」を選びます。
レイヤーが追加できたので、再度テストを実行します。
今度はエラーが出ずに関数が実行されます。
終わりに
調べるのにだいぶ時間を要しましたが、冒頭で紹介した記事のおかげでどうにかなりました。この方法は今回紹介した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の場合と同様です。
出典
- アイキャッチはGerd AltmannによるPixabayからの画像