CloudFormationのネストされたスタックでAPI Gateway、Lambda、DynamoDBをデプロイする(YAML)

もくじ

はじめに

YAMLでCloudFormationテンプレートを作成しました。API GatewayとLambdaとDynamoDBです。

DynamoDBのテーブルをCloudFormationでデプロイする(YAML)

Lambda関数をCloudFormationでデプロイする(YAML)。コードベタ書きとS3からのダウンロード

API GatewayをCloudFormationでデプロイしてLambda関数を実行(YAML)

やること

今回やることは大きくわけて3つです。

  1. LambdaスタックからLambda関数名をエクスポート
  2. API Gatewayスタックで、Lambda関数名をインポート
  3. DynamoDBスタック、Lambdaスタック、API Gatewayスタックを一括でデプロイ

Lambdaスタックから関数名をエクスポート

YAMLコード

---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for Lambda"

Parameters:
  NameOfFunction:
    Type: String
    Description: "Name of Lambda Function"

Resources:
  LambdaFunction:
    Type: AWS::Lambda::Function
    Properties:
      FunctionName: !Ref NameOfFunction
      Description: "Function that put items to DynamoDB"
      Runtime: "python3.9"
      MemorySize: "128"
      Timeout: "5"
      Role: "arn:aws:iam::738452829225:role/service-role/putDynamoFunc-role-l6trp0uq"
      Handler: index.lambda_handler
      Architectures: [ "x86_64" ]
      EphemeralStorage:
        Size: "512"
      Code:
        S3Bucket: "bucket-atsushi-00"
        S3Key: "python/package.zip"
### 追記 ###
Outputs:
  NameOfLambdaFunc:
    Description: "Name of Lambda Function"
    Value: !Ref LambdaFunction
    Export: 
      Name: "ExportFunctionName"

コード説明

「### 追記 ###」の下が、前回の記事から追記した箇所になります。

Outputs:

Outputs」を使うことにより、テンプレート内で取得した値を別のスタックに出力できます。

そして別のスタックから、その値をインポートすることができます。

Value」を先に説明すると、エクスポートするです。ここでは「LambdaFunction」という論理IDを指定しています。結果として、Lambda関数の名前が出力されます。どのような値が出力されるかは、都度確認する必要があると思います。

Export」の中に「Name」を記述して、どのような名前でエクスポートするか指定します。ここで指定したものはインポート時に指定するものになります。

ここでは「ExportFunctionName」にしました。この名前でAPI Gatewayスタックからインポートします。

API GatewayスタックでLambda関数名をインポート

YAMLコード

---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for API Gateway"

Parameters:
  NameOfAPIGateway:
    Type: String
    Description: "Name of API Gateway"

Resources:
  ApiGwRestAPI:
    Type: AWS::ApiGateway::RestApi
    Properties:
      Description: "This is the Description of ApiGwRestAPI"
      Name: !Ref NameOfAPIGateway
      EndpointConfiguration:
        Types: 
          - REGIONAL
  ApiGwDeployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      Description: "This is the Description of ApiGwDeployment"
      RestApiId: !Ref ApiGwRestAPI
    DependsOn:
      - ApiGwResource
      - ApiGwMethod
  ApiGwStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      RestApiId: !Ref ApiGwRestAPI
      DeploymentId: !Ref ApiGwDeployment
      StageName: "v1"
  ApiGwResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      ParentId: !GetAtt ApiGwRestAPI.RootResourceId
      PathPart: "testPath"
      RestApiId: !Ref ApiGwRestAPI
  ApiGwMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      HttpMethod: "POST"
      ResourceId: !Ref ApiGwResource
      RestApiId: !Ref ApiGwRestAPI
      AuthorizationType: NONE
      Integration:
        Type: "AWS"
        IntegrationHttpMethod: "POST"
        # 変更前→ Uri: "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:putDynamoFunc/invocations"
        Uri: !Sub
          - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${FuncName}/invocations"
          - FuncName: {'Fn::ImportValue': !Sub 'ExportFunctionName'}
        IntegrationResponses:
          - StatusCode: "200"
      MethodResponses:
        - StatusCode: "200"
          ResponseModels:
           application/json: Empty
    DependsOn: "LambdaPermission"
  LambdaPermission:
      Type: "AWS::Lambda::Permission"
      Properties:
        # 変更前→ FunctionName: "putDynamoFunc"
        FunctionName: !ImportValue "ExportFunctionName"
        Action: "lambda:InvokeFunction"
        Principal: "apigateway.amazonaws.com"

コード解説

コメントアウトした箇所のすぐ下が変更した箇所です。

Integration

Uri: "arn:aws:apigateway:ap-northeast-1:lambda:path/2015-03-31/functions/arn:aws:lambda:ap-northeast-1:XXXXXXXXXXXX:function:putDynamoFunc/invocations"

これについては、Fn::Subに記載されている以下のかたちで文字列を完成させています。

!Sub
  - String
  - Var1Name: Var1Value
    Var2Name: Var2Value

変更箇所を抜粋すると、

Uri: !Sub
  - "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${FuncName}/invocations"
  - FuncName: {'Fn::ImportValue': !Sub 'Stack-Lambda-FunctionName'}

AWSリージョンは「${AWS::Region}」で取得し、AWSアカウントの番号は「${AWS::AccountId}」で取得して組み込んでいます。

そして、Lambdaスタックからインポートした関数名は、「${FuncName}」という変数で組み込んでいます。

肝心な「${FuncName}」の値は、「{‘Fn::ImportValue’: !Sub ‘Stack-Lambda-FunctionName’}」で取得しています。

ImportValue関数を使って、エクスポートされたName(LambdaスタックのNameで指定した値)を指定して取得することができます。

LambdaPermission

「LambdaPermission」の変更箇所はもう少しシンプルです。

FunctionName: "putDynamoFunc"

!ImportValue "ExportFunctionName"

に変更しました。

「putDynamoFunc」という関数名をベタ書きしてましたが、「!ImportValue “ExportFunctionName”」とすることで、ImportValue関数でLambdaスタックから関数名をインポートしています。

一括デプロイの準備

S3へテンプレートをアップロード

先ほどのLambdaスタック(Lambda.yml)とAPI Gatewayスタック(APIGateway.yml)、そしてDynamoDBスタック(DynamoDB.yml)をS3へアップロードします。

ちなみにDynamoDBスタックのテンプレート(DynamoDB.yml)は、前回の記事から変更していないです。念のため下記に記載しておきます。

(補足)DynamoDBのYAMLコード

---
AWSTemplateFormatVersion: 2010-09-09
Description: "Code for DynamoDB"

Parameters:
  TableName:
    Type: String
    Description: "Name of DynamoDB Table"

Resources:
  DynamoDBTable:
    Type: AWS::DynamoDB::Table
    Properties:
      AttributeDefinitions:
        - AttributeName: "Email"
          AttributeType: "S"
        - AttributeName: "Name"
          AttributeType: "S"
        - AttributeName: "Gender"
          AttributeType: "S"
        - AttributeName: "Inquiry"
          AttributeType: "S"
      KeySchema:
        - AttributeName: "Email"
          KeyType: "HASH"
        - AttributeName: "Name"
          KeyType: "RANGE"
      ProvisionedThroughput:
        ReadCapacityUnits: "3"
        WriteCapacityUnits: "3"
      GlobalSecondaryIndexes:
        - IndexName: "GSI-Name"
          KeySchema:
            - AttributeName: "Name"
              KeyType: "HASH"
            - AttributeName: "Inquiry"
              KeyType: "RANGE"
          Projection:
            NonKeyAttributes:
              - "Email"
            ProjectionType: "INCLUDE"
          ProvisionedThroughput:
            ReadCapacityUnits: "1"
            WriteCapacityUnits: "1"
        - IndexName: "GSI-Gender"
          KeySchema:
            - AttributeName: "Gender"
              KeyType: "HASH"
            - AttributeName: "Inquiry"
              KeyType: "RANGE"
          Projection:
            NonKeyAttributes:
              - "Email"
            ProjectionType: "INCLUDE"
          ProvisionedThroughput:
            ReadCapacityUnits: "1"
            WriteCapacityUnits: "1"
      TableName: !Ref TableName

一括デプロイ

YAMLコード

これらの3つのテンプレートを実行するYAMLコードが下記です。ここでは「All.yml」としました。

---
AWSTemplateFormatVersion: 2010-09-09
Description: "IaC for DynamoDB、Lambda Function、API Gateway"

Parameters:
  TbName:
    Type: String
    Description: "Name of DynamoDB Table"
  FuncName:
    Type: String
    Description: "Name of Lambda Function"
  ApiName:
    Type: String
    Description: "Name of API Gateway"

Resources:
  StackDynamoDB:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: "https://bucket-atsushi-00.s3.ap-northeast-1.amazonaws.com/CloudFormation-Templates/DynamoDB.yml"
      Parameters:
        TableName: !Ref TbName
  StackLambdaFunc:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: "https://bucket-atsushi-00.s3.ap-northeast-1.amazonaws.com/CloudFormation-Templates/Lambda.yml"
      Parameters:
        NameOfFunction: !Ref FuncName
    DependsOn:
      - StackDynamoDB
  StackAPIGateway:
    Type: AWS::CloudFormation::Stack
    Properties:
      TemplateURL: "https://bucket-atsushi-00.s3.ap-northeast-1.amazonaws.com/CloudFormation-Templates/APIGateway.yml"
      Parameters:
        NameOfAPIGateway: !Ref ApiName
    DependsOn:
      - StackDynamoDB
      - StackLambdaFunc

コード解説

Parameters

スタックを実行(作成)するときにユーザーが指定する値です。

DynamoDB.ymlではテーブル名、Lambda.ymlでは関数名、APIGateway.ymlではAPI Gatewayの名前を入力するようにテンプレートを作っています。ですので「All.yml」でデプロイ実行時すべて手入力させるように、Parametersプロパティで記述しています。

AWS::CloudFormation::Stack

「AWS::CloudFormation::Stack」を使うことで、テンプレートからテンプレートを呼び出せます。つまり、「All.yml」から「DynamoDB.yml」と「Lambda.yml」と「APIGateway.yml」を呼び出せます。

AWS::CloudFormation::Stack

「TemplateURL」に各テンプレートが保存されているS3のURLを記述しています。

「Parameters」には、呼び出すテンプレート渡したい値を記述します。

  • 「DymanoDB.yml」にはテーブル名(「All.yml」の中の”TbName”変数)を渡しています。
  • 「Lambda.yml」には関数名(「All.yml」の中の”FuncName”変数)を渡しています。
  • 「APIGateway.yml」にはAPI Gatewayの名前(「All.yml」の中”ApiName”変数)を渡しています。

DependsOn

読み込まれるテンプレートの順番は、「DynamoDB.yml」→「Lambda.yml」→「APIGateway.yml」となります。

なので、LambdaスタックはDynamoDBスタックに依存しています。そして、API Gatewayスタックは、DynamoDBスタックとLambdaスタックに依存しています。

一括デプロイ実行

一括デプロイするコマンドは以下のとおりです。

aws cloudformation create-stack \
--template-body file:///Users/atsushi/Document/AWS/CloudFormation/All.yml \
--stack-name Stack-All \
--parameters ParameterKey="TbName",ParameterValue="inquiry-table" \
ParameterKey="FuncName",ParameterValue="putDynamoFunc" \
ParameterKey="ApiName",ParameterValue="apiInvokeLambda"

実行時の画面ショットはこんな感じです。呼び出されるスタックには自動的にスタック名が付けられています。

テスト

一括デプロイで作成されたAPI Gatewayから、ペイロードを与えてテスト実行してみます。

以下のようなペイロードを指定してテストしました。200ステータスが返ってきています。

一括デプロイで作成されたDynamoDBを見てみると、テーブルに値が入っています。

最後に

ネストされたスタックの実行をするために、「AWS::CloudFormation::Stack」をコード上で使うこと。

テンプレートから値をエクスポートするには、「Outputs」プロパティを使用すること。

テンプレートから値をインポートするには「!ImportValue」関数を使うこと。

あと、YAMLコード内で値を参照するときに、「!Sub」を使うことを少しやりました。

最後までお読みいただき、ありがとうございました。

参考サイト

CloudFormation テンプレートが肥大化したので分割する方法を教えてください

AWS CloudFormationで他のスタックからImportValueした文字列をSub関数で結合する

Fn::Sub

擬似パラメータ参照

CloudFormation の参照周りで意識すべきポイント・Tips

この記事が気に入ったら
フォローしてね!

よかったらシェアしてね!

コメント

コメントする

コメントは日本語で入力してください。(スパム対策)

CAPTCHA

もくじ
閉じる