Google FormsとTerraformを使ってGCPプロジェクトを自動作成するシステムを構築してみた その②
投稿者:今井
初めに
こんにちは、CI部の今井です。
今年もアドベントカレンダーの季節がやってきましたね。(2回目)
今回、私が書いているこの記事はNI+C AdventCalendar GCP/GWS Team 2022 Advent Calendar 2022の8日目の記事となります。
本記事では「GCPプロジェクトの作成」をいろいろなサービスを組み合わせて自動化したことをお話しさせていただければと思います。
その①として、GCPプロジェクトの作成がどうして必要なのかからどういったサービスを選択したかを記載させていただきました。
本記事はその②として利用したコード等のお話をさせていただきます。
今回のその②のブログを書いておりまして、長くなりすぎてしまったので、GCP上の設定に関してはその③として書かせていただきます。
GCPプロジェクトの自動化システムについて
今回作成したシステムはその①でも示した通り以下の画像のようなものとなっています。
この画像のシステムでどのようにサービスを設定していったかは以下の順番でお話しさせていただきます。
- Google Forms
- Google Sheets & Google Apps Script
- Gitlab CI
- Terraform
1. Google Forms
Google Formsではプロジェクト作成の際の情報を取得するために利用しています。
Formでは以下の5点をユーザーに回答してもらい、その回答内容をGoogle Sheetsに吐き出すようにしています。
- GCPのプロジェクト名
- GCPのプロジェクトID
- プロジェクトの用途(フォルダー分けに利用)
- オーナーアカウントのアドレス
- ビリングアカウントの名前
今回のシステムではビリングアカウントのありなしなども考慮に入れる必要があったため上記のようになっていますが、基本的には1と2さえあれば今回のシステムでは本当は十分だと思います。
2. Google Sheets & Google Apps Script
今回のシステムではGoogle Formsの回答内容をGoogle Sheetsで記録するようにしています。
そして、後続で説明するGASではGoogle Formsの回答内容ではなくGoogle Sheetsに蓄積された申請内容をすべて見るように動きます。
これはさらに後続のTerraformで利用されるtfvarsファイルにて今まで作成したプロジェクトのリストを作成する必要があるためとなります。
ということで、システムの前半であるSheetsとGASでは以下のような画像の流れで動くようにしました。
この画像の中におけるGASコードは2種類ありますが、本ブログではPRを飛ばすGASのコードのみ公開します。
もう一つのSheetsに紐づいたGASに関しては機会があれば別の場所で公開させていただきますが、もし似たようなシステムを作られる際は上記のGASを皆さんの環境向けに流用するのみでよいと思います。
GASがどのようにGitLabにPRを起こすかを例で以下に示します。
例えばですが、以下のようにterraform.tfvarsというプロジェクトをリストとして持っているファイルがあるとします。(一部マスクしています)
gcp_projects = [
{
project_name = "testImai"
project_id = "testimai"
folder_category = "学習クラウドでの利用"
folder_name = "folder/DDDDDD"
billing_account = "123456-7890AB-CDEFGH"
owner_account = ["user:test1@test","user:admin@admin"]
},
{
project_name = "imai-test"
project_id = "imai-test"
folder_category = "施策での利用"
folder_name = "folder/CCCCCC"
billing_account = "123456-7890AB-CDEFGH"
owner_account = ["user:test2@test","user:admin@admin"]
},
]
そしてFormから新しいプロジェクトが申請された場合のPRの内容は以下の画像のようになります。
画像の緑色で表示されている部分が新しく申請されたプロジェクトの内容になり、この部分をレビューすることになります。
Gitlab CI
今回のGitLab CIで利用した.gitlab_ci.ymlのコードは以下となります。
stages:
- auth
- validate
- plan
- apply
image:
name: hashicorp/terraform:light
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
.tf-init-intemplate: &tf-init-intemplate
- cd environments/template
- terraform --version
- terraform init -reconfigure
variables:
WORKLOAD_IDENTITY_PROVIDER: $WORKLOAD_IDENTITY_PROVIDER
SERVICE_ACCOUNT_EMAIL: $SERVICE_ACCOUNT_EMAIL
pre_merge_request:
stage: .pre
script:
- cd environments/template
- rm -rf .terraform
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
auth:
stage: auth
image: google/cloud-sdk:alpine
script:
- echo $CI_JOB_JWT_V2 > .ci_job_jwt_file
- gcloud iam workload-identity-pools create-cred-config $WORKLOAD_IDENTITY_PROVIDER
--service-account="$SERVICE_ACCOUNT_EMAIL"
--output-file=.gcp_temp_cred.json
--credential-source-file=.ci_job_jwt_file
- export GOOGLE_APPLICATION_CREDENTIALS=`pwd`/.gcp_temp_cred.json
- gcloud auth login --cred-file="$GOOGLE_APPLICATION_CREDENTIALS"
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event" || $CI_COMMIT_BRANCH == "main"'
validate:
stage: validate
script:
- *tf-init-intemplate
- terraform validate
dependencies:
- auth
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
plan:
stage: plan
script:
- *tf-init-intemplate
- terraform plan -out "planfile"
dependencies:
- validate
artifacts:
paths:
- environments/template/planfile
rules:
- if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
apply:
stage: apply
script:
- *tf-init-intemplate
- terraform apply -input=false "planfile"
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual
ステップは5つあり、以下のように説明できます。
- pre_merge_request:.preステップとして実行される。実行ディレクトリに.terraformがある場合は削除。
- auth:GCPとの認証を実行。Workload Identity認証で実施。
- validate:terraform validateを実行。
- plan:terraform planを実行。Artifactsとしてplanfileがあり、applyステップで利用。
- apply:planステップで作成したplanfileを利用してterraform applyを実行。手動実行のみ。
また、GitLab CICDのVariablesとしてWORKLOAD_IDENTITY_PROVIDER(Workload Identity認証で利用するプロバイダのID)とSERVICE_ACCOUNT_EMAIL(GCP上でなり替わるためのSAのアドレス)を登録する必要があります。
そして、GitLab Runnerを今回利用しているホストのGCPプロジェクト上に作成する必要がありますが、本ブログではそこまでは言及しません。
Terraform
Terraformでは以下の3つのファイルを動かします。
このシステムではプロジェクトの払い出しまでが役目となるため、Terraformにしては動くファイルは少ないと思います。
- main.tf
- variables.tf
- terraform.tfvars
それぞれのファイルで記載した内容を以下に示します。
main.tf
terraform {
required_version = "~> 1.3.4"
required_providers {
google = {
source = "hashicorp/google"
version = "~> 3.61.0"
}
}
}
terraform {
backend "gcs" {
bucket = "ci-project-builder-pj-tfstate"
}
}
resource "google_project" "project" {
for_each = { for k in var.gcp_projects: k.project_name => k }
name = "${each.value.project_name}"
project_id = "${each.value.project_id}"
billing_account = "${each.value.billing_account}"
folder_id = "${each.value.folder_name}"
}
resource "google_project_iam_binding" "project" {
for_each = { for k in var.gcp_projects: k.project_name => k }
project = "${each.value.project_name}"
role = "roles/owner"
members = "${each.value.owner_account}"
}
variables.tf
variable "gcp_projects" {
type = list(object({
project_name = string
project_id = string
folder_category = string
folder_name = string
billing_account = string
owner_account = list(string)
}))
}
terraform.tfvarsは前段で記載しているため、省きます。
main.tfではterraform.tfvarsの中にあるリストをfor_eachとfor構文を利用することでぐるぐる回すことによりプロジェクトのリストを作ります。
これにより、現在作られているプロジェクトのリストをplanfileの中に作りこみ、差分のプロジェクトのみGCP上で作成します。
最後に
今回のブログは長くなってしまったので、GCP上での設定はまた別の機会にお話しさせていただきます。
その②では利用したコード等のお話を記載させていただきました。
手作業やヒューマンエラーを減らし、できるだけ労力をかけず、エンジニアが違うことに時間をさけられるようになれば、この記事の目標は達成できたことになります。
その③ではGCP側の設定をお話しできればと思います。
よろしくお願いいたします。