はじめに
こんにちは!DPE(Developer Productivity Engineering)チームの高畑です。
ちょっと前に iPhone 15 Pro に変えてようやく USB-C ケーブルに統一できる!と思っていたら、手元にある Magic Trackpad が Lightning ケーブルでしょんぼりしました。
さて今回は、ビザスクのインフラ周りで利用している Terraform をエンジニア全員が安心・安全に利用できる仕組みづくりを行なっている話をしていきます!
これまで
ビザスクではインフラの構築・運用に Terraform を利用しており、依頼ベースで DPE のメンバーが Terraform の修正を行なってレビュー&リリースをしていました。
開発メンバーから Terraform の PR をあげてもらうこともありますが、plan / apply の権限を持っていないため権限を持っている DPE メンバーで plan / apply を行わなければならず、かなりリードタイムが長い状態となっていました。
そのため、DPE チームとしてはエンジニア全員に Terraform を触ってもらいたいという気持ちはあるものの、結局 DPE チームに依頼した方がリードタイムも短くて済むのであまり触ってもらえないという課題感を持っていたという状態です。
また Terraform の plan / apply を行うにはローカルの環境でソースコードの最新を取ってきて、ターミナルを起動して手動で plan / apply を行う必要があるため、ダブオペ必須にしているもののオペレーションミスは発生しやすかったり、plan の結果をエビデンスとして GitHub のコメントにコピーして貼り付けたりと結構手間もかかるといった状況でした。
チーム内でもエンジニア全員が安心して Terraform を実行できる環境を整えたいという声が上がっていたこともあり、CI / CD での Terraform 実行環境を整えていくことになりました。
(↓ Terraform に関する他の記事も是非ご覧ください😉)
利用技術検証
Terraform の自動化を図るにあたり Terraform Cloud を活用できないかということで検証を進めていきましたが、以下の理由から今回は GitHub Actions を利用することになりました。
- VCS ドリブンのワークフローの場合、plan の結果は Terrafrom Cloud の画面を見に行く必要がある
- PR のコメントに plan の結果を貼るには、 API ドリブンのワークフローにして GitHub Actions から Terraform Cloud の API を叩きに行く構成にする必要がある
- ステージング環境や本番環境で Terraform の実行ディレクトリが異なる場合は、環境分のワークスペースを作成する必要がある(state ファイル単位で分かれる)
GitHub Actions から OIDC を利用して GCP の認証を行う
GitHub Actions から GCP 環境へ Terraform を実行するためには、サービスアカウントの秘密鍵を GitHub 側に登録するのが一番簡単ではありますが、セキュリティの懸念から OIDC で GCP の認証を行うことにしました。
OIDC で認証をするためには GCP 側へ Terraform 実行用のサービスアカウントを払い出すための Workload Identiry Pool を作成する必要があります。
resource "google_iam_workload_identity_pool" "terraform_pool" { provider = google-beta project = var.project workload_identity_pool_id = "terraform-identity-pool" } resource "google_iam_workload_identity_pool_provider" "terraform_provider" { provider = google-beta project = var.project workload_identity_pool_id = google_iam_workload_identity_pool.terraform_pool[0].workload_identity_pool_id workload_identity_pool_provider_id = "terraform-provider-id" attribute_mapping = { "google.subject" = "assertion.sub" "attribute.actor" = "assertion.actor" "attribute.repository" = "assertion.repository" } oidc { issuer_uri = "https://token.actions.githubusercontent.com" } attribute_condition = "..." # セキュリティ方針に則って適宜設定すること } resource "google_service_account" "terraform_service_account" { project = var.project account_id = "terraform-service-account" display_name = "Terraform Service Account" } resource "google_service_account_iam_member" "terraform_service_account_member" { service_account_id = google_service_account.terraform_service_account[0].name role = "roles/iam.workloadIdentityUser" member = "principalSet://iam.googleapis.com/${google_iam_workload_identity_pool.terraform_pool[0].name}/*" } resource "google_project_iam_member" "terraform_project_member_editor" { project = var.project role = "roles/editor" member = "serviceAccount:${google_service_account.terraform_service_account[0].email}" } ... 都度必要な権限をサービスアカウントにアタッチする
上記の Terraform を実行することで、Workload Identiry Pool と Terraform 実行用のサービスアカウントが作成されます。
google_iam_workload_identity_pool_provider
の attribute_condition
についてはセキュリティポリシーに従って適宜設定を行ってください。
必要なリソースが作成されたら、GitHub Actions 側で Provider とサービスアカウントを指定して認証を通すことで、一時的に指定されたサービスアカウントを借用することができるようになります。
jobs: terraform: ... steps: ... - id: auth name: Authenticate to Google Cloud uses: google-github-actions/auth@v1 with: workload_identity_provider: "projects/xxxxx/locations/global/workloadIdentityPools/terraform-identity-pool/providers/terraform-provider-id" service_account: "terraform-service-account@xxxxxx.iam.gserviceaccount.com"
aqua で依存パッケージをインストールする
ビザスクの Terraform 環境周りではパッケージマネージャーとして aqua を利用しています。
GitHub Actions から利用する terraform コマンドも、後々バージョンアップ対応に手間がかからないよう aqua でインストールをしました。
aquaproj/aqua-installer
を利用することにより、ワーキングディレクトリにある aqua.yml
を読み込んで GitHub Actions の実行環境にパッケージをインストールすることができます。
--- # aqua - Declarative CLI Version Manager # https://aquaproj.github.io/ # checksum: # # https://aquaproj.github.io/docs/reference/checksum/ # enabled: true # require_checksum: true registries: - type: standard ref: v3.103.0 # renovate: depName=aquaproj/aqua-registry packages: - name: hashicorp/terraform@v1.6.3 ... インストールするパッケージを記載する
jobs: terraform: ... steps: ... - uses: aquaproj/aqua-installer@v1.1.2 with: aqua_version: v1.25.0
GitHub Actions から Terraform を実行する
事前準備が整ったら、実際に GitHub Actions から Terraform を実行するためのワークフローを作っていきます。
name: terraform-plan-and-apply-production on: push: branches: - main pull_request: branches: - main defaults: run: working-directory: ./ env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} jobs: terraform: runs-on: ubuntu-latest permissions: id-token: write contents: read pull-requests: write steps: - uses: actions/checkout@v4 - uses: aquaproj/aqua-installer@v1.1.2 with: aqua_version: v1.25.0 - name: hide comment id: hide-comment run: github-comment hide - id: auth name: Authenticate to Google Cloud uses: google-github-actions/auth@v1 with: workload_identity_provider: "projects/xxxxx/locations/global/workloadIdentityPools/terraform-identity-pool/providers/terraform-provider-id" service_account: "terraform-service-account@xxxxx.iam.gserviceaccount.com" - name: terraform init id: init working-directory: ./terraform/environments/prod run: terraform init -reconfigure -backend-config=../../backends/prod.tfvars - name: terraform plan id: plan working-directory: ./terraform/environments/prod run: tfcmt plan -- terraform plan continue-on-error: true - name: terraform plan status if: steps.plan.outcome == 'failure' run: exit 1 - name: terraform apply if: github.ref == 'refs/heads/main' && github.event_name == 'push' working-directory: ./terraform/environments/prod run: tfcmt apply -- terraform apply -auto-approve
plan / apply の結果を PR のコメントに残すために tfcmt
を利用しており、コミットが積まれるたびに terraform plan が実行されこのようにコメントへ投稿されます。
当初は tfcmt に -patch
オプションを渡して plan が実行されるたびに既存のコメントを上書きするようにしていましたが、コード修正前と修正後の plan 結果を比較したかったため github-comment
を組み合わせて古い plan の結果を上書きせずに最小化するようにしました。
tfcmt がコメント内に github-comment のメタデータを書き込んでくれるため、github-comment hide
が実行されるたびに対象のコメントを最小化してくれます。
コードレビューが終わり、リリースするタイミングで PR のマージをすることで apply が自動実行されてコメントに結果が投稿されます。
ステージング環境にも対応する
ここまでで、本番環境に対する plan / apply の自動化はできましたが、ステージング環境に対する plan / apply ができていません。
ビザスクでは、インフラの変更を行う際にまずステージング環境に対して Terraform を適用して動作確認を行なっており、その際手元に最新の staging ブランチを取得してきてローカルマージ & push して apply を実施しています。
そこで、PR のコメントに /pr-staging
と投稿することで、GitHub Actions で staging ブランチに向けた PR を作るようにしたのですが、後続の terraform plan する GitHub Actions が発火されないという問題に直面しました。
というのも、当初 gh コマンドを利用して PR を作成する際に secrets.GITHUB_TOKEN
を使っていたのですが、どうやら secrets.GITHUB_TOKEN
は予期しないワークフローが動かないように制限されているらしく、GitHub App を作るか Personal Access Token を使う必要があるということが分かりました。
Personal Access Token は個人アカウントに依存してしまい退職などでアカウントが削除された際に GitHub Actions が動かなくなってしまうので、ワークフローを実行できる権限を付与した GitHub App を作成し Organization の環境変数に設定するようにしました。
GitHub App の作成が完了したら、tibdex/github-app-token
を利用してトークンを発行することによりワークフローから作成された PR でも GitHub Actions が発火されるようになります。
(公式で GitHub App Token を作成する Actions が出来ていたことを記事執筆中に知ったので今後乗り換えるかもしれません 🙃)
name: create-staging-pr on: issue_comment: types: [created, edited] jobs: create-pr: if: (github.event.issue.pull_request != null) && github.event.comment.body == '/pr-staging' runs-on: ubuntu-latest steps: - id: create_token uses: tibdex/github-app-token@v2 with: app_id: ${{ vars.TERRAFORM_WORKFLOW_APP_ID }} private_key: ${{ secrets.TERRAFORM_WORKFLOW_PRIVATE_KEY }} - name: get pull request branch id: branch run: printf "BRANCH=%s" $(gh pr view --json headRefName --jq .headRefName $NUMBER) >> $GITHUB_ENV env: GH_TOKEN: ${{ steps.create_token.outputs.token }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.issue.number }} - uses: actions/checkout@v4 with: ref: ${{ env.BRANCH }} - name: create pull request for staging run: gh pr create --base staging --title "[Can Self Merge] into staging" --body "#$NUMBER" --assignee $ASSIGNEE env: GH_TOKEN: ${{ steps.create_token.outputs.token }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.issue.number }} ASSIGNEE: ${{ github.event.issue.user.login }}
こちらも同様に、PR が作成された時点でステージング環境に対して plan が実行され、マージしたタイミングで apply まで実行されるようになっています。
おわりに
今回、GitHub Actions を活用して Terraform の適用を自動化したことにより、手作業によるオペレーションが無くなったため安心して Terraform を触ることができる環境づくりの第一歩を踏み出せたような気がしています。
実際、社内でもこのようなリアクションをもらったりしたので、結構待ち望んでいた人も多かったのではないでしょうか。
他にも linter やセキュリティチェックの導入なども進んでいるので、そちらはまた別の記事にて紹介できたらと思っています!