これは、なにをしたくて書いたもの?
LocalStackの機能を見ていると、Amazon API Gatewayが使えそうなので、AWS Lambdaと組み合わせてLocalStack上で
動かしてみようかな、と。
環境
今回の環境は、こちらです。
$ localstack --version 0.12.17.5
AWS CLIは、LocalStack提供のもの+本家のものを使います。
$ 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は、LAMBDA_EXECUTOR
をdocker-reuse
としておきました。
$ LAMBDA_EXECUTOR=docker-reuse localstack start
参考するドキュメント
AWS Lambda関数は、Pythonで作成することにします。ランタイムはPython 3.8を使用。
ドキュメントは、全体を通してこのあたりを参考にしています。
チュートリアル: Python 3.8 での Lambda 関数の作成 - AWS Lambda
Amazon API GatewayはREST APIの方で作ることにします。よって、ドキュメントはこちらを参照。
API Gateway で Lambda プロキシ統合を設定する - Amazon API Gateway
AWS Lambda関数を作成してデプロイする
それでは、まずはAWS Lambda関数を作成します。
こちらのドキュメントを見ていると、Amazon API Gatewayの背後の配置するAWS Lambda関数の入出力には規定が
あるようです。
API Gateway が Lambda 出力を API レスポンスとしてクライアントに渡すには、Lambda 関数は結果をこの形式で返す必要があります。
API Gateway Lambda プロキシの統合について理解する
具体的には、こちらのドキュメントですね。
この形式に則った形で、AWS Lambda関数を作成。
my_function.py
import json def handler(event, context): print(f'event = {event}') path = event['path'] http_method = event['httpMethod'] word = None if 'queryStringParameters' in event: if 'word' in event['queryStringParameters']: word = event['queryStringParameters']['word'] if 'body' in event and len(event['body']) != 0: body = json.loads(event['body']) if 'word' in body: word = body['word'] if word is None: word = 'World' return { 'statusCode': 200, 'body': { 'message': f'Hello {word}!!', 'path': path, 'http_method': http_method } }
今回は、メッセージをQueryStringまたはHTTPボディで受け取り、その内容をレスポンスのメッセージに組み込んで返すように
しましょう。
$ zip my_function_package.zip my_function.py
デプロイ。
$ awslocal lambda create-function \ --function-name my_function \ --zip-file fileb://my_function_package.zip \ --handler my_function.handler \ --runtime python3.8 \ --role test-role
更新する場合は、本来はupdate-function-code
なのですが…LocalStackだとうまくいかないことがあり。
$ awslocal lambda update-function-code --function-name my_function --zip-file fileb://my_function_package.zip
削除して、もう1度登録した方がよいかもしれません。
$ awslocal lambda delete-function --function-name my_function
Amazon API Gatewayのリソースを作成して、AWS Lambdaと統合する
続いて、Amazon API Gatewayの方の作業に進みます。ドキュメントは、こちらを参考に。
API Gateway で Lambda プロキシ統合を設定する - Amazon API Gateway
まずは、REST APIを作成。
$ awslocal apigateway create-rest-api --name 'My Rest API Gateway'
コマンドの実行結果。
{ "id": ".....", "name": ".....", "createdDate": "....., "version": "V1", "binaryMediaTypes": [], "apiKeySource": "HEADER", "endpointConfiguration": { "types": [ "EDGE" ] }, "tags": {}, "disableExecuteApiEndpoint": false }
このレスポンスに含まれるid
の値を覚えておきます。今回はREST_API_ID
という変数に保存しておきました。
$ REST_API_ID=[idの値を指定]
次に、ルートリソースを確認します。
$ awslocal apigateway get-resources --rest-api-id $REST_API_ID
レスポンス。
{ "items": [ { "id": ".....", "path": "/" } ] }
ここで、ルートリソース(path /
のid
)の値を覚えておきます。RESOURCE_ROOT_ID
という変数に保存しておきました。
$ RESOURCE_ROOT_ID=[path / のidの値を指定]
ルートリソースを親として、新しくリソースを作成します。
$ awslocal apigateway create-resource \ --rest-api-id $REST_API_ID \ --parent-id $RESOURCE_ROOT_ID \ --path-part {proxy+}
レスポンス。
{ "id": ".....", "parentId": ".....", "pathPart": "{proxy+}", "path": "/{proxy+}" }
ここで得られたリソースのid
も覚えておきます。RESOURCE_ID
という変数に保存しておきました。
$ RESOURCE_ID=[create-resourceの結果で得られたidの値]
作成したリソースに対してメソッドを追加。
$ awslocal apigateway put-method \ --rest-api-id $REST_API_ID \ --resource-id $RESOURCE_ID \ --http-method ANY \ --authorization-type "NONE"
レスポンス。
{ "httpMethod": "ANY", "authorizationType": "NONE", "apiKeyRequired": false }
続いてはAWS Lambda関数とのインテグレーションを行うのですが、この時に作成したAWS Lambda関数のARNが必要に
なるので取得しておきます。
$ awslocal lambda get-function --function-name my_function --query 'Configuration.FunctionArn' "arn:aws:lambda:us-east-1:000000000000:function:my_function" $ LAMBDA_ARN=arn:aws:lambda:us-east-1:000000000000:function:my_function
取得したAWS Lambda関数のARN、REST APIのID、リソースのIDを指定して、インテグレーションの設定を行います。
$ awslocal apigateway put-integration \ --rest-api-id $REST_API_ID \ --resource-id $RESOURCE_ID \ --http-method ANY \ --type AWS_PROXY \ --integration-http-method POST \ --uri arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/$LAMBDA_ARN/invocations \ --credentials arn:aws:iam::000000000000:role/apigAwsProxyRole
--uri
の部分ですが、AWS LambdaのARNの後に/invocations
が付くのは固定みたいですね。
レスポンス。
{ "type": "AWS_PROXY", "httpMethod": "POST", "uri": "arn:aws:apigateway:us-east-1:lambda:path/2015-03-31/functions/arn:aws:lambda:us-east-1:000000000000:function:my_function/invocations", "requestParameters": {}, "passthroughBehavior": "WHEN_NO_MATCH", "cacheNamespace": ".....", "cacheKeyParameters": [] }
ステージを指定して、デプロイメントの作成。
$ awslocal apigateway create-deployment --rest-api-id $REST_API_ID --stage-name test { "id": "cznsonlidb", "description": "", "createdDate": "2021-09-05T15:21:19+09:00" }
ここまでやって、AWS Lambda関数をAmazon API Gateway経由で呼び出せるようになります。
ところで、本来のAmazon API Gatewayへアクセスするにはxxxxx.execute-api.us-east-1.amazonaws.com
のような
ドメインでアクセスするのですが、LocalStackの場合はそうはいきません。どうするのでしょう。
README.md
に回答が書いてありました。
以下のURLでアクセスするようです。
http://localhost:4566/restapis/<apiId>/<stage>/_user_request_/<methodPath>
_user_request_
という部分は、固定で入ります。
確認してみましょう。
HTTP GETでアクセス。
$ curl http://localhost:4566/restapis/$REST_API_ID/test/_user_request_/request_from_query?word=WordFromQuery {"message": "Hello WordFromQuery!!", "path": "/request_from_query", "http_method": "GET"}
ルートリソースを親にしてpathを{proxy+}
にしているので、この状態だとどのパスでもアクセスできるようです。
POSTでアクセス。
$ curl -XPOST http://localhost:4566/restapis/$REST_API_ID/test/_user_request_/request_post/json -d '{"word": "Lambda"}' {"message": "Hello Lambda!!", "path": "/request_post/json", "http_method": "POST"}
なんとなく、HTTPヘッダーも付けてみたり。
$ curl -XPOST -H 'Content-Type: application/json' http://localhost:4566/restapis/$REST_API_ID/test/_user_request_/request_post/json -d '{"word": "Lambda"}' {"message": "Hello Lambda!!", "path": "/request_post/json", "http_method": "POST"}
こんなところでしょうか。動作確認はOKです。
まとめ
LocalStackで、Amazon API Gateway+AWS Lambdaを動かしてみました。
Amazon API Gatewayが初見だったのですが、用語と操作でいろいろ戸惑う感じがします…。
用語は、このあたりを見ておさえていきましょうか。
Amazon API Gateway の概念 - Amazon API Gateway
とりあえず、動かせるところまでいけたのでOKです。