CloudFormationテンプレートを1からしっかり理解しながらECS on Fargateなアプリを自動構築する(後編)

# 前提条件
[前編](https://qiita.com/neruneruo/items/06b073f3846803f415e5)で作ったALBとECS、[中編](https://qiita.com/neruneruo/items/d62140f29f067b28925b)で作ったデプロイの設定をもとにCodePipelineを使ってCI/CDパイプラインを作るので、それぞれ理解しておく。

前回から引き続き、記事中には、備忘のためにリファレンスに書かれていないデフォルト値を整理しておくが、2020年3月時点の情報であり、後でAWSが仕様を変えたとしても追従する予定はないので、挙動が違ったらリファレンスを見直してほしい。あと、今回の構成(ECS on FargateのBlue/Greenデプロイメント)以外の構成以外のデフォルト値まで調査はしていないのであしからず。

# CI/CDパイプラインを作成する

## IAMロールの作成
パイプライン中で作成しても良いのだけど、ロールが増えすぎてもなので、以下のような信頼関係のポリシで、CodePipeline, CodeCommit, CodeBuild, S3 あたりのもろもろにアクセス可能なポリシをアタッチしたIAMロールを作っておく(実際は、S3なんかはアクセス可能なバケットを絞るとかはあるのだろうけど)。

```JSON
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "codepipeline.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}
```

## パラメータの設定
カスタマイズ箇所を極力減らすために、プロジェクト名をParameterに指定すれば書き換える箇所が減るといった考えにしておく。S3とそれ以外で別パラメータにしているのは、S3のバケット名に大文字が使えないから。パラメータグループをメタ設定に入れた方がキレイだけど、細かいことは一旦気にしない。
ApplicationName と DeploymentGroupName は、本当ならCloudFormation中でデプロイまで定義できれば !Ref できるので不要なパラメータなのだけど、ECSのBlue/Greenデプロイメントが未対応で、前回小細工をしているために入れておく(リソース定義に直接書き込んでしまっても良いけど)。

```YAML
Parameters:
  Prefix:
    Description: "Project name prefix"
    Type: "String"
    Default: "CFn-test"
  PrefixS3:
    Description: "Project name prefix for S3 Bucket"
    Type: "String"
    Default: "cfn-test"
  ApplicationName:
    Description: "ApplicationName on CodeDeploy"
    Type: "String"
    Default: "CFn-test-Application"
  DeploymentGroupName:
    Description: "DeploymentGroupName on CodeDeploy"
    Type: "String"
    Default: "CFn-test-DeploymentGroup"
```

## S3バケットの作成
CI/CDパイプライン中でアーティファクトを格納するためのS3バケットが必要なので作る。

例によって、どのプロパティも「必須：いいえ」ではあるが、何も指定しないとエラーになるので、何かしらは必須になるようだ。
最低限は、以下のような感じでバケット名があれば良い。

```YAML
Resources:
  S3BUCKET: 
    Type: AWS::S3::Bucket
    Properties: 
      BucketName: !Sub ${PrefixS3}-artifact-bucket
```
S3は他にもプロパティたくさんある。
↓にあるように、PublicAccessBlockConfiguration がデフォルトでオフになるのは危ないんじゃない？と思いつつ、今回はテストなので一旦無視。ちゃんと実運用するときは考えよう。

| プロパティ | デフォルト値 |
|:--|:--|
| AccelerateConfiguration | 停止 |
| AccessControl | よく分からない… |
| AnalyticsConfigurations | なし |
| BucketEncryption | 無効 |
| BucketName | AWS払い出しの名前 |
| CorsConfiguration | なし？ |
| InventoryConfigurations | 無効 |
| LifecycleConfiguration | ルールなし |
| LoggingConfiguration | 無効 |
| MetricsConfigurations | なし |
| NotificationConfiguration | 通知なし |
| ObjectLockConfiguration | すべてのオブジェクトに適用 ※リファレンス記載 |
| ObjectLockEnabled | 無効 |
| PublicAccessBlockConfiguration | オフ |
| ReplicationConfiguration | ルールなし |
| Tags | aws:cloudformation:stack-id/aws:cloudformation:stack-name/aws:cloudformation:logical-id|
| VersioningConfiguration | 無効 |
| WebsiteConfiguration | 無効 |

## CodeBuildのプロジェクト作成
CodeBuildもプロパティが大量にあるので設定が大変…。

```YAML
  CODEBUILD:
    Type: AWS::CodeBuild::Project
    Properties: 
      Name: !Sub ${Prefix}-build-project
      Source: 
        Type: CODEPIPELINE
        BuildSpec: buildspec_container.yml
      Artifacts: 
        Type: CODEPIPELINE
      Environment: 
        Type: LINUX_CONTAINER
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:3.0-19.11.26
        PrivilegedMode: true
      Cache: 
        Type: LOCAL
        Modes:
          - LOCAL_CUSTOM_CACHE
      ServiceRole: !Sub arn:aws:iam::${AWS::AccountId}:role/service-role/[CodeBuildの適切なロール]
```

| プロパティ | デフォルト値 |
|:--|:--|
| Artifacts | 必須 ※↓のアーティファクトで詳述 |
| BadgeEnabled | Sourceのプロパティが ```Type: CODEPIPELINE``` の場合は指定してはいけない |
| Cache | キャッシュを使用しない |
| Description | なし |
| EncryptionKey | なし |
| Environment | 必須 ※↓の環境で詳述 |
| LogsConfig | なし |
| Name | AWS払い出しの名前 |
| QueuedTimeoutInMinutes | 480分 |
| SecondaryArtifacts | なし |
| SecondarySources | なし |
| SecondarySourceVersions | なし |
| ServiceRole | 必須 |
| Source | 必須 ※↓のソースで詳述 |
| SourceVersion | 最新のバージョン ※リファレンス記載 |
| Tags | なし |
| TimeoutInMinutes | 60分(リファレンス記載) |
| Triggers | よく分からない |
| VpcConfig | なし |

アーティファクトのプロパティについては以下。
今回は、ビルド生成物はECSに入れるものの、taskdef.json があるので、その格納先が必要。
ただし、```Type: CODEPIPELINE``` の場合は、自動でいろいろやってくれるので、細かいことは考えなくてOK。

| プロパティ | デフォルト値 |
|:--|:--|
| ArtifactIdentifier | デフォルトのKMSのキー |
| EncryptionDisabled | ```Type: CODEPIPELINE``` の場合は無視 |
| Location | ```Type: CODEPIPELINE``` の場合は無視 |
| Name | ```Type: CODEPIPELINE``` の場合は無視 |
| NamespaceType | ```Type: CODEPIPELINE``` の場合は無視 |
| OverrideArtifactName | false |
| Packaging | ```Type: CODEPIPELINE``` の場合は無視 |
| Path | ```Type: CODEPIPELINE``` の場合は無視 |
| Type | 必須 |

ソースのプロパティについては以下。
これも、アーティファクト同様、```Type: CODEPIPELINE``` の場合は、自動でいろいろやってくれるので、細かいことは考えなくてOK。

| プロパティ | デフォルト値 |
|:--|:--|
| Auth | よく分からない |
| BuildSpec | buildspec.yml |
| GitCloneDepth | ```Type: CODEPIPELINE``` の場合は無視 |
| GitSubmodulesConfig | ```Type: CODEPIPELINE``` の場合は無視 |
| InsecureSsl | ```Type: CODEPIPELINE``` の場合は無視 |
| Location | ```Type: CODEPIPELINE``` の場合は無視 |
| ReportBuildStatus | ```Type: CODEPIPELINE``` の場合は設定してはいけない |
| SourceIdentifier | なし？ |
| Type | 必須 |

環境のプロパティについては以下。
Image って何設定すりゃいいのよ？という感じだが、マネコンから作成したビルドプロジェクトの環境イメージ(↓これ)からコピペすれば良さそう。

![キャプチャ5.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/111314/0325d4d1-4d90-6b05-d930-478cccf8ec42.png)

あと、PrivilegedModeは日本語マネジメントコンソールの「特権付与」のチェックボックスに該当するもので、忘れるとDockerのビルドができないので注意。

| プロパティ | デフォルト値 |
|:--|:--|
| Certificate | なし |
| ComputeType | 必須 |
| EnvironmentVariables | なし |
| Image | 必須 |
| ImagePullCredentialsType | よく分からない |
| PrivilegedMode | なし |
| RegistryCredential | なし |
| Type | 必須 |

## CodePipelineのパイプライン作成
CodePipelineはプロパティのサブ要素が複雑。
今回、デフォルト値はサブプロパティまで書いていく(必要な部分のみ)。
RoleArnについては↑に書いた通り、あらかじめ作成しておいたものを使用する。

| プロパティ | デフォルト値 |
|:--|:--|
| ArtifactStore/ArtifactStores | どちらか必須。指定なしの方は無効 |
| DisableInboundStageTransitions | なし |
| Name | AWS払い出しの名前 |
| RestartExecutionOnUpdate |  |
| RoleArn | 必須 |
| Stages | 必須(↓のStageDeclaration) |

ArtifactStore のプロパティについては以下。

| プロパティ | デフォルト値 |
|:--|:--|
| EncryptionKey | よく分からない |
| Location | 必須 |
| Type | 必須 |

StageDeclaration のプロパティについては以下。

| プロパティ | デフォルト値 |
|:--|:--|
| Actions | 必須(↓のActionDeclaration) |
| Blockers | よく分からない |
| Name | 必須 |

ActionDeclaration のプロパティについては以下。

| プロパティ | デフォルト値 |
|:--|:--|
| ActionTypeId | 必須 |
| Configuration | 必須でないと書いてありながら、たぶん↓のConfigurationの設定をしないと動かない気がする |
| InputArtifacts | なし |
| Name | 必須 |
| OutputArtifacts | なし |
| Region | よく分からない |
| RoleArn | よく分からない。ユーザーガイドには「宣言されたアクションを実行する IAM サービスロールの ARN。これは、パイプラインの roleArn を介することが想定されています。」とあるのでパイプラインと同じARNを入れたらエラーになった。 |
| RunOrder | 必須 |

ActionTypeId のプロパティについては以下。
階層が深い……
ちなみに、日本語のドキュメントだとCategoryに指定可能な値が訳されてしまっていて設定可能な値が分からないという罠がある。英語に切り替えよう。

| プロパティ | デフォルト値 |
|:--|:--|
| Category | 必須 |
| Owner | 必須  |
| Provider | 必須 |
| Version | 必須 |

そして、Configuration の設定内容が超曲者で、頑張ってユーザーガイドを掘り下げていって[ここ](https://docs.aws.amazon.com/ja_jp/codepipeline/latest/userguide/action-reference-CodeCommit.html#action-reference-CodeCommit-config)に辿り着かないと何を設定したら良いか分からないという……。

ActionTypeId の値によってパラメータが違うので、今回はCodeCommit、CodeBuild、CodeDeployToECS(CodeDeployと書きたいところだが、ECSのBlue/GreenデプロイメントはプロバイダをCodeDeployToECSに指定する) それぞれについて書いておく。

CodeCommit については以下。

| プロパティ | デフォルト値 |
|:--|:--|
| RepositoryName | 必須 |
| BranchName | 必須 |
| PollForSourceChanges | よく分からない |

CodeBuild については以下。
PrimarySource は、```Type: CODEPIPELINE``` の場合は不要っぽい？
マネジメントコンソールの場合は、そもそもCodePipelineをソースに選択できないので、勝手がよく分からない。

| プロパティ | デフォルト値 |
|:--|:--|
| ProjectName | 必須 |
| PrimarySource | なし |
| EnvironmentVariables | なし |

CodeDeployToECS については以下。

| プロパティ | デフォルト値 |
|:--|:--|
| ApplicationName | 必須 |
| DeploymentGroupName | 必須 |
| TaskDefinitionTemplateArtifact | 必須 |
| AppSpecTemplateArtifact | 必須 |
| AppSpecTemplatePath | 必須 |
| TaskDefinitionTemplatePath | taskdef.json |
| Image1～4ArtifactName | なし |
| Image1～4ContainerName | なし |

さて、ここまでをまとめると、パイプライン制御のリソース定義は↓こうなる。RoleArnはあらかじめ作っておいたロールのARNを指定する。
appspec_container.yml は、Appspecファイルに合わせる。今回、自分が作った環境ではコンテナ用とそうでないもので定義を分けたために標準的な名前から外しているため、別名を指定した。

```YAML
  PIPELINE:
    Type: AWS::CodePipeline::Pipeline
    Properties: 
      Name: !Sub ${Prefix}-pipeline
      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: testProject
                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: CodeDeployToECS
                Version: 1
              Configuration:
                ApplicationName: !Sub ${ApplicationName}
                DeploymentGroupName: !Sub ${DeploymentGroupName}
                AppSpecTemplateArtifact: SourceArtifact
                AppSpecTemplatePath: appspec_container.yml
                TaskDefinitionTemplateArtifact: SourceArtifact
                Image1ArtifactName: BuildArtifact
                Image1ContainerName: IMAGE1_NAME
              InputArtifacts: 
                - Name: SourceArtifact
                - Name: BuildArtifact
```

[中編](https://qiita.com/neruneruo/items/d62140f29f067b28925b)までのCloudFormationテンプレートやCLIを実行するシェルスクリプトを流した後に、今回のテンプレートを流し込めば完璧だ！
ALBの設定からECSの設定からパイプライン作成までを「ほぼ」自動化できた！やったー！