LoginSignup
0
0

More than 3 years have passed since last update.

Amazon APIGatewayでCI/CDパイプラインと親和性を高めてIaC運用するにはどうするべきか

Last updated at Posted at 2020-05-31

はじめに

Amazon APIGatewayは複数のLambda関数や別のALB配下のアプリケーションとかを簡単に統合できて便利。
でも、色々なデプロイ方法が競合して死ぬことってないの?SAMとCloudFormation両方で更新したらどうなってしまうの?というのがよく分からないので、実験してどのように運用すれば良いのかを考えてみる。

サンプルプログラムは、以下の仕様で動作する。

  • 初回デプロイ
    • /nameのパスに対するPUTメソッドでクエリパラメータとしてidnameを受け取り、DynamoDBにid, name, version(1.0)を書き込む
    • /namesのパスに対するGETメソッドでクエリパラメータとしてidを受け取り、DynamoDBからidで指定されたキーを検索してnameを応答する
  • 2回目のデプロイ
    • /nameのパスに対するPUTメソッドのDynamoDBに書き込むレコードをversion(2.0)に変更する
    • /namesのパスに対するGETメソッドの応答にversionを追加する

DynamoDBについては、SAM上でAWS::DynamoDB::Tableを定義するという方法もあるが、基本/共通リソースとサービスリソースを分割した方が良かろうということで、先にDynamoDBテーブルを作成する構成にしている。

あと、Lambda関数中でDynamoDBを扱うので、サービスロールにはDynamoDBを触れるようなIAMポリシーをアタッチしておく。

DynamoDBの準備

何でも良いが、↓こんな感じで用意しておく。

01_createDynamlDB.yml
01_createDynamlDB.yml
AWSTemplateFormatVersion: "2010-09-09"
Description:
  Create DynamoDB template for ApigwTest2

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2"
  TableNameSuffix:
    Description: "DynamoDB Table name suffix"
    Type: "String"
    Default: "-Table"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "DynamoDB configuration"
        Parameters:
          - TableNameSuffix

Resources:
  # ------------------------------------------------------------#
  #  DynamoDB
  # ------------------------------------------------------------#
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties: 
      TableName: !Sub ${Prefix}${TableNameSuffix}
      KeySchema: 
        - AttributeName: id
          KeyType: HASH
      AttributeDefinitions: 
        - AttributeName: id
          AttributeType: S
      ProvisionedThroughput: 
        ReadCapacityUnits: 1
        WriteCapacityUnits: 1
      Tags: 
        - Key: Name
          Value: !Sub ${Prefix}

イベントソースで対応する

SAMテンプレート中のAWS::Serverless::Functionの中でEvents指定するのが一番お手軽なので試してみる。

SetName.yml
SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-SetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix

Globals:
    Function:
        Timeout: 60

Resources:
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Method: put
            Path: /name
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name']})
            except ClientError as e:
              if e.response['Error']['Message'] == 'The provided key element does not match the schema':
                response_statuscode = 404
              else:
                pprint.pprint(e.response['Error']['Message'])
                response_statuscode = 500
            else:
              response_statuscode = 200

            return {
                'statusCode': response_statuscode,
                'isBase64Encoded': 'false'
            }

ポイントは以下の部分。

      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Method: put
            Path: /name

これでAPIGatewayをイベントソースとして、以下の様に取り込むことができる。
キャプチャ1.PNG

だがしかし、この方法だと、APIGatewayの名前が勝手に決まってしまう。
※しかも、割と良い感じの名前になっているが、どうやって抽出しているのだろうか……
これでは、複数のLambdaを1つのAPIGatewayに統合できないので、別の手段が必要だ。

もう少し細かい話は以下のサイトに書いてある。
結局、公式に「変更はできない」と書いてある。ついでに、ステージはProdが適用されるからね!とも書いてあるように見える。
CloudFormation Resources Generated By SAM

たしかに、このSAMテンプレートをCloudFormationに流し込むと、見覚えのないリソースを勝手に作っていた。なるほどこういう仕組みだったのか。
キャプチャ2.PNG

ちゃんとRESTAPIのリソースを定義する

自動作成されるリソースなんぞには任せていられん!
ということで、以下の様にSAMテンプレートを修正する。

リソースを手動作成する場合、作ったAPIGatewayに対するLambdaのパーミッションを設定しなければいけない。

あとは、API GatewayとLambdaの紐付けをSwaggerで定義してあげないといけないようだ。
うーむ、なかなか面倒だ……。
SwaggerはAWS拡張形式で書く必要があるx-amazon-apigateway-integrationについては、ちゃんと公式のドキュメントを読もう。PUTだとかGETを扱うからといって、プロキシのメソッドまで変えてしまうと動かなくなる。

SetName.yml
SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-GetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"
  APIGatewayName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "ApigwTest2"
  APIGatewayStageName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "default"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix
      - Label:
          default: "API Gateway Configuration"
        Parameters:
          - APIGatewayName
          - APIGatewayStageName

Globals:
    Function:
        Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  API Gateway
  # ------------------------------------------------------------#
  APIGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub ${APIGatewayName}
      StageName: !Sub ${APIGatewayStageName}
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "Created by SAM template"
          version: "1.0.0"
          title: "ApiGatewayTest2"
        basePath: "/default"
        schemes:
          - "https"
        paths:
          /names:
            get:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
        definitions:
          Empty:
            type: "object"
            title: "Empty Schema"
      Tags: 
        Key: Name
        Value: !Sub ${APIGatewayName}

  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Path: /names
            Method: get
            RestApiId: !Ref APIGateway
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.get_item(Key={'id': event['queryStringParameters']['id']})
            except ClientError as e:
              response_statuscode = 500
              pprint.pprint(e.response['Error']['Message'])
            else:
              if 'Item' not in response:
                response_statuscode = 404
                response_body = json.dumps({'name': 'null'})
              else:
                response_statuscode = 200
                response_body =json.dumps({'name': response['Item']['name']})

            return {
                'statusCode': response_statuscode,
                'body': response_body,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunction
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

だが、これで任意の名前でAPI Gatewayを作成することができるようになった。
でも、なぜかこれ、指定したステージ名じゃなくて勝手にStageとかいうのまで作ってしまうんだよなぁ……バグなのか??
キャプチャ3.PNG

で、ここからが本題。
別のLambda関数のSAMテンプレートから、上手くこのAPI Gatewayを更新することができるだろうか。

これまで作ってきたDynamoDBにレコードを挿入するAPIに対して、同じAPI Gatewayにレコードを参照するAPIをぶら下げてみよう。

GetName.yml
GetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-GetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"
  APIGatewayName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "ApigwTest2"
  APIGatewayStageName:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "default"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix
      - Label:
          default: "API Gateway Configuration"
        Parameters:
          - APIGatewayName
          - APIGatewayStageName

Globals:
    Function:
        Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  API Gateway
  # ------------------------------------------------------------#
  APIGateway:
    Type: AWS::Serverless::Api
    Properties:
      Name: !Sub ${APIGatewayName}
      StageName: !Sub ${APIGatewayStageName}
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "Created by SAM template"
          version: "1.0.0"
          title: "ApiGatewayTest2"
        basePath: "/default"
        schemes:
          - "https"
        paths:
          /names:
            get:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri:
                  !Sub arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${LambdaFunction.Arn}/invocations
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
        definitions:
          Empty:
            type: "object"
            title: "Empty Schema"
      Tags: 
        Key: Name
        Value: !Sub ${APIGatewayName}

  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      Events:
        ApigwTest2:
          Type: Api
          Properties:
            Path: /names
            Method: get
            RestApiId: !Ref APIGateway
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.get_item(Key={'id': event['queryStringParameters']['id']})
            except ClientError as e:
              response_statuscode = 500
              pprint.pprint(e.response['Error']['Message'])
            else:
              if 'Item' not in response:
                response_statuscode = 404
                response_body = json.dumps({'name': 'null'})
              else:
                response_statuscode = 200
                response_body =json.dumps({'name': response['Item']['name']})

            return {
                'statusCode': response_statuscode,
                'body': response_body,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunction
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

さて、これでCloudFormationを実行してみると、
キャプチャ4.PNG

残念なことに、同じAPI名が二つ作られてしまった。ガーン……。

APIGatewayのリソースを分離したSAMテンプレートにする

結論としては、この方法でやりたいことを実現できた。

以下の構成とする。
- GetName.yml … Lambda関数①と、Lambda関数へのアクセス許可を定義
- SetName.yml … Lambda関数②と、Lambda関数へのアクセス許可を定義
- APIGateway.yml … Lambda関数①②にアクセスするAPI Gateway(REST API)の定義
たぶん、API Gateway.ymlはCloudFormationテンプレートでもTerraformのHCLでも作ることは可能だが、色々と面倒な手間を省くことを考えると、ここもSAMにしてしまった方が手っ取り早いと思う。

Lambda関数の定義

以下の様にSAMテンプレートを定義する。

テンプレート中のDeploymentPreferenceプロパティで指定しているCodeDeploy.LambdaCanaryHalf3minutesはデフォルトには無い。検証のために今回作成しているので、ここは適当なものを作っておく。

GetName.yml
GetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-GetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix

Globals:
  Function:
    Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Description: Created by SAM template
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      AutoPublishAlias: Prod
      DeploymentPreference: 
        Type: CodeDeploy.LambdaCanaryHalf3minutes
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.get_item(Key={'id': event['queryStringParameters']['id']})
            except ClientError as e:
              response_statuscode = 500
              pprint.pprint(e.response['Error']['Message'])
            else:
              if 'Item' not in response:
                response_statuscode = 404
                response_body = json.dumps({'name': 'null'})
              else:
                response_statuscode = 200
                response_body =json.dumps({'name': response['Item']['name']})

            return {
                'statusCode': response_statuscode,
                'body': response_body,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunctionAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

Outputs:
  LambdaFunction:
    Description: ApigwTest2 GetName Lambda function ARN
    Value: !Ref LambdaFunctionAliasProd
    Export:
      Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn

SetName.yml
SetName.yml
AWSTemplateFormatVersion: 2010-09-09
Transform: AWS::Serverless-2016-10-31
Description: API Gateway and Lambda Test

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2-SetName"
  LambdaFunctionNameSuffix:
    Description: "Lambda function name suffix"
    Type: "String"
    Default: "-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "Lambda Configuration"
        Parameters:
          - LambdaFunctionNameSuffix

Globals:
  Function:
    Timeout: 60

Resources:
  # ------------------------------------------------------------#
  #  Lambda Function
  # ------------------------------------------------------------#
  LambdaFunction:
    Type: AWS::Serverless::Function
    Properties:
      FunctionName: !Sub ${Prefix}${LambdaFunctionNameSuffix}
      Description: Created by SAM template
      Handler: index.lambda_handler
      Runtime: python3.7
      MemorySize: 128
      Role: !Sub arn:aws:iam::${AWS::AccountId}:role/lambda-test
      AutoPublishAlias: Prod
      DeploymentPreference: 
        Type: CodeDeploy.LambdaCanaryHalf3minutes
      InlineCode: |
        import json
        import pprint
        import boto3
        from botocore.exceptions import ClientError

        def lambda_handler(event, context):
            dynamodb = boto3.resource('dynamodb')
            table = dynamodb.Table('ApigwTest2-Table')

            try:
              response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '1.0'})
            except ClientError as e:
              if e.response['Error']['Message'] == 'The provided key element does not match the schema':
                response_statuscode = 404
              else:
                pprint.pprint(e.response['Error']['Message'])
                response_statuscode = 500
            else:
              response_statuscode = 200

            return {
                'statusCode': response_statuscode,
                'isBase64Encoded': 'false'
            }
  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunctionAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

Outputs:
  LambdaFunction:
    Description: ApigwTest2 SetName Lambda function ARN
    Value: !Ref LambdaFunctionAliasProd
    Export:
      Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn

ポイントは

  LambdaInvokePermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName: !Ref LambdaFunctionAliasProd
      Action: 'lambda:InvokeFunction'
      Principal: apigateway.amazonaws.com

の部分。SAMテンプレートでは、AWS::Serverless::FunctionEventプロパティについて、中で良い感じにAPI Gatewayのリソースを作ってアクセス許可も与えるように動いてくれる(というのがTransform: AWS::Serverless-2016-10-31の正体。こういう便利なことをやってくれる反面、本質的な動作が分かりにくくなる……)が、今回はAPI Gatewayのリソースを切り出しているので、自力でアクセス許可を与える必要がある。

参照しているLambdaFunctionAliasProdというリソースは自分では定義していないが、これもSAMテンプレートの変換の中で良い感じに動いてくれる部分。詳細は以前この記事に書いた。

また、API Gateway側で作ったLambda関数のARNが必要になるため、

Outputs:
  LambdaFunction:
    Description: ApigwTest2 GetName Lambda function ARN
    Value: !Ref LambdaFunctionAliasProd
    Export:
      Name: !Sub ${Prefix}${LambdaFunctionNameSuffix}-Arn

で参照可能な状態にしておく。

実行は、

$ aws cloudformation deploy --template-file GetName.yml --stack-name ApigwTest2-GetName --capabilities CAPABILITY_IAM
$ aws cloudformation deploy --template-file SetName.yml --stack-name ApigwTest2-SetName --capabilities CAPABILITY_IAM

な感じで行う。アクセス許可を変更するために、--capabilities CAPABILITY_IAMが必要。

API Gateway(REST API)の定義

たいして難しいわけではない。
x-amazon-apigateway-integrationで、前節のLambda関数のARNを表現するのが少し面倒なくらい。

APIGateway.yml
APIGateway.yml
AWSTemplateFormatVersion: "2010-09-09"
Transform: AWS::Serverless-2016-10-31
Description:
  Create APIGateway template for ApigwTest2

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "ApigwTest2"
  RESTApiNameSuffix:
    Description: "REST API name suffix"
    Type: "String"
    Default: "-RESTAPI"
  RESTApiStageName:
    Description: "REST API stage name suffix"
    Type: "String"
    Default: "default"
  GetLambdaFunctionNameSuffix:
    Description: "Lambda Function name suffix"
    Type: "String"
    Default: "-GetName-LambdaFunction"
  SetLambdaFunctionNameSuffix:
    Description: "Lambda Function name suffix"
    Type: "String"
    Default: "-SetName-LambdaFunction"

Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "API Gateway configuration"
        Parameters:
          - RESTApiNameSuffix
          - RESTApiStageName
          - GetLambdaFunctionNameSuffix
          - SetLambdaFunctionNameSuffix

Resources:
  # ------------------------------------------------------------#
  #  API Gateway
  # ------------------------------------------------------------#
  APIGATEWAY:
    Type: AWS::Serverless::Api
    Properties: 
      Name: !Sub ${Prefix}${RESTApiNameSuffix}
      StageName: !Sub ${RESTApiStageName}
      DefinitionBody:
        swagger: "2.0"
        info:
          description: "Created by CloudFormation template"
          version: "1.0.0"
          title: "ApiGatewayTest2"
        basePath: "/default"
        schemes:
          - "https"
        paths:
          /names:
            get:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri: !Sub
                  - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${GetLambdaFunctionArn}/invocations
                  - GetLambdaFunctionArn: {'Fn::ImportValue': !Sub '${Prefix}${GetLambdaFunctionNameSuffix}-Arn'}
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
          /name:
            put:
              produces:
              - "application/json"
              responses:
                "200":
                  description: "200 response"
                  schema:
                    $ref: "#/definitions/Empty"
              x-amazon-apigateway-integration:
                uri: !Sub
                  - arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${SetLambdaFunctionArn}/invocations
                  - SetLambdaFunctionArn: {'Fn::ImportValue': !Sub '${Prefix}${SetLambdaFunctionNameSuffix}-Arn'}
                passthroughBehavior: when_no_templates
                httpMethod: POST
                type: aws_proxy
        definitions:
          Empty:
            type: "object"
            title: "Empty Schema"
      EndpointConfiguration: REGIONAL
      Tags: 
        Key: Name
        Value: !Sub ${Prefix}

これで

$ aws cloudformation deploy --stack-name ApigwTest2-APIGateway --template-file APIGateway.yml

すると、以下のように複数のパス・メソッドを定義したAPI Gatewayを作れる。

キャプチャ5.PNG

キャプチャ6.PNG

キャプチャ7.PNG

アプリケーションのデプロイを行ってみる

例えば、以下のような感じでアプリケーションを修正する。

GetName.ymlの修正内容
65c64
<                 response_body = json.dumps({'name': 'null'})
---
>                 response_body = json.dumps({'name': 'null', 'version': 'null'})
68c67
<                 response_body =json.dumps({'name': response['Item']['name']})
---
>                 response_body =json.dumps({'name': response['Item']['name'], 'version': response['Item']['version']})
SetName.ymlの修正内容
58c58
<               response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '1.0'})
---
>               response = table.put_item(Item={'id': event['queryStringParameters']['id'], 'name': event['queryStringParameters']['name'], 'version': '2.0'})

それぞれ、もう一度 aws cloudformation deployで確認してみると、ちゃんとCanaryなリリースが行われているし、それぞれの機能やデプロイにも干渉しなかった。

SAMテンプレートで完結しないもやっと感はあるが、アプリケーションの大幅改修が無い限りは、API GatewayのREST API定義とのライフサイクルが異なる気がするので、これで特に大きな問題にはならないだろう。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0