【CodePipeline】AWS CloudFormation用CI/CDパイプラインにSNSを用いた承認プロセスを追加したい

AWS

はじめに

今回は、CloudFormationスタックを更新するCodePipelineを作成していきます。

以前、作成した構成は、単純にCodeCommitリポジトリのmasterブランチにマージされたら自動でCloudFormationスタックが作成されるという内容でした。

ですが、今回は、この構成に承認プロセスとステージング環境にテストデプロイするという構成で構築していきたいと思います。

以下のドキュメントを参考にしていきます。

CodePipeline を使用した継続的デリバリー - AWS CloudFormation
CodePipeline を使用して継続的な配信リリースプロセスを作成し、AWS CloudFormation テンプレートへの変更を自動的に構築してテストしてから、本稼働スタックに昇格させます。

構成図

下記の内容を踏襲した構成で検証します。

さっそくCloudFormationで構築!

CloudFormationテンプレートは以下にございます。

ダウンロードしてデスクトップなど任意のフォルダに保存します。

※その他のファイルも後述の手順で使用するためダウンロードしておきます

https://github.com/kosments/aws-improvement-tips/tree/main/cicd-pipeline-for-cfn-approve

CloudFormation:YAMLテンプレートのポイント

YAMLテンプレートのポイントを記述します。

  1. ArtifactStoreBucket: S3バケットを定義します。このバケットは、CodePipelineのアーティファクトを格納するために使用されます。バケット名は、タグ(TagOwner、TagApplication、TagEnv、TagUser)に基づいて自動的に生成されます。バージョニングとパブリックアクセスブロックの設定も含まれています。
  2. CodePipelineSNSTopic: AWS SNSトピックを定義します。このトピックは、CodePipelineの承認アクションで使用され、通知を送信するためのものです。トピック名も、同様にタグに基づいて自動的に生成されます。
  3. MyPipeline: AWS CodePipelineパイプラインを定義します。このパイプラインは、ソースステージ、ステージ、および本番ステージの3つのステージから構成されています。各ステージには、ソースの取得、テストステージ(StgStage)、本番デプロイステージ(PrdStage)が含まれます。各アクションには、スタックの作成、変更セットの作成、承認、およびスタックの削除などが含まれています。

Pipeline箇所の詳細は以下の通りです。

  1. Source Stage:
    • ソースコードのリポジトリからソースコードを取得します。
    • AWS CodeCommitがプロバイダーとして使用され、mainブランチからのソースコードの変更をトリガーとしてビルドが開始されます。
  2. Stg Stage:
    • ステージング環境(Staging Environment)に対するデプロイメントを処理します。
    • CloudFormationを使用して、ステージングスタックを作成、更新、または削除します。
    • **CreateStack**アクションでは、指定されたCloudFormationテンプレートを使用してステージングスタックを作成します。
    • **ApproveStgStack**アクションは、ステージングスタックの変更セットを承認するための人間の介入をトリガーします。このアクションには手動の承認が必要です。
    • **DeleteStgStack**アクションは、ステージングスタックを削除します。このアクションは、本番環境に変更を反映する前にステージング環境をクリーンアップするために使用されます。
  3. Prd Stage:
    • 本番環境(Production Environment)へのデプロイメントを処理します。
    • ステージングスタックの変更が承認されると、本番環境に変更が適用されます。
    • **CreateChangeSet**アクションでは、本番環境の変更セットを作成します。この変更セットは、CloudFormationテンプレートに基づいて本番環境を変更しますが、まだ適用されていません。
    • **ApproveChangeSet**アクションは、変更セットを承認するための人間の介入をトリガーします。このアクションには手動の承認が必要です。
    • **ExecuteChangeSet**アクションでは、承認された変更セットが本番環境に適用されます。つまり、本番環境が変更されます。
  # ------------------------------ #
  # ArtifactStore S3Bucket
  # ------------------------------ #
  ArtifactStoreBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Join ['-', [!Ref 'TagOwner', !Ref 'TagApplication', !Ref 'TagEnv', !Ref 'TagUser', 'artifact-bucket']]
      VersioningConfiguration:
        Status: Enabled
      PublicAccessBlockConfiguration:
        BlockPublicAcls: true
        BlockPublicPolicy: true
        IgnorePublicAcls: true
        RestrictPublicBuckets: true
      LifecycleConfiguration:
        Rules:
          - ExpirationInDays: !Ref ExpirationInDays
            Id: !Join ['-', [!Ref 'TagOwner', !Ref 'TagApplication', !Ref 'TagEnv', !Ref 'TagUser', 'retain-rule']]
            Status: Enabled
      Tags:
        - Key: Name
          Value: !Join ['-', [!Ref 'TagOwner', !Ref 'TagApplication', !Ref 'TagEnv', !Ref 'TagUser', 'artifact-bucket']]
        - Key: Application
          Value: !Ref TagApplication
        - Key: Env
          Value: !Ref TagEnv
        - Key: Owner
          Value: !Ref TagOwner
        - Key: User
          Value: !Ref TagUser
  # ------------------------------ #
  # CodePipeline SNSTopic
  # ------------------------------ #
  CodePipelineSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      TopicName: !Join ['-', [!Ref 'TagOwner', !Ref 'TagApplication', !Ref 'TagEnv', !Ref 'TagUser', 'approve-topic']]
      Subscription:
        - Endpoint: !Ref Email
          Protocol: email
      Tags:
        - Key: Name
          Value: !Join ['-', [!Ref 'TagOwner', !Ref 'TagApplication', !Ref 'TagEnv', !Ref 'TagUser', 'approve-topic']]
        - Key: Application
          Value: !Ref TagApplication
        - Key: Env
          Value: !Ref TagEnv
        - Key: Owner
          Value: !Ref TagOwner
        - Key: User
          Value: !Ref TagUser
  # ------------------------------ #
  # CodePipeline
  # ------------------------------ #
  MyPipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: !Join ['-', [!Ref 'TagOwner', !Ref 'TagApplication', !Ref 'TagEnv', !Ref 'TagUser', 'cfn-pipeline']]
      RoleArn: !GetAtt PipelineRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactStoreBucket
      Stages:
        # ------------------------------ #
        # Source Stage
        # ------------------------------ #
        - Name: Source
          Actions:
            - Name: TemplateSource
              ActionTypeId:
                Category: Source
                Owner: AWS
                Version: "1"
                Provider: CodeCommit
              Configuration:
                RepositoryName: !Ref CodeCommitRepoName
                BranchName: main
              OutputArtifacts:
                - Name: TemplateSource
              Region: !Ref AWS::Region
              RunOrder: 1
        # ------------------------------ #
        # Stg Stage
        # ------------------------------ #
        - Name: StgStage
          Actions:
            - Name: CreateStack
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              InputArtifacts:
                - Name: TemplateSource
              Configuration:
                ActionMode: REPLACE_ON_FAILURE
                RoleArn: !GetAtt CFNRole.Arn
                StackName: !Ref StgStackName
                TemplateConfiguration: !Sub "TemplateSource::${StgStackConfig}"
                TemplatePath: !Sub "TemplateSource::${TemplateFileName}"
              RunOrder: '1'
            - Name: ApproveStgStack
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Provider: Manual
                Version: '1'
              Configuration:
                NotificationArn: !Ref CodePipelineSNSTopic
                CustomData: !Sub 'Do you want to create a change set against the production stack and delete the ${StgStackName} stack?'
              RunOrder: '2'
            - Name: DeleteStgStack
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              Configuration:
                ActionMode: DELETE_ONLY
                RoleArn: !GetAtt CFNRole.Arn
                StackName: !Ref StgStackName
              RunOrder: '3'
        # ------------------------------ #
        # Production Stage
        # ------------------------------ #
        - Name: PrdStage
          Actions:
            - Name: CreateChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              InputArtifacts:
                - Name: TemplateSource
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                RoleArn: !GetAtt CFNRole.Arn
                StackName: !Ref PrdStackName
                ChangeSetName: !Ref ChangeSetName
                TemplateConfiguration: !Sub "TemplateSource::${PrdStackConfig}"
                TemplatePath: !Sub "TemplateSource::${TemplateFileName}"
              RunOrder: '1'
            - Name: ApproveChangeSet
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Provider: Manual
                Version: '1'
              Configuration:
                NotificationArn: !Ref CodePipelineSNSTopic
                CustomData: !Sub 'A new change set was created for the ${PrdStackName} stack. Do you want to implement the changes?'
              RunOrder: '2'
            - Name: ExecuteChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                ChangeSetName: !Ref ChangeSetName
                RoleArn: !GetAtt CFNRole.Arn
                StackName: !Ref PrdStackName
              RunOrder: '3'

スタックの作成_CLI

  1. 任意のターミナルソフトを開き、保存しておいたYAMLテンプレートをカレントディレクトリに配置します。 teststackフォルダ内のファイルは、後ほどapprovetest-codecommit.ymlで作成したリポジトリ内にpushします。
$ ls -R
approvetest-codecommit.yml      test-stack
approvetest-codepipeline.yml

./test-stack:
prd-stack-conf.json     test-s3.yml
stg-stack-conf.json
  1. CodeCommitリポジトリの作成:以下のコマンドを実行します。
aws cloudformation create-stack --region ap-northeast-1 --stack-name approvetest-codecommit --template-body file://approvetest-codecommit.yml
  1. CodePipelineの作成:以下のコマンドを実行します。
aws cloudformation create-stack --region ap-northeast-1 --stack-name approvetest-codepipeline --template-body file://approvetest-codepipeline.yml --capabilities CAPABILITY_NAMED_IAM

スタックの作成_GUI

マネジメントコンソールでスタックを作成する手順は、以下の通りです。リンクにも詳細がございます。

  1. CloudFormation画面を開きスタックの作成>新しいソースを使用(標準)を選択
  2. テンプレートの準備完了>テンプレートファイルのアップロードを選択して、保存したymlを選択
  3. スタックに任意の名称を入力して、パラメータは環境に合わせて変更>次へを選択。※後述の手順に従う場合、デフォルト値でOK
  4. スタックオプションは特に変更せず、次へを選択
  5. レビュー画面を確認して送信を選択

動作検証

実際に、ソースコードをpushして検証していきます。

コードをmainブランチにpushする

下記の順で、コマンドを実行して、以下3つのファイルをmainブランチにpushしました。

./test-stack: prd-stack-conf.json test-s3.yml stg-stack-conf.json

$ git clone {リポジトリ名}
$ cd {リポジトリ名}
〜カレントディレクトリに必要なファイル3つを入れる〜
$ git status
$ git add .
$ git commit -m "1st commit"
$ git push origin main

ソースステージが成功しました。

続いて、TestStageでテスト用スタックが作成されました。

SNSトピックのサブスクリプションに登録したメールの宛先に下記のようなメールが届くので、リンクに飛んで承認します。

レビューを選択します。

よい感じのコメントをつけて、送信を選択します。

(レビュアーのIAMユーザー名もパイプラインの実績に残るため、実務では、適切な文言をご使用ください。。!!)

変更セットが作成されて同様にメールが届くため、同様にレビューして送信します。

今回は、作成されたS3バケットのタグをstg→prdに変更していきます。

ステージングテスト用バケットが削除され、本番用バケットが作成されました。

リソースのタグも本番用のjsonでパラメータを受け取り、正しく変更されていることを確認しました。

まとめ

今回は、承認プロセスを追加したCloudFormationスタック用のCodePipelineを作成しました。

今回のステージングテストは単純作成でしたが、Lambdaを作成してテストコードを書くなど汎用的に使用していきたいと思います。

最後までご覧いただきありがとうございました。

コメント

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