CloudFormationでAPIGatewayを作成するときのワナ

既に作成済みのCloudFormationのAPIGateway用テンプレートを改修する対応が必要な場面があったのですが、その際にはまったポイントがありました。

CloudFormationでAPIGatwayを作成

まずは公式のサンプルテンプレートを参考に、APIGatewayをとりあえずデプロイします。
作成するリソースは、「RestApi」「Mehthod」「Deployment」の3種類とします。

次のようなテンプレートを使用します。

AWSTemplateFormatVersion: 2010-09-09
Description: ---

Parameters:
 apiName:
   Type: String
   Default: TestApi

Resources:
 MyRestApi:
   Type: AWS::ApiGateway::RestApi
   Properties:
     Name: !Ref apiName

 MyRestApiMethod:
   Type: AWS::ApiGateway::Method
   Properties:
     RestApiId: !Ref MyRestApi
     ResourceId: !GetAtt MyRestApi.RootResourceId
     HttpMethod: GET
     AuthorizationType: NONE
     RequestParameters:
       method.request.header.myheader: false
     Integration:
       Type: MOCK
       RequestTemplates:
         application/json: "{\"statusCode\": 200}"
       RequestParameters:
         integration.request.header.header1: method.request.header.myheader
       IntegrationResponses:
         - StatusCode: '200'
           ResponseParameters:
             method.response.header.header1: integration.response.header.header1
             method.response.header.header2: '''staticvalue'''
     MethodResponses:
       - StatusCode: '200'
         ResponseParameters:
           method.response.header.header1: true
           method.response.header.header2: true

 MyRestApiDeployment:
   Type: 'AWS::ApiGateway::Deployment'
   DependsOn:
     - MyRestApiMethod
   Properties:
     RestApiId: !Ref MyRestApi
     Description: My deployment
     StageName: TestStage

正常にデプロイされていると、次のような応答が返されるはずです。

$ curl -i "https://iemix5tpg8.execute-api.ap-northeast-1.amazonaws.com/TestStage/"                                                                                                                                                                                                          
HTTP/2 200 

CloudFormationを改修する

上記のテンプレートに対し、追加のリソースとメソッドを作成してみます。
テンプレートの末尾にMyRestApiResourceとMyRestApiResourceMethodを追加しました。

AWSTemplateFormatVersion: 2010-09-09
Description: ---

Parameters:
 apiName:
   Type: String
   Default: TestApi

Resources:
 MyRestApi:
   Type: AWS::ApiGateway::RestApi
   Properties:
     Name: !Ref apiName

 MyRestApiDeployment:
   Type: 'AWS::ApiGateway::Deployment'
   DependsOn:
     - MyRestApiMethod
     - MyRestApiResourceMethod
   Properties:
     RestApiId: !Ref MyRestApi
     Description: My deployment
     StageName: TestStage

 MyRestApiMethod:
   Type: AWS::ApiGateway::Method
   Properties:
     RestApiId: !Ref MyRestApi
     ResourceId: !GetAtt MyRestApi.RootResourceId
     HttpMethod: GET
     AuthorizationType: NONE
     RequestParameters:
       method.request.header.myheader: false
     Integration:
       Type: MOCK
       RequestTemplates:
         application/json: "{\"statusCode\": 200}"
       RequestParameters:
         integration.request.header.header1: method.request.header.myheader
       IntegrationResponses:
         - StatusCode: '200'
           ResponseParameters:
             method.response.header.header1: integration.response.header.header1
             method.response.header.header2: '''staticvalue'''
     MethodResponses:
       - StatusCode: '200'
         ResponseParameters:
           method.response.header.header1: true
           method.response.header.header2: true

 MyRestApiResource:
   Type: 'AWS::ApiGateway::Resource'
   Properties:
     RestApiId: !Ref MyRestApi
     ParentId: !GetAtt
       - MyRestApi
       - RootResourceId
     PathPart: resource1

 MyRestApiResourceMethod:
   Type: AWS::ApiGateway::Method
   Properties:
     RestApiId: !Ref MyRestApi
     ResourceId: !Ref MyRestApiResource
     HttpMethod: GET
     AuthorizationType: NONE
     RequestParameters:
       method.request.header.myheader: false
     Integration:
       Type: MOCK
       RequestTemplates:
         application/json: "{\"statusCode\": 200}"
       RequestParameters:
         integration.request.header.header1: method.request.header.myheader
       IntegrationResponses:
         - StatusCode: '200'
           ResponseParameters:
             method.response.header.header1: integration.response.header.header1
             method.response.header.header2: '''staticvalue'''
     MethodResponses:
       - StatusCode: '200'
         ResponseParameters:
           method.response.header.header1: true
           method.response.header.header2: true

CloudFormationのデプロイを実行後、同じように確認コマンドを実行します。
すると、以下の通り、レスポンスコード200が返されません。

$ curl -i "https://iemix5tpg8.execute-api.ap-northeast-1.amazonaws.com/TestStage/resource1"
HTTP/2 403 

この理由は、コンソール画面のステージメニューをみるとわかります。

CloudFormation上で追加したリソースとメソッドがステージにデプロイされていない状況であることがわかります。

CloudFormationのテンプレートを改修して、アップデートするだけではデプロイまでは自動で行われないのです。

解決方法

CloudFormationのアップデートのタイミングでデプロイまで自動で行うためには、デプロイメントを新規に作成しなおす必要があります。

したがって、CloudFormation上の論理IDを変更する必要があります。
論理IDを変更することで、CloudFormation上では別リソースと解釈され、新しい物理IDを持ったデプロイメントが作成されます。

具体的には、次のようにDeploymentリソースを修正します。

 MyRestApiDeployment2:  ## MyRestApiDeployment から MyRestApiDeployment2に論理IDを変更
   Type: 'AWS::ApiGateway::Deployment'
   DependsOn:
     - MyRestApiMethod
     - MyRestApiResourceMethod
   Properties:
     RestApiId: !Ref MyRestApi
     Description: My deployment 2
     StageName: TestStage

修正後のテンプレートをデプロイすると、正しくリソースとメソッドがデプロイされました。
この時、デプロイ履歴から確認できるデプロイID(デプロイメントの物理ID)も変更されています。

curlコマンドによる確認も問題なく動作しました。

$ curl -i "https://iemix5tpg8.execute-api.ap-northeast-1.amazonaws.com/TestStage/resource1"
HTTP/2 200

以上のような方法で、API Gatewayの自動デプロイメントを実現できました。

また、後の調査でわかったことではありますが、APIGatewayのステージのリソースでAWS::ApiGatewayV2::Stageを使用し、AutoDeployフィールドをtrueに設定する方法もあるようです。
参考:https://dev.classmethod.jp/articles/tsnote-cloudformation-autodeploy/

AWS::ApiGatewayのリソースで既にテンプレートが作られてしまっている場合などに本記事で紹介した方法の利用は限られそうです。

コメント

タイトルとURLをコピーしました