TerraformとCodeBuildによるクロスプラットフォームのCI/CD構築
こんにちは。エンジニアの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
を行います。
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で働きませんか?
弊社ではカジュアル面談を実施しております。
ご興味を持たれましたら、こちらからご応募お待ちしております。
Discussion