AWS CloudformationでAPI Gateway + Lambdaをデプロイするテンプレートを作る

AWS CloudFormationを利用してAPI GatewayとLambdaの基本的な構成をデプロイするためのテンプレートを作成します。

最近映画の「スパイダーバース」観ました。スパイダーマン映画で2番目に好きになりました。

1番はアメスパ2です(※個人の感想です)。

 

今回は初めて触るAWS CloudFormationで、よくある構成のAPI GatewayとLambdaの構築をしてみたいと思います。

構成は以下のようになります。

LambdaのソースはS3に保存しておき、CloudFormationのスタックの作成開始時に参照します。

Amazon Connectを使うことが多いので、とりあえずLambdaはAmazon ConnectのAPIを実行するものを作成します。

目次

AWS CloudFormationとは

まずはCloudFormationについて簡単に。

AWS CloudFormationは、AWS上に構築するリソースや構成をコードによって管理するためのサービスです。

JSONまたはYAML形式でCloudFormationテンプレートを記述し、AWSコンソールなどからアップロードすることでCloudFormationスタック(テンプレートによりプロビジョニングされたリソース)を作成します。

CloudFormationテンプレートでインフラのコード化(IaC)をすることにより、インフラの管理、複製における手間や人的ミスの削減が期待できます。

それでは、導入で概要を説明した構成を実際にCloudFormationで構築していきます。

Lambda関数とテンプレートの作成

Lambda関数の作成

まずは普通にLambda関数を作成します。

内容は、特定のAmazon Connectインスタンスのユーザーリストを取得して、それを返すだけのものです。

const AWS = require('aws-sdk');
const connect = new AWS.Connect({region:'ap-northeast-1'});

exports.handler = async (event) => {
    let param = {
        InstanceId : process.env.INSTANCE_ID,
        MaxResults : 100
    };

    try{
        let result = await connect.listUsers(param).promise();
        console.log(result);
        return {
            statusCode: 200,
            headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "OPTIONS,POST,GET"
            },
            body: JSON.stringify(result)
        };
    }catch(e){
        return{
            statusCode: 500,
                headers: {
                "Access-Control-Allow-Headers" : "Content-Type",
                "Access-Control-Allow-Origin": "*",
                "Access-Control-Allow-Methods": "OPTIONS,POST,GET"
            },
            body: JSON.stringify(e)
        };
    }
};

このLambda関数はZIP化して任意のS3バケットに保存します。

テンプレートの作成

では、上で作成したLambdaソースをS3から取得し、Lambda関数を作成するCloudFormationのテンプレートを作成します。

AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  FunctionName:
    Type: String
    Description: "get_amazonconnect_users"
Resources:
  # Lambda
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code: 
        S3Bucket: "# S3バケット名"
        S3Key: !Sub "# S3キー名(ZIPファイル名)"
      Description: "Lambda function to list amazon connect users"
      FunctionName: !Sub "${FunctionName}"
      Handler: index.handler
      Runtime: nodejs14.x
      MemorySize: 128
      Timeout: 10
      Role: '# Lambdaに付与するロールARN'
      Environment:
        Variables:
          INSTANCE_ID: "# Amazon ConnectインスタンスID"

以下がキーになります。

  • 関数名はFunctionNameとしてパラメータに定義して、テンプレート内で使いまわせるようにします。後で書くAPI Gatewayのテンプレートでもこの関数名を使いまわします。
  • 関数内で使用する環境変数もテンプレート内で定義します。
  • 今回の場合Lambdaに付与するロールは事前に作成しておきます(ロールの作成は今回のスコープ外なので手作業で作成しました)。通常のLambda実行ポリシーにAmazonConnectReadOnlyAccessを追加しています。

一旦この状態でCloudFormationのスタックを作成してリソースが作成されることを確認しました。

API Gatewayのテンプレート作成

続いてAPI Gatewayのテンプレートを作成します。

今回作成するAPI Gatewayの概要は以下となります。

  1. リソースは1つ。メソッドはGETのみ。
  2. 1のGETメソッドはLambda ProxyによりLambdaを実行し、そのレスポンスをそのまま返す。
  3. ステージ名はdev。
AWSTemplateFormatVersion: '2010-09-09'
Parameters:
  FunctionName:
    Type: String
    Description: "get_amazonconnect_users"
Resources:
  # API Gateway
  Api:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: "api_cfn_test"

  Resource:
    Type: "AWS::ApiGateway::Resource"
    Properties:
      RestApiId: !Ref Api
      ParentId: !GetAtt Api.RootResourceId
      PathPart: !Sub "${FunctionName}"
    DependsOn:
      - Api 

  LambdaPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      FunctionName: !Sub "${FunctionName}"
      Action: "lambda:InvokeFunction"
      Principal: "apigateway.amazonaws.com"
    DependsOn: Resource 

  ResourceMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      RestApiId: !Ref Api
      ResourceId: !Ref Resource
      AuthorizationType: "None"
      HttpMethod: "GET"
      Integration:
        Type: "AWS_PROXY"
        IntegrationHttpMethod: "POST"
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${FunctionName}/invocations"
    DependsOn: LambdaPermission

  Deployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref Api
    DependsOn: ResourceMethod

  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: dev
      Description: dev stage
      RestApiId: !Ref Api
      DeploymentId: !Ref Deployment
    DependsOn: Deployment

以下がキーになります。

  • Lambda Proxyを使用する場合、APIのメソッドはGETやDELETEであろうと、IntegrationHttpMethodにはPOSTを指定する

Lambda 統合では、関数呼び出しの Lambda サービスアクションの仕様に従って、統合リクエストに POST の HTTP メソッドを使用する必要があります。

AWS CLI を使用して Lambda プロキシ統合をセットアップする

  • リソースの作成順によってはまだ作成されていないリソースを参照しようとしてエラーになるのでDependsOnで依存関係を記述する

 

テンプレートをまとめる

最終的にCloudFormationのテンプレートは以下のようになりました。

AWSTemplateFormatVersion: '2010-09-09'
Description: "Cloudformation template for API Gateway & Lambda."
Parameters:
  FunctionName:
    Type: String
    Description: "get_amazonconnect_users"
Resources:
  # Lambda
  Lambda:
    Type: 'AWS::Lambda::Function'
    Properties:
      Code: 
        S3Bucket: "# S3バケット名"
        S3Key: !Sub "# S3キー名(ZIPファイル名)"
      Description: "Lambda function to list amazon connect users"
      FunctionName: !Sub "${FunctionName}"
      Handler: index.handler
      Runtime: nodejs14.x
      MemorySize: 128
      Timeout: 10
      Role: '# Lambdaに付与するロールARN'
      Environment:
        Variables:
          INSTANCE_ID: "# Amazon ConnectインスタンスID"
  
  # API Gateway
  Api:
    Type: "AWS::ApiGateway::RestApi"
    Properties:
      Name: "api_cfn_test"

  Resource:
    Type: "AWS::ApiGateway::Resource"
    Properties:
      RestApiId: !Ref Api
      ParentId: !GetAtt Api.RootResourceId
      PathPart: !Sub "${FunctionName}"
    DependsOn:
      - Api 
      - Lambda

  LambdaPermission:
    Type: "AWS::Lambda::Permission"
    Properties:
      FunctionName: !Sub "${FunctionName}"
      Action: "lambda:InvokeFunction"
      Principal: "apigateway.amazonaws.com"
    DependsOn: Resource 

  ResourceMethod:
    Type: "AWS::ApiGateway::Method"
    Properties:
      RestApiId: !Ref Api
      ResourceId: !Ref Resource
      AuthorizationType: "None"
      HttpMethod: "GET"
      Integration:
        Type: "AWS_PROXY"
        IntegrationHttpMethod: "POST"
        Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${FunctionName}/invocations"
    DependsOn: LambdaPermission

  Deployment:
    Type: AWS::ApiGateway::Deployment
    Properties:
      RestApiId: !Ref Api
    DependsOn: ResourceMethod

  Stage:
    Type: AWS::ApiGateway::Stage
    Properties:
      StageName: dev
      Description: dev stage
      RestApiId: !Ref Api
      DeploymentId: !Ref Deployment
    DependsOn: Deployment

 

また、上記テンプレートでCloudFormationのスタックを作成すると、無事各リソースの作成を行うことができました。

 

初歩の初歩ではありますが、これでIaC(Infrastructure as Code)への第一歩が踏み出せました。

構成としては単純なAPI GatewayとLambdaですが、テンプレートにするとやっぱりそこそこ行数があります。

本格的にやろうとすると依存関係などが複雑になりそうです。今回作成しているテンプレートは依存関係を書きすぎているかもしれません。

まとめ

今回API Gateway + Lambdaの構成をCloudformationのテンプレートで作成しました。思ったこととしては、

  • API Gatewayの場合はOpenAPI(Swagger)ファイルを読み込むことができるようなので、ファイルを分割できる、Swaggerツールが使える、などの点からそちらの方法を取りたい
  • LambdaのデプロイはCI/CDまで考慮して、AWS SAM(CloudFormationのServerless Application向け拡張)とCode PipeLineで構築するようにしたほうが良さそう
  • AWSのIaCといえば他にもterraformがあるので、CloudFormationとの違いを実感するためにも試してみたい

と、いろいろ試したいこともあるので、忘れないうちにやりたいです。

この記事が気に入ったら
いいね ! しよう

Twitter で

【採用情報】一緒に働く仲間を募集しています

採用情報
ページトップへ