Amazon API Gateway と AWS Lambda 関数の CI/CD パイプラインをつくる [AWS SAM + AWS CloudFormation]

本記事は、Japan AWS Ambassador Advent Calendar 2023 の 2023/12/2 付記事となります。

こんにちは、SCSK の広野です。

私は Amazon API Gateway と AWS Lambda 関数の CI/CD を学習する社内研修を開催しておりまして、研修用 CI/CD パイプラインを AWS Code シリーズのサービスと AWS SAM (Serverless Application Model)、AWS CloudFormation で作成しています。

本記事では、その環境を抜粋して AWS CloudFormation でデプロイできるようにしたものを紹介します。

アーキテクチャ

以下のように、AWS サービスを組み合わせて作成しています。

  • 開発環境は AWS Cloud9 を使用。
  • ソースコード管理に AWS CodeCommit を使用。Lambda 関数コードだけでなく、ビルド設定ファイル buildspec.yml と AWS SAM テンプレートファイル template.yml もセットで管理する。
  • コードの変更検知に Amazon EventBridge を使用。AWS CodeCommit のソースコードが変更されると CI/CD パイプラインを開始させる。
  • CI/CD パイプラインに AWS CodePipeline を使用。ビルドステージで AWS CodeBuild、AWS SAM を、デプロイステージで AWS CloudFormation を使用。AWS SAM で API Gateway と Lambda 関数をデプロイする。
  • パイプラインの各ステージで作成される成果物 (アーティファクト) 置き場に Amazon S3 を使用。
    ※図は簡略化していますが 2つのバケットがあります。SAM テンプレート (API Gateway と Lambda 関数リソース作成) と Lambda 関数コードの ZIP で。
これにより、コードを更新かければ API Gateway と Lambda 関数が作成/更新されるようになります。

CI/CD パイプライン準備

CI/CD パイプラインは、以下の AWS CloudFormation テンプレートを流すことでデプロイできます。※ Cloud9 や VPC は除く

AWSTemplateFormatVersion: 2010-09-09
Description: The CloudFormation template that creates a S3 bucket, a CI/CD environment with Code service series, and relevant IAM roles.

Parameters:
# ------------------------------------------------------------#
# Input Parameters
# ------------------------------------------------------------#
  YourID:
    Type: String
    Description: Please fill your full name and today's date, or another unique name. (e.g. yujihirono20231202) You can not use any upper cases and special characters.
    MaxLength: 100
    MinLength: 5

Resources:
# ------------------------------------------------------------#
# CodeCommit Repository
# ------------------------------------------------------------#
  SampleProjectRep:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Sub SampleProject-${YourID}
      Tags:
        - Key: Cost
          Value: !Ref YourID

# ------------------------------------------------------------#
# EventBridge Rule for Starting CodePipeline
# ------------------------------------------------------------#
  SampleProjectRepPipelineEventsRule:
    Type: AWS::Events::Rule
    Properties:
      Name: !Sub SampleProject-StartPipelineRule-${YourID}
      EventBusName: !Sub "arn:aws:events:${AWS::Region}:${AWS::AccountId}:event-bus/default"
      EventPattern:
        source:
          - aws.codecommit
        resources:
          - !GetAtt SampleProjectRep.Arn
        detail-type:
          - "CodeCommit Repository State Change"
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceName:
            - master
      State: ENABLED
      Targets:
        - Arn:
            Fn::Join:
              - ""
              - - "arn:"
                - Ref: AWS::Partition
                - ":codepipeline:"
                - Ref: AWS::Region
                - ":"
                - Ref: AWS::AccountId
                - ":"
                - Ref: Pipeline
          Id: Target0
          RoleArn: !GetAtt PipelineEventsRole.Arn
    DependsOn:
      - PipelineEventsRole
      - Pipeline

# ------------------------------------------------------------#
# CodeBuild Project Role (IAM)
# ------------------------------------------------------------#
  BuildProjectRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub BldPrjRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodeBuild Project Role Policy (IAM)
# ------------------------------------------------------------#
  BuildProjectRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub BldPrjRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              - Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":logs:"
                    - Ref: AWS::Region
                    - ":"
                    - Ref: AWS::AccountId
                    - :log-group:/aws/codebuild/
                    - Ref: BuildProject
                    - :*
              - Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":logs:"
                    - Ref: AWS::Region
                    - ":"
                    - Ref: AWS::AccountId
                    - :log-group:/aws/codebuild/
                    - Ref: BuildProject
          - Action:
              - codebuild:BatchPutCodeCoverages
              - codebuild:BatchPutTestCases
              - codebuild:CreateReport
              - codebuild:CreateReportGroup
              - codebuild:UpdateReport
            Effect: Allow
            Resource:
              Fn::Join:
                - ""
                - - "arn:"
                  - Ref: AWS::Partition
                  - ":codebuild:"
                  - Ref: AWS::Region
                  - ":"
                  - Ref: AWS::AccountId
                  - :report-group/
                  - Ref: BuildProject
                  - -*
          - Action: s3:PutObject
            Effect: Allow
            Resource:
              Fn::Join:
                - ""
                - - !GetAtt PipelinePackageBucket.Arn
                  - /*
          - Action:
              - s3:Abort*
              - s3:DeleteObject*
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
              - s3:PutObject
              - s3:PutObjectLegalHold
              - s3:PutObjectRetention
              - s3:PutObjectTagging
              - s3:PutObjectVersionTagging
            Effect: Allow
            Resource:
              - !GetAtt PipelineArtifactBucket.Arn
              - Fn::Join:
                  - ""
                  - - !GetAtt PipelineArtifactBucket.Arn
                    - /*
        Version: "2012-10-17"
      Roles:
        - Ref: BuildProjectRole
    DependsOn:
      - BuildProjectRole
      - PipelineArtifactBucket
      - PipelinePackageBucket

# ------------------------------------------------------------#
# CodeBuild Project
# ------------------------------------------------------------#
  BuildProject:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Sub SampleProjectBuild-${YourID}
      Artifacts:
        Type: CODEPIPELINE
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        EnvironmentVariables:
          - Name: S3_BUCKET
            Type: PLAINTEXT
            Value:
              Ref: PipelinePackageBucket
        Image: "aws/codebuild/standard:7.0"
        ImagePullCredentialsType: CODEBUILD
        PrivilegedMode: false
        Type: LINUX_CONTAINER
      ServiceRole: !GetAtt BuildProjectRole.Arn
      Source:
        Type: CODEPIPELINE
      Cache:
        Type: NO_CACHE
      Tags:
        - Key: Cost
          Value: !Ref YourID
    DependsOn:
      - BuildProjectRole

# ------------------------------------------------------------#
# CodePipeline Build Package Bucket (S3)
# ------------------------------------------------------------#
  PipelinePackageBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub codepipeline-package-${YourID}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: Cost
          Value: !Ref YourID

# ------------------------------------------------------------#
# CodePipeline Artifact Bucket (S3)
# ------------------------------------------------------------#
  PipelineArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub codepipeline-artifact-${YourID}
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      Tags:
        - Key: Cost
          Value: !Ref YourID

# ------------------------------------------------------------#
# CodePipeline Role (IAM)
# ------------------------------------------------------------#
  PipelineRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action:
              - s3:Abort*
              - s3:DeleteObject*
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
              - s3:PutObject
              - s3:PutObjectLegalHold
              - s3:PutObjectRetention
              - s3:PutObjectTagging
              - s3:PutObjectVersionTagging
            Effect: Allow
            Resource:
              - !GetAtt PipelineArtifactBucket.Arn
              - Fn::Join:
                  - ""
                  - - !GetAtt PipelineArtifactBucket.Arn
                    - /*
          - Action: sts:AssumeRole
            Effect: Allow
            Resource:
              - !GetAtt PipelineBuildCodeBuildActionRole.Arn
              - !GetAtt PipelineDeployCreateChangeSetActionRole.Arn
              - !GetAtt PipelineDeployExecuteChangeSetActionRole.Arn
              - !GetAtt PipelineSourceCodeCommitActionRole.Arn
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineRole
    DependsOn:
      - PipelineRole
      - PipelineArtifactBucket
      - PipelineBuildCodeBuildActionRole
      - PipelineDeployCreateChangeSetActionRole
      - PipelineDeployExecuteChangeSetActionRole
      - PipelineSourceCodeCommitActionRole

# ------------------------------------------------------------#
# CodePipeline
# ------------------------------------------------------------#
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Sub SampleProjectPipeline-${YourID}
      RoleArn: !GetAtt PipelineRole.Arn
      Stages:
        - Actions:
            - ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: "1"
              Configuration:
                RepositoryName: !GetAtt SampleProjectRep.Name
                BranchName: master
                PollForSourceChanges: false
              Name: CodeCommit
              OutputArtifacts:
                - Name: Artifact_Source_CodeCommit
              RoleArn: !GetAtt PipelineSourceCodeCommitActionRole.Arn
              RunOrder: 1
          Name: Source
        - Actions:
            - ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: "1"
              Configuration:
                ProjectName:
                  Ref: BuildProject
              InputArtifacts:
                - Name: Artifact_Source_CodeCommit
              Name: CodeBuild
              OutputArtifacts:
                - Name: Artifact_Build_CodeBuild
              RoleArn: !GetAtt PipelineBuildCodeBuildActionRole.Arn
              RunOrder: 1
          Name: Build
        - Actions:
            - ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: "1"
              Configuration:
                StackName: !Sub SampleProject-Stack-${YourID}
                Capabilities: CAPABILITY_NAMED_IAM
                RoleArn: !GetAtt PipelineDeployCreateChangeSetRole.Arn
                ActionMode: CHANGE_SET_REPLACE
                ChangeSetName: !Sub SampleProject-Deploy-${YourID}
                TemplatePath: Artifact_Build_CodeBuild::packaged.yaml
                ParameterOverrides: !Sub '{"YourID": "${YourID}"}'
              InputArtifacts:
                - Name: Artifact_Build_CodeBuild
              Name: CreateChangeSet
              RoleArn: !GetAtt PipelineDeployCreateChangeSetActionRole.Arn
              RunOrder: 1
            - ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: "1"
              Configuration:
                StackName: !Sub SampleProject-Stack-${YourID}
                ActionMode: CHANGE_SET_EXECUTE
                ChangeSetName: !Sub SampleProject-Deploy-${YourID}
              Name: ExecuteChangeSet
              RoleArn: !GetAtt PipelineDeployExecuteChangeSetActionRole.Arn
              RunOrder: 2
          Name: Deploy
      ArtifactStore:
        Location:
          Ref: PipelineArtifactBucket
        Type: S3
      Tags:
        - Key: Cost
          Value: !Ref YourID
    DependsOn:
      - SampleProjectRep
      - PipelineRoleDefaultPolicy
      - PipelineRole
      - PipelineSourceCodeCommitActionRole
      - PipelineBuildCodeBuildActionRole
      - PipelineDeployCreateChangeSetActionRole
      - PipelineDeployExecuteChangeSetActionRole
      - PipelineArtifactBucket

# ------------------------------------------------------------#
# CodePipeline Source Code Commit Action Role (IAM)
# ------------------------------------------------------------#
  PipelineSourceCodeCommitActionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlSrcCcActionRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS:
                Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":iam::"
                    - Ref: AWS::AccountId
                    - :root
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Source CodeCommit Action Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineSourceCodeCommitActionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlSrcCcActionRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action:
              - s3:Abort*
              - s3:DeleteObject*
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
              - s3:PutObject
              - s3:PutObjectLegalHold
              - s3:PutObjectRetention
              - s3:PutObjectTagging
              - s3:PutObjectVersionTagging
            Effect: Allow
            Resource:
              - !GetAtt PipelineArtifactBucket.Arn
              - Fn::Join:
                  - ""
                  - - !GetAtt PipelineArtifactBucket.Arn
                    - /*
          - Action:
              - codecommit:CancelUploadArchive
              - codecommit:GetBranch
              - codecommit:GetCommit
              - codecommit:GetUploadArchiveStatus
              - codecommit:UploadArchive
            Effect: Allow
            Resource: !GetAtt SampleProjectRep.Arn
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineSourceCodeCommitActionRole
    DependsOn:
      - PipelineArtifactBucket
      - SampleProjectRep
      - PipelineSourceCodeCommitActionRole

# ------------------------------------------------------------#
# CodePipeline Events Role (IAM)
# ------------------------------------------------------------#
  PipelineEventsRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlEventsRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: events.amazonaws.com
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Events Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineEventsRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlEventsRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action: codepipeline:StartPipelineExecution
            Effect: Allow
            Resource:
              Fn::Join:
                - ""
                - - "arn:"
                  - Ref: AWS::Partition
                  - ":codepipeline:"
                  - Ref: AWS::Region
                  - ":"
                  - Ref: AWS::AccountId
                  - ":"
                  - Ref: Pipeline
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineEventsRole
    DependsOn:
      - PipelineEventsRole

# ------------------------------------------------------------#
# CodePipeline Build CodeBuild Action Role (IAM)
# ------------------------------------------------------------#
  PipelineBuildCodeBuildActionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlBldCbActionRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS:
                Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":iam::"
                    - Ref: AWS::AccountId
                    - :root
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Build CodeBuild Action Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineBuildCodeBuildActionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlBldCbActionRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action:
              - codebuild:BatchGetBuilds
              - codebuild:StartBuild
              - codebuild:StopBuild
            Effect: Allow
            Resource: !GetAtt BuildProject.Arn
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineBuildCodeBuildActionRole
    DependsOn:
      - BuildProject
      - PipelineBuildCodeBuildActionRole

# ------------------------------------------------------------#
# CodePipeline Deploy Create ChangeSet Action Role (IAM)
# ------------------------------------------------------------#
  PipelineDeployCreateChangeSetActionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlDepCrChsetActionRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS:
                Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":iam::"
                    - Ref: AWS::AccountId
                    - :root
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Deploy Create ChangeSet Action Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineDeployCreateChangeSetActionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlDepCrChsetActionRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action: iam:PassRole
            Effect: Allow
            Resource: !GetAtt PipelineDeployCreateChangeSetRole.Arn
          - Action:
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
            Effect: Allow
            Resource:
              - !GetAtt PipelineArtifactBucket.Arn
              - Fn::Join:
                  - ""
                  - - !GetAtt PipelineArtifactBucket.Arn
                    - /*
          - Action:
              - cloudformation:CreateChangeSet
              - cloudformation:DeleteChangeSet
              - cloudformation:DescribeChangeSet
              - cloudformation:DescribeStacks
            Condition:
              StringEqualsIfExists:
                cloudformation:ChangeSetName: !Sub SampleProject-Deploy-${YourID}
            Effect: Allow
            Resource:
              Fn::Join:
                - ""
                - - "arn:"
                  - Ref: AWS::Partition
                  - ":cloudformation:"
                  - Ref: AWS::Region
                  - ":"
                  - Ref: AWS::AccountId
                  - !Sub :stack/SampleProject-Stack-${YourID}/*
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineDeployCreateChangeSetActionRole
    DependsOn:
      - PipelineDeployCreateChangeSetRole
      - PipelineArtifactBucket
      - PipelineDeployCreateChangeSetActionRole

# ------------------------------------------------------------#
# CodePipeline Deploy Create ChangeSet Role (IAM)
# ------------------------------------------------------------#
  PipelineDeployCreateChangeSetRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlDepCrChsetRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Deploy Create ChangeSet Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineDeployCreateChangeSetRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlDepCrChsetRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action:
              - s3:GetBucket*
              - s3:GetObject*
              - s3:List*
            Effect: Allow
            Resource:
              - !GetAtt PipelineArtifactBucket.Arn
              - Fn::Join:
                  - ""
                  - - !GetAtt PipelineArtifactBucket.Arn
                    - /*
          - Action: "*"
            Effect: Allow
            Resource: "*"
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineDeployCreateChangeSetRole
    DependsOn:
      - PipelineArtifactBucket
      - PipelineDeployCreateChangeSetRole

# ------------------------------------------------------------#
# CodePipeline Deploy Execute ChangeSet Action Role (IAM)
# ------------------------------------------------------------#
  PipelineDeployExecuteChangeSetActionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub PlDepExecChsetActionRole-${YourID}
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              AWS:
                Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":iam::"
                    - Ref: AWS::AccountId
                    - :root
        Version: "2012-10-17"

# ------------------------------------------------------------#
# CodePipeline Deploy Execute ChangeSet Action Role Policy (IAM)
# ------------------------------------------------------------#
  PipelineDeployExecuteChangeSetActionRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: !Sub PlDepExecChsetActionRolePolicy-${YourID}
      PolicyDocument:
        Statement:
          - Action:
              - cloudformation:DescribeChangeSet
              - cloudformation:DescribeStacks
              - cloudformation:ExecuteChangeSet
            Condition:
              StringEqualsIfExists:
                cloudformation:ChangeSetName: !Sub SampleProject-Deploy-${YourID}
            Effect: Allow
            Resource:
              Fn::Join:
                - ""
                - - "arn:"
                  - Ref: AWS::Partition
                  - ":cloudformation:"
                  - Ref: AWS::Region
                  - ":"
                  - Ref: AWS::AccountId
                  - !Sub :stack/SampleProject-Stack-${YourID}/*
        Version: "2012-10-17"
      Roles:
        - Ref: PipelineDeployExecuteChangeSetActionRole
    DependsOn:
      - PipelineDeployExecuteChangeSetActionRole

CI/CD パイプライン使用方法

AWS Cloud9 準備

今回は、API Gateway と Lambda 関数をコードで管理します。コードは AWS CodeCommit で管理し、コードの編集は AWS Cloud9 を使用します。あらかじめ CodeCommit の該当するリポジトリ と Cloud9 を連携する必要があります。ここでは、この準備の説明は割愛します。以下の AWS 公式ドキュメントを参考に準備頂けると幸いです。

コードの準備

API Gateway と Lambda 関数をデプロイするために、以下 3 種類のコードファイルを準備します。ファイル名はこの例のまま使用頂いた方が良いです。(CI/CD パイプライン内で指定している都合でw)

  • lambda_function.py
  • buildspec.yml
  • template.yml

以下、それぞれについて解説します。

lambda_function.py

Lambda 関数の実体です。ここでは、サンプルなので現在時刻を返すだけの Python コードになっています。

from datetime import datetime

def lambda_handler(event, context):
  # Get the current date and time
  now = datetime.now()

  # Format the date and time in a readable format (e.g., YYYY-MM-DD HH:MM:SS)
  current_time = now.strftime("%Y-%m-%d %H:%M:%S")

  # Return the current date and time
  return {
    "statusCode": 200,
    "body": current_time
  }

buildspec.yml

ビルドステージで何をするか、を定義したファイルです。

実際のところ、Lambda 関数コードは上述の lambda_function.py をそのまま使用するのでコンパイルすることはありません。API Gateway や Lambda 関数リソースをこの CI/CD パイプラインでデプロイするため、sam build や sam package というコマンドを使用して AWS SAM テンプレートを AWS 側で処理しやすいフォーマットに?若干変更するようです。(変更後ファイルは packaged.yaml) 

version: 0.2
phases:
  install:
    runtime-versions:
      python: 3.11
    commands:
  pre_build:
    commands:
      - sam build
  build:
    commands:
      - sam package --s3-bucket $S3_BUCKET --output-template-file packaged.yaml
artifacts:
  files:
    - packaged.yaml

template.yml

AWS SAM テンプレートファイルです。この中で、デプロイしたい API Gateway と Lambda 関数のリソースを定義しています。

AWS CloudFormation と比べるとかなり少ない行数です。標準的な設定を使用して動くリソースをデプロイしてくれます。

AWSTemplateFormatVersion: 2010-09-09
Description: >-
  SampleProject
Transform:
- AWS::Serverless-2016-10-31
Parameters:
  YourID:
    Type: String
    Default: dummy
Resources:
  SampleFunction:
    Type: AWS::Serverless::Function
    Properties:
      Handler: lambda_function.lambda_handler
      Runtime: python3.11
      Policies:
        - DynamoDBReadPolicy:
            TableName: YourTableName
      Environment:
        Variables:
          YourID: !Ref YourID
      Events:
        Api:
          Type: Api
          Properties:
            Path: /test
            Method: POST
Outputs:
  ApiEndpoint:
    Description: "APIG Endpoint"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"

SAM (Serverless Application Model) は AWS CloudFormation よりも簡易にリソースを定義できる分、カスタマイズ性に欠けるので要件によって CloudFormation との使い分けを考える必要があります。

ここでは、全く使用しないのですが Lambda 関数に YourTableName という Amazon DynamoDB テーブルの Read Only 権限を与えています。このように AWS があらかじめ用意した権限パターンセットを使うと簡単な権限定義ができます。

最終的に、前述した通り SAM テンプレートはビルドステージで内部的に CloudFormation テンプレートに変換され、デプロイに使用されます。

実際にデプロイしてみる

CodeCommit と連携した状態の Cloud9 で、以下のようにプロジェクトのルートディレクトリに 3 つのコードを配置します。

配置後、CodeCommit にコードをプッシュします。(以下、コマンド例)

git commit -A
git commit -m "initial commit"
git push origin master

プッシュすると、自動的に CI/CD パイプライン (AWS CodePipeline) が動き出します。

Deploy ステージまで完了。

全てのステージが完了すると、API Gateway と Lambda 関数が連携された状態でデプロイされています。

Lambda 関数の IAM ロールも、デフォルトのポリシー + DynamoDB Read only 系の権限が自動的に作成されてますね。

AWS CloudFormation のコンソールを見ると、CI/CD パイプラインから実行された CloudFormation スタックがあることが確認できます。

説明は省略しますが、API Gateway や Lambda 関数を更新するときは Cloud9 のコードを更新して上記手順を繰り返します。

注意点ですが、一連の環境を削除するときには先にこの CI/CD パイプラインから呼び出された CloudFormation スタックを削除する必要があります。その後、CI/CD パイプラインをデプロイしたときのスタック削除になります。

まとめ

いかがでしたでしょうか?

API Gateway と Lambda 関数をコードでバージョン管理するときにはこのような方法を採るのが今時点のソリューションかと思います。あくまでも簡易な一例ですので、ディテールは書き換えて下さい。

本記事が皆様のお役に立てれば幸いです。

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