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

CI/CDによるCloudFormationのスタック更新(導入編)

0
Posted at

はじめに

これまでCloudFormationのスタック作成/更新はコンソールから実施していました。
単純に更新するだけであれば、手間のかかるような作業ではありませんが、cfn-lint, cfn-guardなどによるテストを組み込みたいとなると複雑化していきます。
そのため、スタックの更新をCI/CDとして組み込んで効率化したいと思います。

今回は導入編ということで、以下の流れを試してみます。
スモールスタートのためテストのステージはありません。

  1. CodeCommitにテンプレートを同期
  2. CloudFormationの変更セットを作成
  3. 手動承認
  4. 承認された場合は変更セットを実行

cfn.drawio.png

※詳細に書くと、CodePipelineをトリガーするEventBrigeやArtifactを格納するS3バケットなども必要ですが、流れとしては上図の通りです。

実装

リソースはCloudFormationで作成をします。
テンプレート以下を使用しており、CI/CDで作成するリソースはS3バケットのみを想定しています。

cicd-pipeline.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: CICD CFn
Parameters:
  Environment:
    Type: String
    Default: prod
    AllowedValues:
      - prod
      - dev
      - dr
  CFnTemplateName:
    Type: String
    Description: The name of the CloudFormation template file in the source repository
    Default: s3.yml
    AllowedPattern: '^[a-zA-Z0-9._-]+\.yml$'
  CFnConfigurationName:
    Type: String
    Description: The name of the CloudFormation configuration file in the source repository
    Default: cfn-config.json
    AllowedPattern: '^[a-zA-Z0-9._-]+\.json$'
  CFnStackName:
    Type: String
    Description: The name of the CloudFormation stack to create or update
    Default: cicd-cfn-stack

Resources:

###########################
## CodeCommit
###########################
  CodeCommitMain:
    Type: AWS::CodeCommit::Repository
    Properties:
      RepositoryName: !Sub 'cicd-codecommit-${Environment}'
      Tags:
        - Key: Env
          Value: !Ref Environment

##############################
# S3 Bucket
##############################

  ArtifactBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName: !Sub 'cicd-artifact-bucket-${AWS::AccountId}'

##############################
# CodePipeline
##############################

  CFnPipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: cfn-pipeline
      PipelineType: V2
      RoleArn: !GetAtt CodePipelineServiceRole.Arn
      ArtifactStore:
        Type: S3
        Location: !Ref ArtifactBucket
      Stages:
        - Name: Source
          Actions:
            - Name: SourceAction
              RunOrder: 1
              RoleArn: !GetAtt PipelineSourceRole.Arn
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: CodeCommit
                Version: '1'
              Configuration:
                RepositoryName: !GetAtt CodeCommitMain.Name
                BranchName: main
                PollForSourceChanges: false
              OutputArtifacts:
                - Name: SourceOutput
        - Name: CreateChangeSet
          Actions:
            - Name: CloudFormation
              RunOrder: 1
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                StackName: !Ref CFnStackName
                ChangeSetName: pipeline-changeset
                TemplatePath: !Sub "SourceOutput::${CFnTemplateName}"
                TemplateConfiguration: !Sub "SourceOutput::${CFnConfigurationName}"
                Capabilities: CAPABILITY_AUTO_EXPAND
                RoleArn: !GetAtt CFnExecuteRole.Arn
              InputArtifacts:
                - Name: SourceOutput
        - Name: ManualApprove
          Actions:
            - Name: Approval
              RunOrder: 1
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Provider: Manual
                Version: '1'
              Configuration:
                CustomData: Please review the change set
                ExternalEntityLink: !Sub "https://${AWS::Region}.console.aws.amazon.com/cloudformation/home?region=${AWS::Region}#/stacks/"
        - Name: ExecutionChangeSet
          Actions:
            - Name: CloudFormation
              RunOrder: 1
              RoleArn: !GetAtt PipelineActionRole.Arn
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: '1'
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                StackName: !Ref CFnStackName
                ChangeSetName: changeset-by-pipeline
              InputArtifacts:
                - Name: SourceOutput

##############################
# IAM
##############################
    
  CodePipelineServiceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub CodePipeline-Service-Role-${AWS::Region}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codepipeline.amazonaws.com
            Action:
              - sts:AssumeRole
      Path: /

  CodePipelineServiceRolePolicy:
    Type: AWS::IAM::RolePolicy
    Properties:
      RoleName: !Ref CodePipelineServiceRole
      PolicyName: CodePipelineServicePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - s3:GetObject*
              - s3:GetBucket*
              - s3:List*
              - s3:PutObject*
              - s3:DeleteObject*
              - s3:Abort*
            Resource:
              - !GetAtt ArtifactBucket.Arn
              - !Sub '${ArtifactBucket.Arn}/*'
          - Effect: Allow
            Action:
              - cloudformation:CreateStack
              - cloudformation:UpdateStack
              - cloudformation:DescribeStacks
              - cloudformation:DescribeStackResources
              - cloudformation:DescribeStackEvents
              - cloudformation:GetTemplate
              - cloudformation:DescribeChangeSet
              - cloudformation:CreateChangeSet
              - cloudformation:DeleteChangeSet
              - cloudformation:ExecuteChangeSet
            Resource:
              - !Sub arn:${AWS::Partition}:cloudformation:${AWS::Region}:${AWS::AccountId}:stack/${CFnStackName}/*
          - Effect: Allow
            Action:
              - sts:AssumeRole
            Resource:
              - !GetAtt PipelineSourceRole.Arn
              - !GetAtt PipelineActionRole.Arn
          - Effect: Allow
            Action:
              - iam:PassRole
            Resource:
              - !GetAtt PipelineActionRole.Arn
              - !GetAtt PipelineSourceRole.Arn
              - !GetAtt CFnExecuteRole.Arn

  PipelineSourceRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub CodePipeline-Source-Role-${AWS::Region}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS: !GetAtt  CodePipelineServiceRole.Arn
            Action:
              - sts:AssumeRole
      Path: /

  PipelineSourceRolePolicy:
    Type: AWS::IAM::RolePolicy
    Properties:
      RoleName: !Ref PipelineSourceRole
      PolicyName: PipelineSourceRolePolicy
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Action:
              - codecommit:CancelUploadArchive
              - codecommit:GetBranch
              - codecommit:GetCommit
              - codecommit:GetRepository
              - codecommit:GetUploadArchiveStatus
              - codecommit:UploadArchive
            Resource:
              - !GetAtt CodeCommitMain.Arn
          - Effect: Allow
            Action:
              - s3:GetObject*
              - s3:GetBucket*
              - s3:List*
              - s3:PutObject*
              - s3:DeleteObject*
              - s3:Abort*
            Resource:
              - !GetAtt ArtifactBucket.Arn
              - !Sub '${ArtifactBucket.Arn}/*'

  PipelineActionRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: !Sub CodePipeline-Action-Role-${AWS::Region}
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              AWS: !GetAtt CodePipelineServiceRole.Arn
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AWSCloudFormationFullAccess
      Path: /

  CFnExecuteRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: CFn-Execute-Role
      AssumeRolePolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
            Action: sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/AmazonS3FullAccess
      Path: /

##############################
# EventBridge
##############################
  CodeCommitTriggerRole:
    Type: AWS::IAM::Role
    Properties:
      RoleName: Amazon_EventBridge_Rule_Target_CFn_Pipeline
      AssumeRolePolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Principal:
              Service: events.amazonaws.com
            Action: sts:AssumeRole
      MaxSessionDuration: 3600
  CodeCommitTriggerPolicy:
    Type: AWS::IAM::RolePolicy
    Properties:
      PolicyName: start-pipeline-policy
      RoleName: !Ref CodeCommitTriggerRole
      PolicyDocument:
        Version: '2012-10-17'
        Statement:
          - Effect: Allow
            Action:
              - codepipeline:StartPipelineExecution
            Resource:
              - !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CFnPipeline}

  PipelineTriggerRule:
    Type: AWS::Events::Rule
    Properties:
      Name: codepipeline-trigger-rule
      EventPattern: !Sub >-
        {"source":["aws.codecommit"],"detail-type":["CodeCommit Repository State
        Change"],"resources":["${CodeCommitMain.Arn}"],"detail":{"event":["referenceCreated","referenceUpdated"],"referenceType":["branch"],"referenceName":["main"]}}
      State: ENABLED
      EventBusName: default
      Targets:
        - Id: codepipeline-trigger
          Arn: !Sub arn:${AWS::Partition}:codepipeline:${AWS::Region}:${AWS::AccountId}:${CFnPipeline}
          RoleArn: !GetAtt CodeCommitTriggerRole.Arn

こちらでスタックを作成すると、以下のリソースが作成されます。

  • CodeCommit
  • CodePipeline
  • EventBridge Rule
  • S3
  • IAMロール / ポリシー

CodePipelineは作成後に初回のパイプラインが動きますが、現時点ではCodeCommitへテンプレート等の資材を格納していないため、エラーになります。
image.png

CodeCommitに必要資材を格納し、動作を確かめてみます。
必要な資材は以下の2つです。

  • テンプレートファイル:CFnTemplateNameで指定した値 (s3.ymlなど)
  • テンプレート構成ファイル:CFnConfigurationNameで指定した値(cfn-config.jsonなど)

資材は以下の内容で用意しました。
こちらはパイプラインで自動作成されるものになります。

  • s3.yml

CI/CDで作成するAWSリソースを定義したテンプレートです。

s3.yml
AWSTemplateFormatVersion: "2010-09-09"
Description: S3 Resource
Parameters:
  Environment:
    Type: String
    Default: Dev
    AllowedValues:
      - Prod
      - Dev
      - DR

Mappings:
  BucketName:
    Environment:
      Prod: prod
      Test: test
      Dev: dev

Resources:
  CommonBucket:
    Type: AWS::S3::Bucket
    Properties:
      BucketName:
        !Join
          - "-"
          - - !FindInMap [BucketName, Environment, !Ref Environment]
            - "guard"
            - "bucket"
            - !Ref AWS::Region
            - !Ref AWS::AccountId
      Tags:
        - Key: Env
          Value: !Ref Environment
  • cfn-config.json

テンプレートで使用するパラメータの設定や共通で付与するタグ、スタックポリシーの設定を定義する構成ファイルです。

cfn-config.json
{
  "Parameters" : {
    "Environment" : "Prod"
  },
  "Tags" : {
    "Department" : "Marketing"
  }
}

資材を準備したらCodeCommitにアップロードしていきます。
Gitを使用するか、面倒であればコンソールからファイルの追加でも問題ありません。
image.png

ファイルをアップロードするとパイプラインが動いています。
変更セットの作成まで完了し、手動承認のステージで保留中となっています。
image.png

CloudFormationの画面から変更セットを参照し、期待通りの差分かを確認します。
下図はCodeCommitにアップロードしたテンプレートの内容と一致するため期待通りでした。
image.png

問題がなければ「承認します」を選択し、送信します。
変更セットに問題があれば、「却下します」を選択することで、変更セットの実行を行いません。
image.png

最後のステージまですべて成功していれば、CloudFormationスタックの自動デプロイは完了です。
image.png

CloudFormation上からもスタックの作成が確認できます。
image.png

CodeCommitに同期したテンプレートへ変更をかけると、再度パイプラインが起動し、同じスタックに対する変更セットが作成されます。
以上で、CodePipelineを使用したCloudFormationのスタック更新は完了です。

料金はほとんどかからない想定ですが、今回作成したリソースは削除しておきましょう。

まとめ

今回はCodePipelineを使用したCloudFormationのスタック更新を実施しました。
ひとまず動く物は作れましたが、考慮すべき点はまだまだありそうです。
今後は以下を調査/検証できればなと思います。

  • CodeBuildによるテストの追加
  • スタックがネスト構造の際の実装方法
  • CodeCommitの更新対象による、パイプラインの起動を制御
  • CodePipelineのIAM権限について
0
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
0
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?