TerraformのCDパイプラインをCodeシリーズで作ってみる(CI/CDアカウントにパイプラインを集約パターン)

2023.08.14

TerraformのCDパイプラインをCodeシリーズを使って作りたいことがありました。

TerraformのCDパイプラインを一つのAWSアカウントに集約する構成で、TerraformでCDパイプライン用のCodeシリーズを構築してみました。

terraform-sample/codepipeline-terraform at main · msato0731/terraform-sample

構成図

今回作成した構成は以下です。

CI/CDパイプライン用のAWSアカウントとワークロード用のAWSアカウントを分けている構成です。

Codeシリーズに関しては、CI/CDパイプライン用のAWSアカウントに配置します。

ワークロード用のAWSアカウントにパイプライン用のIAMロールを用意して、CI/CD用のCodeシリーズではそのIAMロールをAssume Roleしてterraform applyterraform planを行います。

CI/CDパイプライン用のAWSアカウントとワークロードアカウントを分けるメリット・デメリット

CI/CDパイプライン用途でAWSアカウントを分ける・分けないは一長一短あり組織によって適切な構成が変わってくると思います。

分ける場合のメリットは以下があると思います。

  • 一つのAWSアカウント上でCI/CDの結果が確認できる
  • 通知設定等を集約できる

逆にデメリットとしては、以下があります。

  • アカウントをまたぐ必要があり、設定が複雑になる

例えば、デプロイのためにCI/CDアカウント内のCodeBuild上でワークロード用ののIAMロールの引受が必要になります。

信頼ポリシー等でクロスアカウントの設定が必要になります。CodeBuildの設定ファイル(buildspce.yaml)にも上記の処理の記載が必要です。

やってみる

実際に上記の構成をTerraformで作成してみます。

コードは以下になります。

terraform-sample/codepipeline-terraform at main · msato0731/terraform-sample

この手順を実施するには、AWSアカウントが2つ必要です。

アカウント名 作成するインフラ
CI/CD用AWSアカウント デプロイ用のパイプライン
ワークロード用AWSアカウント ワークロード用のインフラ、Terraform実行用のIAMロール

ディレクトリ構成は以下になっています。

├── cicd # CI/CD用AWSアカウント
│   └── pipeline
└── workload # ワークロード用AWSアカウント
    ├── iam-role
    └── infra

パイプライン作成(CI/CD用AWSアカウント)

パイプラインを作成します。

terraform.tfvarsのアカウントIDの部分を書き換えます。

$ cd cicd/pipeline
$ cp terraform.tfvars_sample terraform.tfvars
$ vi terraform.tfvars

terraform.tfvars

target_aws_account_id = "<Pipelineを使用してリソースをデプロイするAWSアカウントID>" # 書き換える

CodeBuild上でAssumeRoleするために、CodeBuildの環境変数でワークロード用AWSアカウントのTerraform実行用IAM RoleのARNの設定が必要です。

しかし、Terraform実行用IAM Roleには信頼ポリシーでCodePipelineのIAMロールを許可する必要があります。CodePipelineのIAMロールはパイプライン用のTerraformで作成します。

そのため、異なるStateファイル間で相互参照が発生してしまいます。

強引ですが、今回はTerraform実行用のIAMロールの名前は決まっているものとしてワークロードアカウントのアカウントIDだけを渡しています。

cidd/pipeline/locals.tf

locals {
  name_prefix                  = "codepipeline-terraform"
  terraform_execution_role_arn = "arn:aws:iam::${var.target_aws_account_id}:role/terraform-execution-role"
}

terraform applyを実行してリソースを作成します。

$ terraform init
$ terraform apply
#省略
Outputs:

pipeline_role_arn = "arn:aws:iam::1234567890:role/codepipeline-terraform-codepipeline"

OutPutsでPipelineのIAMロールARNが表示されます。

Terraform実行用IAMロール作成時に必要なため、メモしておきます。

Terraform実行用IAMロールの作成(ワークロード用AWSアカウント)

Terraform実行用のIAMロールを用意します。このIAMロールはCDパイプラインがTerraformを実行する際に使用します。

tfvarsファイルを書き換えます。

$ cd workload/iam-role
$ cp terraform.tfvars_sample terraform.tfvars
$ vi terraform.tfvars
pipeline_role_arn = "<前の手順でOutputsにでてきたIAMロールARNに置き換える>"

terarform applyでリソースを作成します。

$ terraform init
$ terraform apply

ワークロード用インフラ作成(ワークロード用AWSアカウント)

ワークロード用のインフラを作成します。(※)

テスト用にSQSのキューを作成します。

パイプライン上でインフラを実行するために、Stateファイルはリモートで管理する必要があります。

S3とDynamoDBを用意して、コード上の該当箇所を修正してください。

workload/infra/main.tf

terraform {
  backend "s3" {
    bucket         = "<バケット名>" # 置き換えが必要
    key            = "codepipeline-terraform-infra.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "<DynamoDBテーブル名>" # 置き換えが必要
  }
}

resource "aws_sqs_queue" "example" {
  name = "example-queue"
}
$ cd workload/infra 
$ terraform init
$ terraform apply

テスト用のインフラリソースのSQSが作成されます。

※パイプライン完成後は、このステップはパイプライン上で実行可能です。今回のコードは書き換えが必要な箇所があるため、動作確認のため一度ローカルで実行します。

buildspecは以下の内容です。

buildspec_plan.yml

version: 0.2

phases:
  install:
    runtime-versions:
      python: 3.9
    commands:
      - "curl -s -qL -o terraform.zip https://releases.hashicorp.com/terraform/${TF_VERSION}/terraform_${TF_VERSION}_linux_amd64.zip"
      - "unzip -o terraform.zip"
      - "mv terraform /bin"
      - "rm terraform.zip"
  pre_build:
    commands:
      - credentials=$(aws sts assume-role --role-arn ${TERRAFORM_EXECUTION_ROLE_ARN} --role-session-name "plan" | jq .Credentials)
      - export AWS_ACCESS_KEY_ID=$(echo ${credentials} | jq -r .AccessKeyId)
      - export AWS_SECRET_ACCESS_KEY=$(echo ${credentials} | jq -r .SecretAccessKey)
      - export AWS_SESSION_TOKEN=$(echo ${credentials} | jq -r .SessionToken)
  build:
    commands:
      - "cd ${CODEBUILD_SRC_DIR}/${CODE_SRC_DIR}"
      - "echo ## TERRAFORM PLAN : Generate the Terraform Plan"
      - "terraform init"
      - "terraform plan"
artifacts:
  files:
    - '**/*'

planとapplyでファイルを分けています。

ワークロード用のIAMロールから引き受けて認証情報をセットし、terraform plan/applyを実行しています。

各種環境変数はCodeBuildの環境変数でセットしています。値の設定は、CodePipeline用のTerraformで行っています。

動作確認: ワークロード用tfファイルの変更を、パイプライン経由で適用

ワークロード用tfファイルを変更して、パイプライン経由でPlanやApplyができるか確認します。

ワークロード用tfファイルに、example-queue2という名前のSQSキューを追加します。

workload/infra/main.tf

terraform {
  backend "s3" {
    bucket         = "terraform-state-20230808" # 置き換えが必要
    key            = "codepipeline-terraform-infra.tfstate"
    region         = "ap-northeast-1"
    encrypt        = true
    dynamodb_table = "terraform-state-20230808" # 置き換えが必要
  }
}

resource "aws_sqs_queue" "example" {
  name = "example-queue"
}
# 追加
resource "aws_sqs_queue" "example2" {
  name = "example-queue2"
}

追加したら、CodeCommitにPushします。

$ git add .
$ git commit -m "add: sqs queue" 
$ git push codecommit HEAD

CodePipelineを確認します。

CodeCommitにPushしたタイミングで、CodePipelineが動いてPlanのCodeBuildを実行しています。

しばらくすると、CodeBuildが実行完了するのでログからterraform planの結果を確認します。想定通り、SQSキュー example-queue2の作成がPlan結果に表示されています。

Plan結果が問題なかったため、Approvalのステップで手動承認を行います。

Apply用のCodeBuildが実行されます。CodeBuildのログからterraform applyの結果を確認します。

Apply complete!となっており、正常に実行されていました。

AWS CLIでもワークロードアカウントでSQSキューが作成されていることを確認できました。

$ aws sqs list-queues
{
    "QueueUrls": [
        "https://sqs.ap-northeast-1.amazonaws.com/<ワークロード用アカウント アカウントID>/example-queue",
        "https://sqs.ap-northeast-1.amazonaws.com/<ワークロード用アカウント アカウントID>/example-queue2"
    ]
}

おわりに

パイプラインを作って動かすところまで、やってみました。

今後以下の部分を、改良していきたいと思います。

ここまで読んでくださった方の中には、「Codeシリーズを使ってTerraform用のCDパイプラインを作るの大変そう」と思った方もいるかもしれません。

他のツールとの比較は以下のブログをご確認ください。

私のおすすめは Terraform Cloud です。少ないステップで簡単に、デプロイパイプラインを用意することができます。

以上、AWS事業本部の佐藤(@chari7311)でした。