😊

TerraformとCodeBuildによるクロスプラットフォームのCI/CD構築

2024/07/23に公開

こんにちは。エンジニアのTです。

Sprocketでは、データ連携や分析サービスのインフラをTerraformによりIaC管理しています。従来はAWS環境のみを対象としていましたが、ログ収集システムをリプレースした際に、GCP環境を管理対象へ追加しました。

今回は、AWS CodeBuild project上のTerraformからAWSとGCPの両環境に対してCI/CDを実行する手法を紹介いたします。

構築結果

AWSアカウントとGCPプロジェクトを組として、staging/productionの2環境に対してCI/CDを構築します。

TerraformのソースコードはGit-flowで管理されており、developブランチがstaging環境に、masterブランチがproduction環境に対応しています。

Pull Requestのマージコミットを契機として、developブランチの内容をstaging環境へ、masterブランチの内容をproduction環境へterraform applyします。

また、terraform apply結果をコードレビュー時に確認するため、feature/release/hotfixの各ブランチへのpushを契機としてterraform planを行います。

CI/CDパイプライン図

CodeBuild projectでTerraformを実行する

staging/productionの各環境にCodeBuild projectを2つずつ作成します。"planning-with-terraform"はterraform planを実行します。"applying-with-terraform"はterraform applyを実行します。

4つのprojectには、以下のようなwebhookを設定します。

  • featureブランチへのpushに際して、staging環境の"planning-with-terraform"が起動する。
  • developブランチへのpushに際して、staging環境の"applying-with-terraform"が起動する。
  • release/hotfixブランチへのpushに際して、production環境の"planning-with-terraform"が起動する。
  • masterブランチへのpushに際して、production環境の"applying-with-terraform"が起動する。

また、GitHubのbranch protectionにてdevelop/masterブランチへのマージはPull Requestの承認を必須に設定しました。これにより、terraform plan結果を確認後にterraform applyするフローを確立しました。

CodeBuildのpre_buildフェーズでは、Terraformが動作するイメージをdocker buildします。その後のbuildフェーズでdocker runすることにより、terraform plan/applyを実行します。

ベースイメージにはGoogle Cloud SDK Dockerを指定し、AWS CLIとTerraformをインストールします。これはterraform applyの際にgcloudコマンドを含むスクリプトを実行する必要があるためです。

AWS環境へのデプロイを許可する権限設定

AWS環境だけを対象としている限りは、CodeBuild projectの権限設定は非常にスムーズです。

インフラリソースを編集するコンテナには強い権限を割り当てる必要がありますが、以下の手順で簡潔かつ安全に実現できました。

# 1.CodeBuild projectからAssumeRoleを行うためのIAMロールを作成する。
resource "aws_iam_role" "iam_role_for_terraform" {
  assume_role_policy = data.aws_iam_policy_document.assume_role_policy_for_terraform.json
}

data "aws_iam_policy_document" "assume_role_policy_for_terraform" {
  statement {
    actions = [
      "sts:AssumeRole"
    ]

    principals {
      type = "Service"
      identifiers = [
        "codebuild.amazonaws.com"
      ]
    }
  }
}

# 2. IAMロールに強い権限(PowerUserAccess)をアタッチする。
resource "aws_iam_role_policy_attachment" "attach_power_user_access_to_terraform" {
  policy_arn = "arn:aws:iam::aws:policy/PowerUserAccess"
  role       = aws_iam_role.iam_role_for_terraform.id
}

# 3. IAMリソースを操作するインラインポリシーを作成し、IAM Roleにアタッチする。
resource "aws_iam_policy" "inline_policy_for_terraform" {
  policy = data.aws_iam_policy_document.inline_policy_for_terraform.json
}

data "aws_iam_policy_document" "inline_policy_for_terraform" {
  statement {
    actions = [
      "iam:*"
    ]
    resources = [
      "*"
    ]
  }
}

resource "aws_iam_role_policy_attachment" "attach_inline_policy_to_terraform" {
  policy_arn = aws_iam_policy.inline_policy_for_terraform.arn
  role       = aws_iam_role.iam_role_for_terraform.id
}

# 4. Terraformを実行するCodeBuild projectに、作成したIAM roleを設定する。
resource "aws_codebuild_project" "applying_with_terraform" {
  name          = "applying-with-terraform"
  service_role  = aws_iam_role.iam_role_for_terraform.arn
}

GCP環境へのデプロイを許可する権限設定

ローカル環境であれば、TerraformからAWS/GCPの両環境にアクセスすることは容易です。しかしながら、CodeBuild project上のTerraformでは込み入った手順が必要になります。Sprocketでは、下記の方法を採用しました。

  • GCP環境のリソースを編集するためのクレデンシャルを払い出し、AWS SecretsManagerに保持する。
  • CodeBuild project上のTerraformからクレデンシャルを使用する。

順を追って見ていきましょう。

GCP環境のクレデンシャルを払い出す

この手順は、AWS/GCPの両環境に権限を持った環境からエンジニアが実行します。誤りを防ぐため、シェルスクリプトにて記述した上でチームメンバーのレビューを実施しました。

スクリプトの概略は下記の通りです。実際のコードでは、実行結果を冪等に保つため、既存のサービスアカウントやシークレットの存在確認および削除処理を追加しています。

# 1. GCPプロジェクトにて、クレデンシャルを発行するためのサービスアカウントを作成する。
gcloud iam service-accounts create $service_account_name

# 2. サービスアカウントにownerロールを付与する。
gcloud projects add-iam-policy-binding "$project_id" \
  --member="serviceAccount:$service_account_email" \
  --role="roles/owner"

# 3. ownerロールを持つサービスアカウントからキーファイルを生成する。
gcloud iam service-accounts keys create --iam-account "$service_account_email" "$credentials_path"

# 4. キーファイルをbase64エンコードする。
base64 < "$credentials_path" > "$base64_encoded_credentials_path"

# 5. AWSアカウント側のSecretsManagerに登録する。
aws secretsmanager create-secret --name $secretsmanager_secret_path --secret-string file://"$base64_encoded_credentials_path"

CodeBuild projectからクレデンシャルを使用する

続いて、CodeBuild projectのビルド実行時に、AWS SecretsManagerから取得したクレデンシャルを使用するように設定します。

具体的には、Terraformイメージのエントリポイントとして実行されるスクリプトにて下記の処理を実行します。

# 1. AWS SecretsManagerからクレデンシャルをダウンロードし、base64デコードする。
aws secretsmanager get-secret-value \
  --secret-id $secretsmanager_secret_path \
  --query 'SecretString' \
  --output text | base64 -d >$gcp_credentials_path

# 2. コンテナ内の環境変数で、`GOOGLE_CREDENTIALS`にクレデンシャルファイルへのファイルパスを指定する。
export GOOGLE_CREDENTIALS=$gcp_credentials_path

# 3. terraformコマンドを実行する。
terraform plan

CodeBuildはビルド完了後に実行コンテナが破棄されるため、この方法でクレデンシャルを比較的安全に管理できます。

まとめ

冒頭にて述べた通り、SprocketのエンジニアチームはAWS環境の管理時からTerraformを採用していました。今回クロスプラットフォームでの一元的なIaCを構築するにあたっては、各プロバイダーの開発が活発に行われており、宣言的かつ記述力が高い文法を持つTerraformの恩恵を再認識しました。

GCP環境の外部に編集権限を払い出して使用する方法は、各ドキュメントを読み込んだ上で、チーム内で試行錯誤を重ねながら徐々に実現できました。

IaCのリポジトリをベースにCI/CDパイプラインを構築し、複数プラットフォームを一元的に管理する手法の参考になればと思います。

Sprocketで働きませんか?

弊社ではカジュアル面談を実施しております。
ご興味を持たれましたら、こちらからご応募お待ちしております。

Sprocketテックブログ

Discussion