LoginSignup
5
0

More than 3 years have passed since last update.

Lambda関数をBlue/GreenデプロイメントするCodePipelineをCloudFormationで自動構築する

Last updated at Posted at 2020-04-18

前提条件

以下の記事で手動構築していたCodePipelineのCI/CDパイプラインをCloudFormationで自動構築してみる記事。なので、構成やアプリケーションの内容は読んで把握しておくことが望ましい。

Amazon API Gateway/ALBのバックエンドで動くLambda関数をJava(Eclipse+maven)で実装する
ALBのバックエンドで動作するJava実装のLambda関数をBlue/GreenデプロイメントするCodePipelineを作る

最終的に目指すCI/CDパイプラインの構成イメージは以下。Lambda関数のフロントにALBがいる想定だが、このパイプラインでは触らない(厳密には、リスナーとターゲットグループは触る)ので割愛する。
LambdaCFn.png

記事中には、過去のCloudFormation関連の記事同様、プロパティに関する補足をしている部分がある。
備忘のためにリファレンスに書かれていないデフォルト値も整理しておくが、2020年4月時点の情報であり、後でAWSが仕様を変えたとしても追従する予定はないので、挙動が違ったらユーザーガイドのリファレンスを見直してほしい。あと、今回の構成以外の構成以外のデフォルト値まで調査はしていないのであしからず。

CodePipelineのデプロイステージ

過去のCloudFormationの記事と同様に作っていくことになるが、今回の主な違いはデプロイステージである。アクションプロバイダ(ActionTypeIdのProvider)が今回はCloudFormationになる。

上記のケースでのアクション構造のリファレンスはここ

プロパティ デフォルト値
ActionMode 必須 ※リファレンス記載
StackName 必須 ※リファレンス記載
Capabilities 条件により必須 ※リファレンス記載
今回のようにActionMode: CREATE_UPDATE に設定する場合、CAPABILITY_NAMED_IAM, CAPABILITY_AUTO_EXPANDを設定する。
ChangeSetName 条件により必須 ※リファレンス記載
今回のようにActionMode: CREATE_UPDATE に設定する場合、不要
RoleArn 条件により必須 ※リファレンス記載
今回のようにActionMode: CREATE_UPDATE に設定する場合、必須
TemplatePath 条件により必須 ※リファレンス記載
今回のようにActionMode: CREATE_UPDATE に設定する場合、必須
OutputFileName なし
ParameterOverrides なし
TemplateConfiguration なし

最小権限にするためのIAM設定

これまでの記事では、毎回「適当にS3にフルアクセスできるような権限でも用意しておいて」なノリで書いていて、最小権限の考え方と外れていたので、少し真面目に考えてみる。
今回、アーティファクトのやり取りのためにS3を新規に作ったり、CodeBuildのためにCloudWatch Logsのロググループを作ったりしているので、それだけを既存のCodeBuildのサービスロールに追加してあげることにしよう。

なお、${Prefix}, ${CodeBuildPolicyNameSuffix}, ${CodeBuildLogGroupNameSuffix}, ${S3BucketName}, ${CodeBuildRole} はParametersで事前に設定しておけるようにしておこう。

  CODEBUILDIAMPOLYCY:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${Prefix}${CodeBuildPolicyNameSuffix}
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowS3Bucket
            Effect: Allow
            Action:
              - "s3:GetBucketAcl"
              - "s3:GetBucketLocation"
            Resource: !GetAtt S3BUCKET.Arn
          - Sid: AllowS3Object
            Effect: Allow
            Action:
              - "s3:PutObject"
              - "s3:GetObject"
              - "s3:GetObjectVersion"
            Resource: !Join [ "/", [ !GetAtt S3BUCKET.Arn, "*" ] ]
          - Sid: AllowCloudWatchLogs
            Effect: Allow
            Action:
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
            Resource: !GetAtt CODEBUILDLOGGROUP.Arn
      Roles:
        - !Sub ${CodeBuildRole}
  S3BUCKET: 
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub ${S3BucketName}
  CODEBUILDLOGGROUP:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${Prefix}${CodeBuildLogGroupNameSuffix}

気を付けなければいけないのが、IAMについては、CodePipelineとの依存関係をCloudFormationが自動で解決してくれないということ。
何も考えずに設定してしまうと、IAMとCodePipelineの作成順序が変わってしまう可能性があり、CodePipeline実行時にまだIAMポリシの設定が完了していなくてエラーになってしまう。

CodePipelineのリソース定義に

  PIPELINE:
    Type: AWS::CodePipeline::Pipeline
    DependsOn: CODEBUILDIAMPOLYCY
    Properties: 
      

と、忘れず入れておくようにしよう。

CloudFormationテンプレート全体

ちょっと長いけど↓こんな感じになる。
今回は、ParametersのPrefixとS3BucketNameを修正すれば良いようにしたので、

AWSTemplateFormatVersion: "2010-09-09"
Description:
  CI/CD Pipeline for Lambda Create

Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "LambdaTest"
  S3BucketName:
    Description: "S3 bucket name for Artifact"
    Type: "String"
    Default: "lambdatest-artifact-bucket"
  PipelineNameSuffix:
    Description: "PipelineName on CodePipeline"
    Type: "String"
    Default: "-Pipeline"
  RepositoryNameSuffix:
    Description: "RepositoryName on CodeCommit"
    Type: "String"
    Default: ""
  BuildProjectNameSuffix:
    Description: "BuildProjectName on CodeBuild"
    Type: "String"
    Default: "-Build-Project"
  StackNameSuffix: 
    Description: "StackName on CloudFormation(SAM)"
    Type: "String"
    Default: "-CodePileline-Stack"
  CodeBuildRole:
    Description: "CodeBuild Service Role"
    Type: "String"
    Default: "codebuild-LambdaTestProject-service-role"
  CodeBuildPolicyNameSuffix:
    Description: "IAM Policy Name for CodeBuild"
    Type: "String"
    Default: "-CodeBuild-Polycy"
  CodeBuildLogGroupNameSuffix:
    Description: "LogGroup Name for CodeBuild"
    Type: "String"
    Default: "-CodeBuild-LogGroup"


Metadata:
  AWS::CloudFormation::Interface:
    ParameterGroups:
      - Label:
          default: "Project name prefix"
        Parameters:
          - Prefix
      - Label:
          default: "S3 Configuration"
        Parameters:
          - S3BucketName
      - Label:
          default: "Pipeline Configuration"
        Parameters:
          - PipelineNameSuffix
          - RepositoryNameSuffix
          - BuildProjectNameSuffix
          - StackNameSuffix
      - Label:
          default: "IAM Role/Policy Configuration"
        Parameters:
          - CodeBuildRole
          - CodeBuildPolicyNameSuffix
      - Label:
          default: "Log Configuration"
        Parameters:
          - CodeBuildLogGroupNameSuffix

Resources:
  # ------------------------------------------------------------#
  #  IAM Policy 
  # ------------------------------------------------------------#
  CODEBUILDIAMPOLYCY:
    Type: AWS::IAM::ManagedPolicy
    Properties:
      ManagedPolicyName: !Sub ${Prefix}${CodeBuildPolicyNameSuffix}
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Sid: AllowS3Bucket
            Effect: Allow
            Action:
              - "s3:GetBucketAcl"
              - "s3:GetBucketLocation"
            Resource: !GetAtt S3BUCKET.Arn
          - Sid: AllowS3Object
            Effect: Allow
            Action:
              - "s3:PutObject"
              - "s3:GetObject"
              - "s3:GetObjectVersion"
            Resource: !Join [ "/", [ !GetAtt S3BUCKET.Arn, "*" ] ]
          - Sid: AllowCloudWatchLogs
            Effect: Allow
            Action:
              - "logs:CreateLogGroup"
              - "logs:CreateLogStream"
              - "logs:PutLogEvents"
            Resource: !GetAtt CODEBUILDLOGGROUP.Arn
      Roles:
        - !Sub ${CodeBuildRole}

  # ------------------------------------------------------------#
  #  S3 Bucket 
  # ------------------------------------------------------------#
  S3BUCKET: 
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub ${S3BucketName}

  # ------------------------------------------------------------#
  #  Cloud Watch Log Group
  # ------------------------------------------------------------#
  CODEBUILDLOGGROUP:
    Type: AWS::Logs::LogGroup
    Properties:
      LogGroupName: !Sub ${Prefix}${CodeBuildLogGroupNameSuffix}

  # ------------------------------------------------------------#
  #  CodeBuild
  # ------------------------------------------------------------#
  CODEBUILD:
    Type: AWS::CodeBuild::Project
    Properties: 
      Name: !Sub ${Prefix}${BuildProjectNameSuffix}
      Source: 
        Type: CODEPIPELINE
        BuildSpec: buildspec.yml
      Artifacts: 
        Type: CODEPIPELINE
      Environment: 
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
      LogsConfig:
        CloudWatchLogs:
          GroupName: !Sub ${Prefix}${CodeBuildLogGroupNameSuffix}
          Status: ENABLED
      Cache: 
        Type: LOCAL
        Modes:
          - LOCAL_CUSTOM_CACHE
      ServiceRole: !Sub arn:aws:iam::${AWS::AccountId}:role/service-role/codebuild-LambdaTestProject-service-role

  # ------------------------------------------------------------#
  #  CodePipeline
  # ------------------------------------------------------------#
  PIPELINE:
    Type: AWS::CodePipeline::Pipeline
    DependsOn: CODEBUILDIAMPOLYCY
    Properties: 
      Name: !Sub ${Prefix}${PipelineNameSuffix}
      ArtifactStore: 
        Location: !Ref S3BUCKET
        Type: S3
      RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/CodePipelineRole
      Stages: 
        - Name: Source
          Actions:
            - RunOrder: 1
              Name: Source
              ActionTypeId: 
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: 1
              Configuration:
                RepositoryName: !Sub ${Prefix}${RepositoryNameSuffix}
                BranchName: master
              OutputArtifacts: 
                - Name: SourceArtifact
        - Name: Build
          Actions:
            - RunOrder: 2
              Name: Build
              ActionTypeId: 
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: 1
              Configuration:
                ProjectName: !Ref CODEBUILD
              InputArtifacts: 
                - Name: SourceArtifact
              OutputArtifacts: 
                - Name: BuildArtifact
        - Name: Deploy
          Actions:
            - RunOrder: 3
              Name: Deploy
              ActionTypeId: 
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: 1
              Configuration:
                StackName: !Sub ${Prefix}${StackNameSuffix}
                ActionMode: CREATE_UPDATE
                RoleArn: !Sub arn:aws:iam::${AWS::AccountId}:role/CloudFormationLambdaPipeline
                TemplatePath: BuildArtifact::output-template.yml
                Capabilities: CAPABILITY_NAMED_IAM,CAPABILITY_AUTO_EXPAND
              InputArtifacts: 
                - Name: BuildArtifact

↓の部分が、アプリのbuildspec.ymlと連動しなければいけないのがちょっとイケてない……。
また、このテンプレートで作るS3バケットの名前をあらかじめbuildspec.ymlの中で指定しなければいけないのもな……。この辺を、Outputと連動させられるようになると良い感じなのだが。

TemplatePath: BuildArtifact::output-template.yml
5
0
2

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
5
0