こまぶろ

技術のこととか仕事のこととか。

S3へのソースコードの配置をトリガーにしてCodePipelineを動かす設定をTerraformで書く

Amazon S3 へのソースコードの配置をトリガーにして AWS CodePipeline を動かす方法はいくつかあります。

  • S3 を CodePipeline からポーリングして変更を検知する
  • S3 への操作を CloudTrail で検知してイベントを発行して EventBridge 経由で CodePipeline を動作させる
  • S3 からイベントを EventBridge に送って CodePipeline を動作させる

それぞれについて Terraform で書く場合の要点をまとめておきます。

なお、Terraform AWS Provider はv3を前提にした記述になっています(3番目の方法についてはv3.74.0以上が必要です)。v4などを使う場合は適宜読み替えてください。

また、CodePipeline を動かす部分に焦点を置いているため、

  • S3 にどうソースコードを配置するか
  • CodePipeline をどう設定するか(トリガーされたあとの挙動)

については省略するか簡易にしか記述しません。

S3 を CodePipeline からポーリングして変更を検知する

1番目の方法で、以前から利用できる方法です。勝手にポーリングしてくれるので簡単。

PollForSourceChanges を true にすることで有効化できます。

たとえば以下のように記述します。

resource "aws_codepipeline" "my_pipeline" {
  name     = "my-pipeline"
  role_arn = aws_iam_role.my_pipeline_role.arn

  artifact_store {
    location = aws_s3_bucket.my_source.bucket
    type     = "S3"
  }

  stage {
    name = "Source"
    action {
      category = "Source"
      configuration = {
        PollForSourceChanges = "true" // ここが肝
        S3Bucket             = aws_s3_bucket.my_source_s3.bucket
        S3ObjectKey          = "my_artifact.zip"
      }
      name             = "Source"
      output_artifacts = ["SourceArtifact"]
      owner            = "AWS"
      provider         = "S3"
      run_order        = "1"
      version          = "1"
    }
  }
  stage {
    name = "Deploy"
    action {
      category = "Deploy"
      configuration = {
        ApplicationName     = aws_codedeploy_app.my_deployment.name
        DeploymentGroupName = aws_codedeploy_deployment_group.my_deployment.deployment_group_name
      }
      input_artifacts = ["SourceArtifact"]
      name            = "Deploy"
      owner           = "AWS"
      provider        = "CodeDeploy"
      run_order       = "1"
      version         = "1"
    }
  }
}

S3 への操作を CloudTrail で検知してイベントを発行して EventBridge 経由で CodePipeline を動作させる

2番目のやり方です。前掲の公式ドキュメントで、1番目の方法よりも推奨されている方法です。イベントドリブンなので、ポーリングによる方法に比べて迅速に CodePipeline を起動することができます。

PollForSourceChanges

Required: No

PollForSourceChanges controls whether CodePipeline polls the Amazon S3 source bucket for source changes. We recommend that you use CloudWatch Events and CloudTrail to detect source changes instead. For more information about configuring CloudWatch Events, see Update pipelines for push events (Amazon S3 source) (CLI) or Update pipelines for push events (Amazon S3 source) (AWS CloudFormation template).

難点は設定が面倒なことで、AWS CloudTrail という典型的には監査などの目的で利用されるサービスを間に噛ませる必要があります。以下のような流れになります。

  • S3 上のオブジェクトへの操作
  • CloudTrail が検知
  • CloudTrail がイベントを発行
  • EventBridge で定義しておいたルールに基づいて CodePipeline を起動

こちらの方法を採用する場合は、二重でパイプラインが起動するのを避けるため、先述の PollForSourceChanges を false に設定しておく必要があります。

1番目の方法で用いるリソースにさらに追加で以下のリソースが必要になります。

  • CloudTrail aws_cloudtrail
  • CloudTrail のログ出力先となる S3 バケット aws_s3_bucket
    • バケットポリシーで CloudTrail からの読み書きを許可する必要があります
    • 説明は省略します
  • EventBridge のルール定義 aws_cloudwatch_event_rule
  • EventBridge のイベントターゲット定義 aws_cloudwatch_event_target
  • EventBridge が CodePipeline を起動するためのIAMロール aws_iam_role
    • 説明は省略します

CloudTrail はたとえば以下のように記述します。

resource "aws_cloudtrail" "start_my_pipeline" {
  name           = "start-my-pipeline"
  s3_bucket_name = aws_s3_bucket.start_codepipeline.id // CloudTrailがログを出力する先のS3バケットを指定

  event_selector {
    read_write_type = "WriteOnly"

    data_resource {
      type   = "AWS::S3::Object"
      values = ["arn:aws:s3:::my_source_s3"] // ソースコードを配置する S3 を指定する
    }
  }
}

EventBridge のルールとターゲットはそれぞれ以下のように記述します。

resource "aws_cloudwatch_event_rule" "start_my_pipeline" {
  name = "start-pipeline-from-s3-rule"
  event_pattern = <<EOF
{
  "source": ["aws.s3"],
  "detail-type": ["AWS API Call via CloudTrail"],
  "detail": {
    "eventSource": ["s3.amazonaws.com"],
    "eventName": ["PutObject", "CompleteMultipartUpload", "CopyObject"],
    "requestParameters": {
      "bucketName": ["${aws_s3_bucket.my_source_s3.bucket}"],
      "key": ["my_artifact.zip"]
    }
  }
}
EOF
}
resource "aws_cloudwatch_event_target" "start_pipeline" {
  rule     = aws_cloudwatch_event_rule.start_my_pipeline.name
  arn      = aws_codepipeline.my_pipeline.arn
  role_arn = aws_iam_role.my_pipeline_role_for_event_bridge.arn
}

S3からイベントを EventBridge に送って CodePipeline を動作させる

3番目のやり方です。2021年11月のAWSのアップデートでサポートされた方法です。こちらでも PollForSourceChanges は false に設定しておく必要があります。

S3 へのソースコードの配置のイベントを CloudTrail を介することなく直接 EventBridge に送ることができるようになりました。これにより、2番目の方法で必要だった CloudTrail 関係のリソースが不要になります。 CloudTrall は細かく設定ができるのですが、リソースの数などについての制約事項があるため、個人的にはあまりパイプラインのために利用するのは嬉しくないのでは?と感じています。とはいえ、 EventBridge への S3 からのイベント発行が CloudTrail でやる場合に比べて増えるというデメリットもこちらにはあります。

1番目の方法との差分として必要なリソースは、以下になります。

  • EventBridge のルール定義 aws_cloudwatch_event_rule
    • 少し設定の書き方が変わります
  • EventBridge のイベントターゲット定義 aws_cloudwatch_event_target
    • 2番目の方法と同じ
  • EventBridge が CodePipeline を起動するためのIAMロール aws_iam_role
    • 2番目の方法と同じ
    • 説明は省略します
  • S3 から EventBridge への通知設定 aws_s3_bucket_notification

aws_cloudwatch_event_rule はたとえば以下のように記述します。

resource "aws_cloudwatch_event_rule" "start_my_pipeline" {
  name = "start-pipeline-from-s3-rule"
  event_pattern = <<EOF
{
  "source": ["aws.s3"],
  "detail-type": ["Object Created"],
  "detail": {
    "eventSource": ["s3.amazonaws.com"],
    "eventName": ["PutObject", "CompleteMultipartUpload", "CopyObject"],
    "bucket": {
      "name": ["${aws_s3_bucket.my_source_s3.bucket}"]
    },
    "object": {
      "key": ["my_artifact.zip"]
    },
    "reason": ["PutObject", "CompleteMultipartUpload", "CopyObject"]
  }
}
EOF
}

また、 S3 から EventBridge へのイベント発行はデフォルトでは無効になっているので、 aws_s3_bucket_notification を追加する必要があります。

resource "aws_s3_bucket_notification" "bucket_notification" {
  bucket      = aws_s3_bucket.my_source_s3.id
  eventbridge = true
}

なお、 aws_s3_bucket_notification リソースの eventbridgeプロパティは、Terraform AWS provider の v3.74.0 以降でサポートされています。

Release v3.74.0 · hashicorp/terraform-provider-aws · GitHub

まとめ

この記事を執筆している2022年8月現在では、公式ドキュメント上は2番目の方法が推奨されているのですが、仕組みのシンプルさを欠いている印象が強く、ゆくゆくは3番目の方法や、それを更に発展させた方法が標準になっていくのではないかなと感じました。

参考資料