ECSデプロイ CodePipelineとGitHubを使った手動リリース実行

概要
-

ECSで以下のようなざっくり要件でデプロイ準備してみた記録です。

- ECRにデプロイしたいタグイメージは事前に出来上がっている
- リリース担当者が任意のタイミングで手動デプロイ開始できる
- GitHubの特定ファイル更新でwebhookからデプロイ開始する形にもできる

出来上がりイメージ
-

![deploy.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/55198/03e23463-95da-153e-f1fc-bac93aee8a8e.png)


ざっくりTerraform記載例
-

以下の環境で検証。
terraform v0.12.29
aws provider v2.69.0
github provider v2.9.2

ざっくり値はsampleになっていますが、イメージ的にはこんな感じ。

```javascript

resource "aws_codepipeline" "sample" {
  name     = "sample"
  role_arn = "arn:aws:iam::XXXXXXXXXXXX:role/SampleRole"

  artifact_store {
    type     = "S3"
    location = "sample-bucket"
  }

  stage {
    name = "Source"

    action {
      name             = "Source"
      category         = "Source"
      owner            = "ThirdParty"
      provider         = "GitHub"
      version          = "1"
      output_artifacts = ["SourceArtifact"]
      namespace        = "SourceVariables"

      configuration = {
        Owner                = "sample"
        Repo                 = "sample-repo"
        Branch               = "master"
        OAuthToken           = aws_ssm_parameter.github_personal_access_token.value
        PollForSourceChanges = "false"
      }
    }
  }

  stage {
    name = "Deploy"

    action {
      name            = "Deploy"
      category        = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeployToECS"
      version         = "1"
      input_artifacts = ["SourceArtifact"]
      namespace       = "DeployVariables"

      configuration = {
        AppSpecTemplateArtifact        = "SourceArtifact"
        AppSpecTemplatePath            = "appspec.yml"
        ApplicationName                = "AppECS-cluster-sample-service"
        DeploymentGroupName            = "DgpECS-cluster-sample-service"
        TaskDefinitionTemplateArtifact = "SourceArtifact"
        TaskDefinitionTemplatePath     = "taskdef.json"
      }
    }
  }

  lifecycle {
    ignore_changes = [
      stage[0].action[0].configuration
    ]
  }
}

resource "random_id" "sample" {
  keepers = {
    codepipeline_name = aws_codepipeline.sample.name
  }

  byte_length = 32
}

resource "aws_codepipeline_webhook" "sample" {
  name            = "sample-webhook"
  authentication  = "GITHUB_HMAC"
  target_action   = "Source"
  target_pipeline = aws_codepipeline.sample.name

  authentication_configuration {
    secret_token = random_id.sample.hex
  }

  filter {
    json_path    = "$.ref"
    match_equals = "refs/heads/{Branch}"
  }

  filter {
    json_path    = "$.head_commit.modified.*"
    match_equals = "placeholder-file"
  }
}

resource "github_repository_webhook" "sample" {
  repository = "sample-repo"

  configuration {
    url          = aws_codepipeline_webhook.sample.url
    content_type = "json"
    insecure_ssl = false
    secret       = random_id.sample.hex
  }

  events = ["push"]
}
```

webhook部分の制御
-

CodePipelineがGitHubからのwebhook情報を受け取って判定している箇所は以下部分。
AWSのドキュメントだと、Branch部分が指定されているfilter例しか載っていないけれど、GitHubからはwebhook時に他の情報もやってくるので、それを使って判定条件にする。

```javascript

resource "aws_codepipeline_webhook" "sample" {
〜略〜
  filter {
    json_path    = "$.ref"
    match_equals = "refs/heads/{Branch}"
  }

  filter {
    json_path    = "$.head_commit.modified.*"
    match_equals = "placeholder-file"
  }
}
```

上記だと、`master`ブランチに変更があって、`placeholder-file`というファイルに変更があった場合、CodePipeline側のデプロイが開始するような記述。

`placeholder-file`が存在していなければ自動でデプロイは開始されないので手動開始が実現できる。
`placeholder-file`を`taskdef.json`に変えておけば、イメージを更新したタスク定義でデプロイが自動開始される。

`match_equals`の部分、正規表現使えると嬉しいケースがありそうだけど現状使えない模様。
https://stackoverflow.com/questions/56767494/aws-codepipeline-webhook-filter
https://docs.aws.amazon.com/codepipeline/latest/APIReference/API_WebhookFilterRule.html

webhookのシークレット情報
-

GitHubとCodePipelineのwebhook連携で使われているシークレット情報、どういう文字列でいいのかな？と思ってGitHubのドキュメントを見てもあまり具体的に載っていなかったので、サンプルを踏襲してhex文字列を使ってみています。
https://docs.github.com/en/developers/webhooks-and-events/securing-your-webhooks

```javascript

resource "random_id" "sample" {
  keepers = {
    codepipeline_name = aws_codepipeline.sample.name
  }

  byte_length = 32
}
```

CodePipelineに設定するGitHub Personal Access Tokenの制御
-

検証したバージョンのaws provider(v2.69.0)だと、`OAuthToken`部分の更新が毎回走ってしまうので、無理矢理感あるけど以下のように制御入れてある感じ。

```javascript

  lifecycle {
    ignore_changes = [
      stage[0].action[0].configuration
    ]
  }
```

このissueで同じ課題持っているなーと思っていたら、aws provider v3.0.0で修正が入って、この記述を入れなくても`OAuthToken`の制御がうまくいくようになった。
https://github.com/terraform-providers/terraform-provider-aws/issues/2854


参考にさせて貰った情報
-

ECSデプロイについて全般については以下が参考になりました。
https://dev.classmethod.jp/articles/ecs-deploy-all/

GitHubとCodePipeleのwebhook挙動については以下が参考になりました。
https://qiita.com/techneconn/items/cff3e76301006f42c78f