2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

AWS CloudFormationでLambdaを使用するAPI Gatewayを構築しよう

Last updated at Posted at 2021-05-08

はじめに

AWS CloudFormationを利用してLambdaを使用するAPI Gateway構築のテンプレートのサンプルです。
Lambdaは、S3アーティファクトを使用します。

テンプレートの概要が分からない場合は、はじめてのAWS CloudFormationテンプレートを理解するを参考にしてください。

コードはGitHubにもあります。

今回は、akane というシステムの dev 環境を想定しています。
同じ構成で違う環境を作成する場合は、{環境名}-parameters.jsonを別途作成します。

ディレクトリ構成
akane (システム)
  ├── apigw (スタック)
  │   ├── apigw.yml (CFnテンプレート)
  │   └── dev-parameters.json (dev 環境のパラメータ)
  ├── lambda (スタック)
  │   ├── code
  │   │   └── getTiAmo.py (S3アーティファクトソース)
  │   ├── code-lambda-getTiAmo.zip (S3アーティファクト)
  │   ├── delete_artifact.dev.sh (S3アーティファクト削除シェル)
  │   ├── dev-parameters.json (dev 環境のパラメータ)
  │   ├── lambda.yml (CFnテンプレート)
  │   ├── mkzip.sh (S3アーティファクト作成シェル)
  │   └── upload_artifact.dev.sh (S3アーティファクトアップロードシェル)
  └─ s3 (スタック)
      ├─ s3.yml (CFnテンプレート)
      └─ all-parameters.json (all 環境のパラメータ)

AWS リソース構築内容

  1. apigwスタック
    • CloudWatch ログのロール
    • API Gateway
    • 使用量プラン
    • API キー
  2. lambdaスタック
    • Lambdaロール
    • Lambda
  3. s3スタック
    • s3バケット (akane-all-s3-artifacts)
    • バケットポリシー (s3:GetObject)

実行環境の準備

AWS CloudFormationを動かすためのAWS CLIの設定を参考にしてください。

AWS リソース構築手順

  1. 下記を実行してスタックを作成

    ./create_stacks.sh
    
  2. 下記を実行してLambdaの動作を確認

    ./test_lambda.sh
    
  3. 下記を実行してAPI Gatewayの動作を確認

    ./test_apigw.sh <URL> <API_KEY>
    
  4. 下記を実行してスタックを削除

    ./delete_stacks.sh
    

構築テンプレート

1. s3スタック

s3.yml
AWSTemplateFormatVersion: 2010-09-09
Description: S3 For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.

# Mappings

# Conditions

# Transform

Resources:
  # S3 Bucket作成
  akaneS3Bucket:
    Type: AWS::S3::Bucket
    Properties:
      AccessControl: Private
      BucketName: !Sub
        - ${SystemName}-${EnvType}-s3-artifacts
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-s3-artifacts
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  # S3 BucketPolicy作成
  akaneS3BucketPolicy:
    Type: AWS::S3::BucketPolicy
    DependsOn: akaneS3Bucket
    Properties:
      Bucket: !Ref akaneS3Bucket
      PolicyDocument:
        Statement:
          - Action:
              - s3:GetObject
            Effect: Allow
            Resource: !Join
              - ''
              - - 'arn:aws:s3:::'
                - !Ref akaneS3Bucket
                - /*
            Principal:
              AWS: '*'

Outputs:
  akaneS3Bucket:
    Value: !Ref akaneS3Bucket
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-s3-artifacts
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
all-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "all"
        }
    ]
}

2. lambdaスタック

lambda.yml
AWSTemplateFormatVersion: 2010-09-09
Description: Lambda For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  ArtifactS3Bucket:
    Type: String
  LambdaName:
    Type: String
  LambdaMemorySize:
    Type: Number
  LambdaRuntime:
    Type: String
  GirlName:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  # ロール作成
  akaneRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - lambda.amazonaws.com
            Action:
              - sts:AssumeRole
      Description: !Sub
        - ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
      Path: /
      RoleName: !Sub
        - ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-role-lambda-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  # Lambda作成
  akaneLambdaGetTiAmo:
    Type: AWS::Lambda::Function
    Properties:
      Code:
        S3Bucket: !Ref ArtifactS3Bucket
        S3Key: !Sub
          - code-lambda-${LambdaName}.zip
          - {LambdaName: !Ref LambdaName}
      Description: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Environment:
        Variables:
          GIRL_NAME: !Ref GirlName
      FunctionName: !Sub
        - ${SystemName}-${EnvType}-lambda-${LambdaName}
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType, LambdaName: !Ref LambdaName}
      Handler: !Sub
        - ${LambdaName}.handler
        - {LambdaName: !Ref LambdaName}
      MemorySize: !Ref LambdaMemorySize
      Role: !GetAtt akaneRole.Arn
      Runtime: !Ref LambdaRuntime
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-lambda-${LambdaName}
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      Timeout: 900
      TracingConfig:
        Mode: PassThrough

Outputs:
  akaneLambdaGetTiAmoArn:
    Value: !GetAtt akaneLambdaGetTiAmo.Arn
    Export:
      Name: !Sub
        - ${SystemName}-${EnvType}-lambda-arn
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "ArtifactS3Bucket",
            "ParameterValue": "akane-all-s3-artifacts"
        },
        {
            "ParameterKey": "LambdaName",
            "ParameterValue": "getTiAmo"
        },
        {
            "ParameterKey": "LambdaMemorySize",
            "ParameterValue": "128"
        },
        {
            "ParameterKey": "LambdaRuntime",
            "ParameterValue": "python3.8"
        },
        {
            "ParameterKey": "GirlName",
            "ParameterValue": "akane"
        }
    ],
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ]
}

3. apigwスタック

apigw.yml
AWSTemplateFormatVersion: 2010-09-09
Description: API Gateway For Akane

# Metadata:

Parameters:
  SystemName:
    Type: String
    AllowedPattern: '[a-zA-Z0-9-]*'
  EnvType:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  EnvTypeCommon:
    Description: Environment type.
    Type: String
    AllowedValues: [all, dev, stg, prod]
    ConstraintDescription: must specify all, dev, stg, or prod.
  APIPathPart:
    Type: String
  APIGatewayStage:
    Type: String

# Mappings

# Conditions

# Transform

Resources:
  # ApiGateway CloudWatchRole 作成
  akaneCloudWatchRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - apigateway.amazonaws.com
            Action: sts:AssumeRole
      Description: !Sub
        - ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
      ManagedPolicyArns:
        -  arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
      Path: /
      RoleName: !Sub
        - ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
        - {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvTypeCommon}-role-apigw-cloudwatch-${AWS::Region}
          - {SystemName: !Ref SystemName, EnvTypeCommon: !Ref EnvTypeCommon}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvTypeCommon
          Value: !Ref EnvTypeCommon
  akaneApiGatewayAccount:
    Type: AWS::ApiGateway::Account
    Properties:
      CloudWatchRoleArn: !GetAtt akaneCloudWatchRole.Arn

  # ApiGateway 作成
  akaneApiGatewayRestApi:
    Type: AWS::ApiGateway::RestApi
    DependsOn: akaneApiGatewayAccount
    Properties:
      Name: !Sub
        - ${SystemName}-${EnvType}-apigw
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      EndpointConfiguration:
        Types:
          - REGIONAL
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  akaneApiGatewayResource:
    Type: AWS::ApiGateway::Resource
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      ParentId: !GetAtt akaneApiGatewayRestApi.RootResourceId
      PathPart: !Ref APIPathPart
  akaneApiGatewayResourceMethod:
    Type: AWS::ApiGateway::Method
    Properties:
      RestApiId: !Ref akaneApiGatewayRestApi
      ResourceId: !Ref akaneApiGatewayResource
      ApiKeyRequired: true
      AuthorizationType: NONE
      HttpMethod: GET
      Integration:
        Type: AWS_PROXY
        IntegrationHttpMethod: POST
        Uri: !Join
          - ''
          - - 'arn:aws:apigateway:'
            - !Ref AWS::Region
            - ':lambda:path/2015-03-31/functions/'
            - Fn::ImportValue: !Sub
              - ${SystemName}-${EnvType}-lambda-arn
              - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
            - '/invocations'

  # ApiGateway LambdaPermission 作成
  akaneApiGatewayLambdaPermission:
    Type: AWS::Lambda::Permission
    Properties:
      FunctionName:
        Fn::ImportValue: !Sub
          - ${SystemName}-${EnvType}-lambda
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Action: lambda:InvokeFunction
      Principal: apigateway.amazonaws.com

  # ApiGateway Deployment 作成
  akaneApiGatewayDeployment:
    Type: AWS::ApiGateway::Deployment
    DependsOn: akaneApiGatewayResourceMethod
    Properties:
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-deployment
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      RestApiId: !Ref akaneApiGatewayRestApi
  akaneApiGatewayStage:
    Type: AWS::ApiGateway::Stage
    Properties:
      DeploymentId: !Ref akaneApiGatewayDeployment
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-stage
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      MethodSettings:
        - CacheDataEncrypted: false
          CachingEnabled: false
          DataTraceEnabled: true # CloudWatch ログを有効化
          HttpMethod: '*'
          LoggingLevel: ERROR
          MetricsEnabled: false # 詳細 CloudWatch メトリクスを有効化
          ResourcePath: '/*'
      RestApiId: !Ref akaneApiGatewayRestApi
      StageName: !Ref APIGatewayStage
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw-stage
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
      TracingEnabled: false # X-Ray トレースの有効化

  # ApiGateway APIキー 作成
  akaneApiGatewayKey:
    Type: AWS::ApiGateway::ApiKey
    Properties:
      Name: !Sub
        - ${SystemName}-${EnvType}-apigw-key
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-key
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Enabled: true
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw-key
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  akaneApiGatewayUsagePlan:
    Type: AWS::ApiGateway::UsagePlan
    DependsOn: akaneApiGatewayStage
    Properties:
      ApiStages:
        - ApiId: !Ref akaneApiGatewayRestApi
          Stage: !Ref APIGatewayStage
      Description: !Sub
        - ${SystemName}-${EnvType}-apigw-usage-plan
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      UsagePlanName: !Sub
        - ${SystemName}-${EnvType}-apigw-usage-plan
        - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
      Tags:
        - Key: Name
          Value: !Sub
          - ${SystemName}-${EnvType}-apigw-usage-plan
          - {SystemName: !Ref SystemName, EnvType: !Ref EnvType}
        - Key: SystemName
          Value: !Ref SystemName
        - Key: EnvType
          Value: !Ref EnvType
  akaneApiGatewayUsagePlanKey:
    Type: AWS::ApiGateway::UsagePlanKey
    Properties:
      KeyId: !Ref akaneApiGatewayKey
      KeyType: API_KEY
      UsagePlanId: !Ref akaneApiGatewayUsagePlan

dev-parameters.json
{
    "Parameters": [
        {
            "ParameterKey": "SystemName",
            "ParameterValue": "akane"
        },
        {
            "ParameterKey": "EnvType",
            "ParameterValue": "dev"
        },
        {
            "ParameterKey": "EnvTypeCommon",
            "ParameterValue": "all"
        },
        {
            "ParameterKey": "APIPathPart",
            "ParameterValue": "whisper"
        },
        {
            "ParameterKey": "APIGatewayStage",
            "ParameterValue": "v1"
        }
    ],
    "Capabilities": [
        "CAPABILITY_NAMED_IAM"
    ]
}

4. 実行ファイル

create_stacks.sh
#!/bin/sh

cd `dirname $0`

SYSTEM_NAME=akane

create_stack () {
    ENV_TYPE=$1
    STACK_NAME=$2
    aws cloudformation create-stack \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME} \
    --template-body file://./${SYSTEM_NAME}/${STACK_NAME}/${STACK_NAME}.yml \
    --cli-input-json file://./${SYSTEM_NAME}/${STACK_NAME}/${ENV_TYPE}-parameters.json

    aws cloudformation wait stack-create-complete \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}

create_stack all s3
./akane/lambda/mkzip.sh
./akane/lambda/upload_artifact.dev.sh
create_stack dev lambda
create_stack dev apigw

exit 0
test_lambda.sh
#!/bin/sh

cd `dirname $0`

echo " --- \033[0;33m TEST LAMBDA \033[0;39m --- "

LAMBDA_NAME=akane-dev-lambda-getTiAmo
OUTPUT_FILE=response.json

aws lambda invoke --function-name ${LAMBDA_NAME} --log-type Tail ${OUTPUT_FILE} --query 'LogResult'  --output text |  base64 -D

cat ${OUTPUT_FILE}

exit 0
test_apigw.sh
#!/bin/sh

set -e

if [ $# -ne 2 ]; then
    echo " --- \033[0;31m [ERROR]: The following arguments are required \033[0;39m --- "
    echo "      \033[0;33m$0 <URL> <API_KEY>\033[0;39m"
    echo "      \033[0;33m<URL> Example: https://xxxxxxxxxx.execute-api.ap-northeast-1.amazonaws.com/v1/whisper\033[0;39m"
    exit 1
fi

cd `dirname $0`

echo " --- \033[0;33m TEST APIGW \033[0;39m --- "

URL=$1
API_KEY=$2

curl -X GET -H 'Content-Type: application/json' \
-H "x-api-key: ${API_KEY}" \
"${URL}"

exit 0
delete_stacks.sh
#!/bin/sh

cd `dirname $0`

SYSTEM_NAME=akane

delete_stack () {
    ENV_TYPE=$1
    STACK_NAME=$2
    aws cloudformation delete-stack \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}

    aws cloudformation wait stack-delete-complete \
    --stack-name ${SYSTEM_NAME}-${ENV_TYPE}-${STACK_NAME}
}

delete_stack dev apigw
delete_stack dev lambda
./akane/lambda/delete_artifact.dev.sh
delete_stack all s3

exit 0

4. アーティファクト関連ファイル

mkzip.sh
#!/bin/sh

cd `dirname $0`

ENV_TYPE=dev
LAMBDA_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')

FILE=../code-lambda-${LAMBDA_NAME}.zip

cd ./code

ls . |  grep -v -E ".gitignore|${LAMBDA_NAME}.py" | xargs rm -rf

rm -f ${FILE}
zip -r ${FILE} ./*

exit 0
upload_artifact.dev.sh
#!/bin/sh

cd `dirname $0`

BASENAME=$(basename $0)
FILENAME=${BASENAME%.*}
ENV_TYPE=${FILENAME##*.}

BUCKET_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "ArtifactS3Bucket").ParameterValue')

ARTIFACT_NAME=code-lambda-$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')

aws s3 cp ${ARTIFACT_NAME}.zip s3://${BUCKET_NAME}/${ARTIFACT_NAME}.zip

exit 0
delete_artifact.dev.sh
#!/bin/sh

cd `dirname $0`

BASENAME=$(basename $0)
FILENAME=${BASENAME%.*}
ENV_TYPE=${FILENAME##*.}

BUCKET_NAME=$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "ArtifactS3Bucket").ParameterValue')

ARTIFACT_NAME=code-lambda-$(cat ${ENV_TYPE}-parameters.json | jq -r '.Parameters[] | select(.ParameterKey == "LambdaName").ParameterValue')

aws s3 rm s3://${BUCKET_NAME}/${ARTIFACT_NAME}.zip

exit 0
2
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
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?