見出し画像

AWS CDK + TypeScript で API Gateway + Lambda 構成を作る

 前回は 公式 CDK Workshop を見ながら Lambda 関数をデプロイしました。このままでは外側から呼び出すことができないので、API Gateway と連携させてみることにします。

 上記の公式 Workshop の続きに API Gateway との連携が用意されているので、その通りにやっていきます。

API Gateway を追加する

(CDK プロジェクトは前回から引き継いだものを使います)

 まずは依存するパッケージのインストールから。

npm install @aws-cdk/aws-apigateway

 次に lib/example-cdk-hello-lambda-stack.ts の変更を行います。まずは先頭に入れたパッケージの import を追記。

import * as apigw from "@aws-cdk/aws-apigateway";

 そして Lambda 関数をデプロイしている処理の下に下記コードを挿入。

    new apigw.LambdaRestApi(this, "Endpoint", {
     handler: hello,
   });

 えっ、これだけ? すごい。

 たった数行のこのコードだけで API Gateway で Lambda 統合プロキシをセットアップして Lambda 関数と紐づけまでできちゃうみたいです。

cdk diff

 プロビジョニングされているリソースと今回定義したリソースの差分を見てみます。

cdk diff

 出力結果

Stack ExampleCdkHelloLambdaStack
IAM Statement Changes
┌───┬────────────────────────┬────────┬────────────────────────┬────────────────────────┬────────────────────────┐
│   │ ResourceEffectActionPrincipalCondition              │ 
├───┼────────────────────────┼────────┼────────────────────────┼────────────────────────┼────────────────────────┤ 
│ + │ ${Endpoint/CloudWatchRAllow  │ sts:AssumeRoleService:apigateway.ama │                        │ 
│   │ ole.Arn}               │        │                        │ zonaws.com             │                        │ 
├───┼────────────────────────┼────────┼────────────────────────┼────────────────────────┼────────────────────────┤ 
│ + │ ${HelloHandler.Arn}    │ Allow  │ lambda:InvokeFunctionService:apigateway.ama │ "ArnLike": {           │ 
│   │                        │        │                        │ zonaws.com             │   "AWS:SourceArn": "ar │ 
│   │                        │        │                        │                        │ n:${AWS::Partition}:ex │ 
│   │                        │        │                        │                        │ ecute-api:${AWS::Regio │ 
│   │                        │        │                        │                        │ n}:${AWS::AccountId}:$ │ 
│   │                        │        │                        │                        │ {EndpointEEF1FD8F}/${E │ 
│   │                        │        │                        │                        │ ndpoint/DeploymentStag │ 
│   │                        │        │                        │                        │ e.prod}/*/{proxy+}"    │ 
│   │                        │        │                        │                        │ }                      │ 
│ + │ ${HelloHandler.Arn}    │ Allow  │ lambda:InvokeFunctionService:apigateway.ama │ "ArnLike": {           │ 
│   │                        │        │                        │ zonaws.com             │   "AWS:SourceArn": "ar │ 
│   │                        │        │                        │                        │ n:${AWS::Partition}:ex │ 
│   │                        │        │                        │                        │ ecute-api:${AWS::Regio │ 
│   │                        │        │                        │                        │ n}:${AWS::AccountId}:$ │ 
│   │                        │        │                        │                        │ {EndpointEEF1FD8F}/tes │ 
│   │                        │        │                        │                        │ t-invoke-stage/*/{prox │ 
│   │                        │        │                        │                        │ y+}"                   │ 
│   │                        │        │                        │                        │ }                      │
│ + │ ${HelloHandler.Arn}    │ Allow  │ lambda:InvokeFunctionService:apigateway.ama │ "ArnLike": {           │ 
│   │                        │        │                        │ zonaws.com             │   "AWS:SourceArn": "ar │ 
│   │                        │        │                        │                        │ n:${AWS::Partition}:ex │ 
│   │                        │        │                        │                        │ ecute-api:${AWS::Regio │ 
│   │                        │        │                        │                        │ n}:${AWS::AccountId}:$ │ 
│   │                        │        │                        │                        │ {EndpointEEF1FD8F}/${E │ 
│   │                        │        │                        │                        │ ndpoint/DeploymentStag │ 
│   │                        │        │                        │                        │ e.prod}/*/"            │ 
│   │                        │        │                        │                        │ }                      │ 
│ + │ ${HelloHandler.Arn}    │ Allow  │ lambda:InvokeFunctionService:apigateway.ama │ "ArnLike": {           │ 
│   │                        │        │                        │ zonaws.com             │   "AWS:SourceArn": "ar │ 
│   │                        │        │                        │                        │ n:${AWS::Partition}:ex │ 
│   │                        │        │                        │                        │ ecute-api:${AWS::Regio │ 
│   │                        │        │                        │                        │ n}:${AWS::AccountId}:$ │ 
│   │                        │        │                        │                        │ {EndpointEEF1FD8F}/tes │ 
│   │                        │        │                        │                        │ t-invoke-stage/*/"     │ 
│   │                        │        │                        │                        │ }                      │ 
└───┴────────────────────────┴────────┴────────────────────────┴────────────────────────┴────────────────────────┘ 
IAM Policy Changes
┌───┬────────────────────────────┬───────────────────────────────────────────────────────────────────────────────┐ 
│   │ ResourceManaged Policy ARN                                                            │ 
├───┼────────────────────────────┼───────────────────────────────────────────────────────────────────────────────┤ 
│ + │ ${Endpoint/CloudWatchRole} │ arn:${AWS::Partition}:iam::aws:policy/service-role/AmazonAPIGatewayPushToClou │ 
│   │                            │ dWatchLogs                                                                    │ 
└───┴────────────────────────────┴───────────────────────────────────────────────────────────────────────────────┘ 
(NOTE: There may be security-related changes not in this list. See https://github.com/aws/aws-cdk/issues/1299)     

Resources
[+] AWS::ApiGateway::RestApi Endpoint EndpointEEF1FD8F 
[+] AWS::IAM::Role Endpoint/CloudWatchRole EndpointCloudWatchRoleC3C64E0F 
[+] AWS::ApiGateway::Account Endpoint/Account EndpointAccountB8304247 
[+] AWS::ApiGateway::Deployment Endpoint/Deployment EndpointDeployment318525DAc5ea4e80a778e1689ca3a2bce5ed88de     
[+] AWS::ApiGateway::Stage Endpoint/DeploymentStage.prod EndpointDeploymentStageprodB78BEEA0 
[+] AWS::ApiGateway::Resource Endpoint/Default/{proxy+} Endpointproxy39E2174E 
[+] AWS::Lambda::Permission Endpoint/Default/{proxy+}/ANY/ApiPermission.ExampleCdkHelloLambdaStackEndpoint7EE55095.ANY..{proxy+} EndpointproxyANYApiPermissionExampleCdkHelloLambdaStackEndpoint7EE55095ANYproxy24FC3890
[+] AWS::Lambda::Permission Endpoint/Default/{proxy+}/ANY/ApiPermission.Test.ExampleCdkHelloLambdaStackEndpoint7EE55095.ANY..{proxy+} EndpointproxyANYApiPermissionTestExampleCdkHelloLambdaStackEndpoint7EE55095ANYproxy33B39215     
[+] AWS::ApiGateway::Method Endpoint/Default/{proxy+}/ANY EndpointproxyANYC09721C5 
[+] AWS::Lambda::Permission Endpoint/Default/ANY/ApiPermission.ExampleCdkHelloLambdaStackEndpoint7EE55095.ANY.. EndpointANYApiPermissionExampleCdkHelloLambdaStackEndpoint7EE55095ANYCFA1F0D0 
[+] AWS::Lambda::Permission Endpoint/Default/ANY/ApiPermission.Test.ExampleCdkHelloLambdaStackEndpoint7EE55095.ANY.. EndpointANYApiPermissionTestExampleCdkHelloLambdaStackEndpoint7EE55095ANYD177CB2C
[+] AWS::ApiGateway::Method Endpoint/Default/ANY EndpointANY485C938B 

Outputs
[+] Output Endpoint/Endpoint Endpoint8024A810: {"Value":{"Fn::Join":["",["https://",{"Ref":"EndpointEEF1FD8F"},".execute-api.",{"Ref":"AWS::Region"},".",{"Ref":"AWS::URLSuffix"},"/",{"Ref":"EndpointDeploymentStageprodB78BEEA0"},"/"]]}}

 あのたった数行を追加しただけで、IAM 設定から API Gateway 作成 と Lambda 関数への紐づけを行おうとしていることが分かります。

CloudFormation テンプレートを確認する

 以下のコマンドで CloudFormation テンプレートが標準出力されます。

cdk synth

 出力結果(一部抜粋)

  EndpointproxyANYC09721C5:
   Type: AWS::ApiGateway::Method
   Properties:
     HttpMethod: ANY
     ResourceId:
       Ref: Endpointproxy39E2174E
     RestApiId:
       Ref: EndpointEEF1FD8F
     AuthorizationType: NONE
     Integration:
       IntegrationHttpMethod: POST
       Type: AWS_PROXY
       Uri:
         Fn::Join:
           - ""
           - - "arn:"
             - Ref: AWS::Partition
             - ":apigateway:"
             - Ref: AWS::Region
             - :lambda:path/2015-03-31/functions/
             - Fn::GetAtt:
                 - HelloHandler2E4FBA4D
                 - Arn
             - /invocations

 すべてのリクエストを Lambda 関数へ流していることが見て取れます。

 Lambda 統合プロキシの場合は IntegrationHttpMethod が常に POST になるそうです。

Lambda 統合では、関数呼び出しの Lambda サービスアクションの仕様に従って、統合リクエストに POST の HTTP メソッドを使用する必要があります。
https://docs.aws.amazon.com/ja_jp/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html

 API Gateway から Lambda 関数へは常に POST になるから? だそうで。

デプロイしてブラウザから確認する

cdk deploy
 ✅  ExampleCdkHelloLambdaStack

Outputs:
ExampleCdkHelloLambdaStack.Endpoint = https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/prod/

 最後に出力された URL にアクセスしてみましょう。

画像1

 HTTP ステータスコード 200 で text/plain で返ってきました🤘

まとめ

 AWS CDK を用いて Lambda 関数をデプロイして API Gateway との統合をすることができました。

 独自ドメインは? 単体テストは? TypeScript 化は? とまだまだ深堀できる部分がありますので、やっていきたいと思います。

 正直なところ、今の段階ではまだ Serverless Framework を選択するかなぁと思いました。やっぱりプロジェクト作成時に TypeScript のボイラーテンプレート選べるの強い。とはいえ CDK の方がインフラ部分も TypeScript で定義できるので、TypeScript の勉強もかねてしばらく付き合っていこうと思います。


😉