これは、なにをしたくて書いたもの?
前に、LocalStackの提供するAWS CLIを使ってLocalStackを操作してみました。
LocalStackの提供するAWS CLIを使って、LocalStackを操作する - CLOVER🍀
こちらと類似のもので、LocalStackの提供するAWS SAM(aws-sam-cli-local
)もあったので、こちらを試してみることに
しました。
GitHub - localstack/aws-sam-cli-local: Simple wrapper around AWS SAM CLI for use with LocalStack
これはLocalStackの提供するAWS CLI(awslocal
)と似たようなもので、エンドポイントをLocalStackに向けた
AWS SAMのラッパーです。
環境
今回の環境は、こちら。
$ localstack --version 0.12.17.5 $ python3 -V Python 3.8.10 $ aws --version aws-cli/2.2.35 Python/3.8.8 Linux/5.4.0-81-generic exe/x86_64.ubuntu.20 prompt/off $ awslocal --version aws-cli/2.2.35 Python/3.8.8 Linux/5.4.0-81-generic exe/x86_64.ubuntu.20 prompt/off
LocalStack自体のインストールは割愛します。AWS Lambdaの実行には、docker-reuse
を指定して起動。
$ LAMBDA_EXECUTOR=docker-reuse localstack start
aws-sam-cli-localをインストールして、Amazon API Gateway+AWS Lambdaを動かしてみる
まずはaws-sam-cli-local
をインストール。
$ pip3 install aws-sam-cli-local==1.1.0.1
AWS SAMのラッパーなのですが、aws-sam-cli-local
の依存関係に含まれているので明示的にインストールしなくても
大丈夫です。自分でインストールしていても問題ありません。
バージョン。
$ samlocal --version SAM CLI, version 1.30.0
では、こちらに沿ってAmazon API Gateway+AWS LambdaのHello Worldアプリケーションを作成して、LocalStackに
デプロイしてみましょう。
チュートリアル: Hello World アプリケーションのデプロイ - AWS Serverless Application Model
init。
$ samlocal init
アプリケーション名はsam-app
、ランタイムはPython 3.8とします。
----------------------- 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
生成されたディレクトリに移動。
$ cd sam-app
こんな感じにディレクトリツリーができています。
$ tree . ├── 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
Lambda関数。
hello_world/app.py
import json # import requests def lambda_handler(event, context): """Sample pure Lambda function Parameters ---------- event: dict, required API Gateway Lambda Proxy Input Format Event doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-simple-proxy-for-lambda-input-format context: object, required Lambda Context runtime methods and attributes Context doc: https://docs.aws.amazon.com/lambda/latest/dg/python-context-object.html Returns ------ API Gateway Lambda Proxy Output Format: dict Return doc: https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html """ # try: # ip = requests.get("http://checkip.amazonaws.com/") # except requests.RequestException as e: # # Send some context about this error to Lambda Logs # print(e) # raise e return { "statusCode": 200, "body": json.dumps({ "message": "hello world", # "location": ip.text.replace("\n", "") }), }
Amazon API Gatewayを通したレスポンス形式に沿っていますね。
テンプレート。
template.yaml
AWSTemplateFormatVersion: '2010-09-09' Transform: AWS::Serverless-2016-10-31 Description: > sam-app Sample SAM Template for sam-app # More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst Globals: Function: Timeout: 3 Resources: HelloWorldFunction: Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction Properties: CodeUri: hello_world/ Handler: app.lambda_handler Runtime: python3.8 Events: HelloWorld: Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api Properties: Path: /hello Method: get Outputs: # ServerlessRestApi is an implicit API created out of Events key under Serverless::Function # Find out more about other implicit resources you can reference within SAM # https://github.com/awslabs/serverless-application-model/blob/master/docs/internals/generated_resources.rst#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/" HelloWorldFunction: Description: "Hello World Lambda Function ARN" Value: !GetAtt HelloWorldFunction.Arn HelloWorldFunctionIamRole: Description: "Implicit IAM Role created for Hello World function" Value: !GetAtt HelloWorldFunctionRole.Arn
ビルド。
$ samlocal build
次はデプロイなのですが、今回はS3バケットを先に作成しておきます。
$ awslocal s3 mb s3://my-bucket
デプロイ。AWS CloudFormationのスタック名は任意の名称で。--region
を明示しているのはAWS_REGION
環境変数を
設定していないからのようです。aws configure
もしていないのですが、実施していたらよいのかもしれませんね。
--s3-bucket
では、先ほど作成したS3バケットを指定。
$ samlocal deploy --stack-name my-stack --region us-east-1 --s3-bucket my-bucket
実行結果。
CloudFormation outputs from deployed stack -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Outputs -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- Key HelloWorldApi Description API Gateway endpoint URL for Prod stage for Hello World function Value https://uvpkw1p552.execute-api.us-east-1.amazonaws.com/Prod/hello/ Key HelloWorldFunction Description Hello World Lambda Function ARN Value arn:aws:lambda:us-east-1:000000000000:function:my-stack-HelloWorldFunction-23bdfa27 Key HelloWorldFunctionIamRole Description Implicit IAM Role created for Hello World function Value arn:aws:iam::000000000000:role/cf-my-stack-HelloWorldFunctionRole --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
あっさり作成されました。これで、LocalStack上にデプロイされています。
Amazon API Gatewayのエンドポイント、AWS LambdaのARN、IAMロールのARNが得られます。
awslocal
でも、作成されたAmazon API GatewayのREST APIのidを確認。
$ awslocal apigateway get-rest-apis
$ REST_API_ID=.....
ステージは、Prod
以外にもStage
があるようです。
$ awslocal apigateway get-stages --rest-api-id $REST_API_ID
{ "item": [ { "deploymentId": ".....", "stageName": "Stage", "description": "", "cacheClusterEnabled": false, "methodSettings": {}, "variables": {} }, { "deploymentId": ".....", "stageName": "Prod", "description": "", "cacheClusterEnabled": false, "methodSettings": {}, "variables": {}, "tags": {} } ] }
LocalStack上にデプロイされたLambda関数を、Amazon API Gateway越しに呼び出してみます。
$ curl http://localhost:4566/restapis/$REST_API_ID/Prod/_user_request_/hello {"message": "hello world"}
LocalStackで動作させた時のAmazon API GatewayのURLのルールは、こちらに記載があります。
LocalStack / Invoking API Gateway
使えそうですね。あっさり確認OKです。
動作確認というと、sam local invoke
をしてみては?という話もあるのですが、これをやるんだったら素のAWS SAMを
そのまま使えばよいのではと思うので書きません。
※動かしはしました…というか、動かした瞬間に「そのまま使うのと同じでは」と思いました…
aws-sam-cli-localの仕組み
最初に書いた通り、aws-sam-cli-local
はAWS SAMのラッパーです。
aws-sam-cli-local
のPythonスクリプト内で、エンドポイントをLocalStack向けに設定してsam
コマンドを
呼び出しているものです。
ハマったところ
実は、ひとつだけハマったところがありまして。
sam build
した後に、sam deploy --guided
をサジェストされるのですが。
こちらに素直に従うと
$ samlocal deploy --guided --region us-east-1
S3バケット名が解決できなくてエラーになります。
Error: Unable to upload artifact HelloWorldFunction referenced by CodeUri parameter of HelloWorldFunction resource. Parameter validation failed: Invalid bucket name "!Ref SamCliSourceBucket": Bucket name must match the regex "^[a-zA-Z0-9.\-_]{1,255}$" or be an ARN matching the regex "^arn:(aws).*:(s3|s3-object-lambda):[a-z\-0-9]*:[0-9]{12}:accesspoint[/:][a-zA-Z0-9\-.]{1,63}$|^arn:(aws).*:s3-outposts:[a-z\-0-9]+:[0-9]{12}:outpost[/:][a-zA-Z0-9\-]{1,63}[/:]accesspoint[/:][a-zA-Z0-9\-]{1,63}$"
これを回避したくて、先にS3バケットを作成したのでした…。