はじめに
- terraformでインフラを管理してアプリを構築したい
- localからマニュアルでterraform applyすることもできるが、毎回マニュアルで実施するのは手間がかかる。github actionsでCI/CDパイプラインを構築し自動でデプロイできるのが望ましい
本記事で扱う内容
- GCPとterraformの初心者向けに、GCPプロジェクトの作成から、github actionsによるterraformのCI/CDを構築するまでの流れ
- ハンズオンで実行することを想定しています
- ※インフラは全くの門外漢なので、技術的な詳細には言及が難しい点はご理解ください
ディレクトリ構成
- terraform配下に下記のように.tfを配置する
- ちなみに、terraformは.tfをすべて統合して実行するので、ファイルを分割するのは管理のしやすさの観点
|-- [APP_NAME]
|-- terraform
|-- main.tf
|-- variables.tf
|-- outputs.tf
流れ
1.GCPプロジェクトの設定
- projectを作成する。billing accountも紐づける。
- GCPアカウントの作成が完了していない型はこちらから作成
gcloud projects create [PROJECT_ID] --name=[PROJECT_NAME]
gcloud config set project [PROJECT_ID]
gcloud billing projects link [PROJECT_ID] --billing-account=[BILLING_ACCOUNT_ID]
- 必要なAPIを有効化する
gcloud services enable cloudbuild.googleapis.com
gcloud services enable iam.googleapis.com
gcloud services enable iamcredentials.googleapis.com
gcloud services enable cloudresourcemanager.googleapis.com
2. workload identity federationのみlocalからapplyする
-
workload identity federationについてはこちらの記事がわかりやすい
-
workload identity federationは、GitHub ActionsのOIDCトークンをGCPが信頼できるよう橋渡しする仕組み。これが設定されていないと、GitHub ActionsからGCPのリソースにアクセスできない。
-
最初はlocalからapplyする必要がある。
-
下記のterraformコードを用意する。
-
varにてproject_id, app_name, github_repositoryを設定する。
- 1アプリで、1リポジトリ1プロジェクトの場合は、すべて同じ名前でよい
main.tf
# terraformのバージョン・プロバイダの設定
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "~> 5.0"
}
}
}
# google providerの設定(google関連のリソースを作成するために必要)
provider "google" {
project = var.project_id
region = var.region
}
resource "google_iam_workload_identity_pool" "github" {
workload_identity_pool_id = "github-pool"
display_name = "GitHub Actions Pool"
}
resource "google_iam_workload_identity_pool_provider" "github" {
workload_identity_pool_id = google_iam_workload_identity_pool.github.workload_identity_pool_id
workload_identity_pool_provider_id = "github-provider"
display_name = "GitHub Actions Provider"
oidc {
issuer_uri = "https://token.actions.githubusercontent.com"
}
attribute_mapping = {
"google.subject" = "assertion.sub",
"attribute.actor" = "assertion.actor",
"attribute.repository" = "assertion.repository"
}
attribute_condition = "attribute.repository == '${var.github_repository}'"
}
# github actionsからGCPリソースにアクセスするためのサービスアカウントを作成
resource "google_service_account" "github_actions" {
account_id = "github-actions-sa"
display_name = "GitHub Actions Service Account"
}
# 上記SAにeditorロールを付与
# ※本番運用では管理するリソースに必要な権限のみに絞ることを推奨
resource "google_project_iam_member" "github_actions_editor" {
project = var.project_id
role = "roles/editor"
member = "serviceAccount:${google_service_account.github_actions.email}"
}
# workload identityにgithub actionsのSAを紐づける(これでgithub actionsがworkload identityを通じて github_actions用のSAを利用できるようになる)
resource "google_service_account_iam_member" "github_actions_wi" {
service_account_id = google_service_account.github_actions.name
role = "roles/iam.workloadIdentityUser"
member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.github.name}/attribute.repository/${var.github_repository}"
}
- 変数を定義する
variables.tf
variable "project_id" {
type = string
default = "[PROJECT_ID]"
}
variable "region" {
type = string
default = "asia-northeast1"
}
variable "app_name" {
type = string
default = "[APP_NAME]"
}
variable "github_repository" {
type = string
default = "[GITHUB_REPOSITORY]"
}
- 出力する値を定義する
outputs.tf
output "workload_identity_provider" {
value = "projects/${var.project_id}/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
}
output "github_actions_sa_email" {
value = google_service_account.github_actions.email
}
下記の作成が完了したら、shellからterraform applyする
cd terraform
terraform init
terraform apply
apply完了後、terminalにoutputの値が表示される。この値は次のステップ(4. GitHub Actionsの設定)で使用するのでメモしておく。
workload_identity_provider = "projects/[PROJECT_NUMBER]/locations/global/workloadIdentityPools/github-pool/providers/github-provider"
github_actions_sa_email = "github-actions-sa@[PROJECT_ID].iam.gserviceaccount.com"
3. tfstateをGCSに移行
- terraformのstateファイルをGCSに保存するように設定する。これにより、複数人でterraformコードを管理する際に、stateファイルの競合を防ぐことができる。
gcloud storage buckets create gs://[PROJECT_ID]-tfstate \
--project=[PROJECT_ID] \
--location=asia-northeast1 \
--uniform-bucket-level-access
- terraformは現在localにstateファイルを保存しているため、これをGCSに移行するための設定を記載する。
-
main.tfのterraformブロックにbackendを追加
terraform {
...
backend "gcs" {
bucket = "[PROJECT_ID]-tfstate"
prefix = "[APP_NAME]"
}
}
- stateをGCSに移行する
terraform init -migrate-state
これで、terraformコマンド実施時は、GCS上の[PROJECT_ID]-tfstateバケット内の[APP_NAME]ディレクトリにstateファイルが保存されるようになる。
4. GitHub Actionsによるterraform plan/applyの設定
.github/workflows/[APP_NAME].ymlを作成し、以下のように記述する。
workload_identity_providerとservice_accountには、手順2のapply後にoutputに出力された値を設定する。
name: Terraform Deploy
# mainブランチへのpushをトリガーとする
on:
push:
branches:
- main
paths:
- '[APP_NAME]/terraform/**'
pull_request:
branches:
- main
paths:
- '[APP_NAME]/terraform/**'
permissions:
id-token: write # OIDCトークンを発行する権限
contents: read #
jobs:
terraform:
runs-on: ubuntu-latest
steps:
# リポジトリのコードをactions上にDL
- uses: actions/checkout@v4
# workload identity federationを使用してGCPに認証
# workload_identity_provider, service_accountは手順2のterraform apply後のoutput値を設定する
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/[PROJECT_NUMBER]/locations/global/workloadIdentityPools/github-pool/providers/github-provider # output: workload_identity_provider
service_account: github-actions-sa@[PROJECT_ID].iam.gserviceaccount.com # output: github_actions_sa_email
# Actions用にterraformをsetup
- uses: hashicorp/setup-terraform@v3
# terraform init(毎回新たな環境となるので毎回initが必要)
- name: Terraform Init
working-directory: [APP_NAME]/terraform
run: terraform init
- name: Terraform Plan
working-directory: [APP_NAME]/terraform
run: terraform plan
# yesの入力を自動化してapply
- name: Terraform Apply
working-directory: [APP_NAME]/terraform
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
run: terraform apply -auto-approve
以上で、githubのmainブランチにpushすると、自動でterraform applyが走るようになる。
感想
- 上記の体制を確立できると、terraformコードをmainブランチにpushするだけで自動でGCPのインフラが更新されるようになるので、非常に便利
- 一度localからapplyするのは少し面倒だが、これはWorkload Identity Federation自体を作るためにWorkload Identity Federationが必要という鶏卵問題