CI/CDによるIaC運用

# CI/CDによるIaC運用
Shigeyukiです。

今回は、CD/CIを利用したIaCの運用方法について、紹介したいと思います。

# IaCの運用状況
アプリのリリース頻度が高くなるため、どうしてもアプリのためのCI/CDが最優先となってしまい、IaC自体はCI/CDの対象外となり、手動運用されている方はまだ多いのではないかと思います。
CI/CDによるIaC運用に関する情報はネットには少なく、これからIaCを本番運用される人向けに、分かりやすく汎用性が高いCI/CDを解説したいと思います。


# IaCの運用に対する課題
IaCを手動運用するとした場合、初回リリースはできても、再リリースする場合はどうでしょうか。
CIとCDでの実施内容に対して人手を介すほど、ミスの発生リスクが高まったり、IaCに対するセキュリティ統制も図れなかったりすると思います。
デプロイするインフラコードに関して、一貫したセキュリティチェックやコード内容の妥当性確認や既存リソースへの影響確認が必要となります。

そこで、CloudFormationを題材にして、IaCのCI/CDで運用する方法を解説したいと思います。

# IaCをCI/CDで運用する目的
IaCもプログラムである以上、アプリのプログラムと同様にCI/CDで運用することで、インフラ整備の作業効率と安定した運用が可能となります。
そのためには、インフラコード（テンプレートファイル）はGit上で管理する必要があり、インフラコードに対する検証とデリバリーを導入することで、質の高いDevOpsが実現されることで上記の課題は解消されると考えております。

    IaCをGitなどで管理してCI/CDすることで、インフラ整備の作業効率と安定した運用
    アプリのデプロイに比べて、インフラ整備のデプロイ頻度は少なく、IaCが
     
     IaCもDevOps的に運用して作業効率を図りたい。
    
    アプリのデプロイに比べて、インフラ整備のデプロイ頻度は少なく、IaCがDevOpsされていないことが多いのではないかと思います。
    IaCをGitなどで管理してCI/CDすることで、インフラ整備の作業効率と安定した運用が可能となります。

:::note info
**Point**
インフラコードをGit管理しDevOpsで運用することにより、セキュリティ統制を図りながら品質の高いインフラの迅速な提供をします。
手操作によるミスを徹底排除します。
:::

# 全体構成：CodePipeline  +  CloudFormation
IaCをCI/CDするために、CodePipeline上でCloudFormationを利用した基本構成は以下のイメージとなります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744312/b098fcae-8c4f-3795-fc2f-6c8af0e0cef4.png)




## CI構成（CodeCommit + CodeBuild）
ここでは、全体構成のうちCI構成にフォーカスして解説します。
管理者にてコンプライアンスチェック用のテンプレートを事前に配置します。
開発者は作成したテンプレートをCodeCommitの対象ブランチにPushuします。
これにより、開発者がPushしたテンプレートは自動的に静的解析（文法チェック、コンプライアンスチェック）され、簡易的なセキュリティ統制を図ります。

:::note info
**テンプレートチェックの処理構成（参考）**
- テンプレートの文法チェック（cfn-lint）
- コンプラインスチェック（cfn-guard） 
:::
構成イメージは以下となります。
事前に管理者はソースアーティファクトに、コンプライアンスチェックとなるテンプレートをS3へアップロードしておきます。
その上で開発者はインフラコードとなるテンプレートをCodeCommitへPushします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744312/ac1e4983-bdf8-808d-20c5-7e43d4fa1826.png)

CI/CD作成テンプレートの内、CIとなる部分のイメージは以下となります。
```cfn-cicd-template.yml（CI部分：一部省略） 
AWSTemplateFormatVersion: "2010-09-09"

Description: CodeBuild Stack
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------# 
Resources:
# ------------------------------------------------------------#
# アーティファクトS3
# ------------------------------------------------------------# 
  CodeArtifactS3:
    Type: AWS::S3::Bucket
    Properties: 
      BucketEncryption: 
        ServerSideEncryptionConfiguration: 
          - ServerSideEncryptionByDefault:
              SSEAlgorithm: AES256
      BucketName: !Sub ${AWS::StackName}-${AWS::AccountId}-artifact
      OwnershipControls:
        Rules: 
          - ObjectOwnership: BucketOwnerEnforced
      PublicAccessBlockConfiguration:
        BlockPublicAcls: True
        BlockPublicPolicy: True
        IgnorePublicAcls: True
        RestrictPublicBuckets: True
      Tags:
        - Key: Name
          Value: !Sub ${AWS::StackName}-${AWS::AccountId}-artifact
# ------------------------------------------------------------#
# CodeCommit
# ------------------------------------------------------------# 
  CodeCommit:
    Type: AWS::CodeCommit::Repository
    Properties: 
      RepositoryDescription: !Ref RepositoryDescription
      RepositoryName: !Ref RepositoryName

# ------------------------------------------------------------#
# CodeBuild
# ------------------------------------------------------------# 
  CodeBuild:
    Type: AWS::CodeBuild::Project
    DependsOn:
      - CodeBuildIAMRole
    Properties: 
      Artifacts:
        Type: CODEPIPELINE
      Description: !Ref CodeBuildDescription
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:4.0
        Type: LINUX_CONTAINER
      Name: !Ref CodeBuildName
      ServiceRole: !GetAtt CodeBuildIAMRole.Arn
      Source: 
        BuildSpec: buildspec.yml
        Type: CODEPIPELINE
      Tags:
        - Key: Name
          Value: test-build

# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------# 
  EventBridgeIAMPolicy:
    Type: AWS::IAM::ManagedPolicy
    Properties: 
      ＜省略＞

  EventBridgeIAMRole:
    Type: AWS::IAM::Role
    Properties:
      ＜省略＞

  EventBridge:
    Type: AWS::Events::Rule
    Properties: 
      Description: for codepipeline
      EventPattern:
        source:
          - aws.codecommit
        detail-type:
          - 'CodeCommit Repository State Change'
        resources:
          - !GetAtt CodeCommit.Arn
        detail:
          event:
            - referenceCreated
            - referenceUpdated
          referenceType:
            - branch
          referenceName:
            - main
      Name: eventbridge-codepipeline
      State: ENABLED
      Targets: 
        - Arn: !Join 
            - ''
            - - 'arn:aws:codepipeline:ap-northeast-1:'
              - !Sub '${AWS::AccountId}:'
              - !Ref CodePipeline
          Id: CodePipeline
          RoleArn: !GetAtt EventBridgeIAMRole.Arn
```
CIのメイン処理となるビルドスペックファイルは以下となります。

```buildspec.yml（一部省略） 
version: 0.2
phases:
  install:
    runtime-versions:
      python: 3.9
    commands:
      - pip install --upgrade pip
      - pip install cfn-lint --user
      - cfn-lint -v
      - curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/aws-cloudformation/cloudformation-guard/main/install-guard.sh | sh
  build:
    commands:
      - cfn-lint --template deploy/*.yml
      - cfn-guard rulegen --template <S3パス>/check.yml --output check/check.guard
      - cfn-guard validate --data deploy/*.yml -- rulescheck/check.guard --output-format yaml --show-summary all
  artifacts:
    files:
      - "template/*"
```

ポイントは、ビルド時にコンプライアンスチェック用テンプレート(check.yml）からセキュリティチェックルールを生成して、自動チェックするところとなります。

:::note info
**Point**
サーバ側でセキュリティ統制を図るために、一元的にテンプレートに関するチェックを実施します。
:::

## CD構成（CodePipeline統合CloudFormation + 変更セット + 手動承認）
ここでは、全体構成のうちCD構成にフォーカスして解説します。
CD構成では、CI構成でテンプレートチェックされたテンプレートを使用して、CloudFormationの変更セットを生成し、変更セットに関してレビューアが問題ないか確認します。
問題がなければ、パイプラインの後続処理によりデプロイされ、問題があればパイプラインを中止し、開発者へ修正依頼メールを送信します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744312/79f54479-aa9b-08d7-a218-e899991ff67b.png)



:::note info
**継続デリバリの構成**
- 変更セットの作成
- 手動承認（IaCのレビュー作業）
- 変更セットの実施（レビューがOKの場合のみ）
:::

IaCで既存インフラを更新する場合は、既存インフラに対して**無停止・再起動・再作成**のいずれかが発生するため、変更セットによる**事前確認**が必須となります。
レビュー時にはコード内容の妥当性を確認するだけでなく、既存インフラへの影響を確認します。

:::note warn
**レビュー時確認ポイント**
・変更セットから想定外のリソース削除が発生しないか確認（**重要**）
・変更セットから想定外のリソース再起動が発生しないか確認（**重要**）
・コード内容が妥当であるか確認
:::
```cfn-cicd-template.yml（CD部分：一部省略） 
# ------------------------------------------------------------#
# Resources
# ------------------------------------------------------------# 
Resources:

# ------------------------------------------------------------#
# ArtifactStoreBucket
# ------------------------------------------------------------# 
  ArtifactStoreBucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled

# ------------------------------------------------------------#
# CodePipelineSNSTopic
# ------------------------------------------------------------# 
  CodePipelineSNSTopic:
    Type: AWS::SNS::Topic
    Properties:
      Subscription:
        - Endpoint: !Ref Email
          Protocol: email

# ------------------------------------------------------------#
# Pipeline
# ------------------------------------------------------------# 
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      ArtifactStore:
        Location: !Ref 'ArtifactStoreBucket'
        Type: S3
      DisableInboundStageTransitions: []
      Name: !Ref 'PipelineName'
      RoleArn: !GetAtt [PipelineRole, Arn]
      Stages:
        - Name: S3Source
          Actions:
            - Name: TemplateSource
              ActionTypeId:
                Category: Source
                Owner: AWS
                Provider: S3
                Version: '1'
              Configuration:
                S3Bucket: !Ref 'S3Bucket'
                S3ObjectKey: !Ref 'SourceS3Key'
              OutputArtifacts:
                - Name: TemplateSource
              RunOrder: '1'
        - Name: ProdStage
          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 DeployStackName
                ChangeSetName: !Ref ChangeSetName
                TemplatePath: "BuildArtifact::template/cfn-template.yml"
              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 ${DeployStackName} 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 DeployStackName
              RunOrder: '3'
# ------------------------------------------------------------#
# EventBridge
# ------------------------------------------------------------# 
  EventBridgeForManualAppoveReject:
    Type: AWS::Events::Rule
    Properties: 
      Description: 'for codepipeline'
      EventPattern:
        source:
          - 'aws.codepipeline'
        detail-type:
          - 'CodePipeline Action Execution State Change'
        resources:
          - 'arn:aws:codepipeline:\:\:'
        detail:
          stage:
            - 'Approve'
          action:
            - 'Approve'
          state:
            - 'FAILED'
      Name: eventbridge-manualapprove-reject
      State: ENABLED
      Targets: 
        - Arn: !Join 
            - ''
            - - 'arn:aws:codepipeline:ap-northeast-1:'
              - !Sub '${AWS::AccountId}:'
              - !Ref CodePipeline
          Id: CodePipeline
          RoleArn: !GetAtt EventBridgeIAMRole.Arn
```

# 画面イメージ
●CI構成に該当するパイプライン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744312/1f704465-0fda-7e4d-ac17-aa8c65fd981b.png)

●CD構成に該当するパイプライン
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3744312/46f3a5a2-39a2-8376-bf17-ad0cb90a957d.png)


# 発展
IaCをCI/CD構成に取り込むことで、IaCのDevOpsベースラインとなり、IaCによる運用が効率的に実施できるようになります。また、IaC毎にCI/CDを作成したり、要件に応じてCIとCDの内容を拡張しやすいと考えております。
次回移行のブログで解説してみます。

# まとめ
IaCをDevOps化させることで、インフラ整備を安定して効率的に実施できます。
本トピックは基本的な構成となるため、機能拡張のさせ方で応用が利かせやすいと考えております。
またIaCの種類に応じてCIやCDに対して機能拡張して、実務に合うようにカスタマイズできます。