GCP+Terraform+GitHubActionsのテンプレートを作ってみた
何を書くか
GCPのインフラ環境構成管理をTerraformで行い、GitHub Actions上でCI/CDを行うテンプレートレポジトリ、その構築過程、また最初にやっておくべき設定を書きます[1]。この技術構成であれば多くのプロジェクトでコピーして使用できるものを意識していますが、もし使う場合過不足はよしなに修正してください。
サンプルレポジトリ
構成要素
- ディレクトリ構成
- dev/stg/prdで環境を分けるためのディレクトリ
- モジュール用のディレクトリ
- GitHub ActionsからのCI/CD
- tfsecによるセキュリティの静的解析
- Workload IdentityによるGCPへの認可
- terraformコマンド実行
- renovateによる依存ライブラリの自動更新
- tfstateをGCSバックエンドに保存
- git pre-commit hookでコミット前に必要処理を実施(今回は
terraform fmt
のみ) - git-secretsにより機密情報のコミット防止(レポジトリ上では見えない要素)
ディレクトリ構成
方針
- dev/stg/prdで環境(GCPプロジェクト)を分けて構築することを想定
- モジュールを分け、各モジュールは環境に応じた変数(例:マシンサイズ)を持つものとする
上記の方針でまずはざっくり以下のようなディレクトリを作成します。
.
├── environments
│ ├── dev
│ ├── prd
│ └── stg
└── modules
├── module1
└── module2
environments
dev/stg/prdの各環境に対して以下のファイルを作成します(サンプル)。
- locals.tf: 各環境のローカル変数を定義
- variables.tf: 各環境の
.tfvars
や環境変数等で外部から注入する変数を定義 - provider.tf: 各環境のプロバイダを定義
- main.tf: 各環境のバックエンドとモジュールを定義
modules
各モジュールに対して以下のファイルを作成します(サンプル[2])
- variables.tf: 各環境で異なる変数や依存する他のモジュールから注入する変数(モジュール構築時に動的に生成される変数等)を定義する
- outputs.tf: 依存されるモジュールへ渡す変数を定義する
- main.tf: モジュールを定義する(必要に応じて分割しても良い)
GitHub Actions からの CI/CD
方針
GitHub Actions workflowsを利用して、CIとCDで以下の内容を実行します。
- CI (環境別にしない)
- tfsec
- terraform validate
- terraform plan
- CD (環境別に作成)
- terraform apply
また、GitHub ActionsからGCPにアクセスするために、Workload Identity連携を用いて認可を行います。
CI
以下に.github/workflows/ci.yml
(CI用のワークフローファイル)の例を書きます。
name: ci
# 1
on:
workflow_dispatch:
pull_request:
branches:
- main
jobs:
ci:
name: ci
runs-on: ubuntu-latest
# 3-1
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@v3
# 2
- name: Terraform security scan
uses: triat/terraform-security-scan@v3
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# 3-2
- name: auth
id: 'google-cloud-auth'
uses: google-github-actions/auth@v1
with:
token_format: 'access_token'
workload_identity_provider: '[workload identity providerのID]'
service_account: '[service_account]@[project_id].iam.gserviceaccount.com'
# 4-1
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.3.7
# 4-2
- name: terraform init
run: terraform init
working-directory: ./environments/dev
# 4-3
- name: terraform validate
run: terraform validate -no-color
working-directory: ./environments/dev
# 4-4
- name: terraform plan
run: terraform plan
working-directory: ./environments/dev
1. CIワークフローをトリガーするイベント
workflow_dispatch(手動)での実行、及びmain
ブランチへのPRの際に実行されるように設定しています。
2. tfsecによるセキュリティ静的解析
tfsecというTerraformのセキュリティ静的スキャンツールがあり、これをワークフローで使うtriat/terraform-security-scan
というアクションがあります(後発で公式からaquasecurity/tfsec-pr-commenter-action
というアクションも出ています)。
セキュリティリスクが検出されるとワークフローがエラー終了となるので、レポートを見て対応や受容(tfsec:ignore
)を行います。
3. Workload Identity連携により認可
GCP外部からGCPにアクセスする手段として、サービスアカウントキーを使うのではなく、Workload Identity連携というものを使うことがセキュリティ上の観点で推奨されている。Workload Identity連携の設定方法についてはこちらなどをご参照ください。
ワークフローでこれを用いて認可を行うには、google-github-actions/auth
というアクションを使えば良いです(3-2)。これを使うために、permissionsの設定が必要です(3-1)。
4. terraformコマンド実行
まずhashicorp/setup-terraform
アクションでTerraformのセットアップを行います(4-1)。次にterraform init
(4-2)、terraform validate
(4-3)、terraform plan
(4-4)を実行し、更新差分の確認をします。
CD
各環境に対してワークフローを作成します。以下で、dev環境でCDを行うワークフローの例を書きます。
name: dev_cd
# 1
on:
workflow_dispatch:
push:
branches:
- "main"
jobs:
dev_cd:
name: dev_cd
runs-on: ubuntu-latest
permissions:
contents: 'read'
id-token: 'write'
steps:
- uses: actions/checkout@v3
# 2
- name: auth
id: 'google-cloud-auth'
uses: google-github-actions/auth@v1
with:
token_format: 'access_token'
workload_identity_provider: '[workload identity providerのID]'
service_account: '[service_account]@[project_id].iam.gserviceaccount.com'
- uses: hashicorp/setup-terraform@v2
with:
terraform_version: 1.3.7
- name: terraform init
run: terraform init
working-directory: ./environments/dev
# 3
- name: terraform apply
run: terraform apply -auto-approve
working-directory: ./environments/dev
1. CDワークフローをトリガーするイベント
workflow_dispatch(手動)及びmain
ブランチへのpush(通常PRがマージされたこと)をトリガーにしています。なお、prd環境ではworkflow_dispatchのトリガーを削除した方が良いです。
2. Workload Identity連携により認可
CIと同じくWorkload Identity連携による認可を行います。
terraform plan
3. CIと同様にTerraformのセットアップをした後、terraform plan
によってデプロイを実行します。
renovateによる依存ライブラリの自動更新
renovateという依存ライブラリを自動で検出し、依存ライブラリに更新があった際にPRを出してくれる等の機能を持つツールを使うことにします[3]。
このツールの導入は簡単で、こちらのページからインストールすればOKです。インストール後、renovateの設定ファイルであるrenovate.json
ファイルを追加するPRが自動で作成されます。これをマージ後、必要に応じて設定を追加します(ドキュメント)。
tfstateをGCSバックエンドに保存
環境毎に異なるtfstateファイルを、バックエンドとしてGCSに格納します[4]。以下は、environments/dev/main.tf
の中に書くバックエンド定義の例です。
terraform {
backend "gcs" {
bucket = "dev_gcp_terraform_gcp_template_2023_tfstate"
prefix = "terraform/state"
}
}
git pre-commit hookでコミット前に必要処理を実施
terraform fmt
のようにコミット前に実行すべきだがつい忘れてしまう操作を、pre-commit hookを使って自動で実行するようにできます。
Macなら以下のコマンドをgitレポジトリのルートで実行するだけで簡単にインストールできます。
brew install pre-commit
pre-commit install
インストール後、.pre-commit-config.yaml
という設定ファイルを追加することによって、コミット前hookを定義できます。terraform fmt
を実行する場合の例。
repos:
- repo: https://github.com/antonbabenko/pre-commit-terraform
rev: v1.77.0
hooks:
- id: terraform_fmt
args:
- --args=-write=true
git-secretsにより機密情報のコミット防止
サービスアカウントJSONキーなど、機密情報のコミットを防止するために、git-secretsというツールなどを使うべきでしょう。
Macなら以下のコマンドをgitレポジトリのルートで実行してインストールできます。
brew install git-secrets
git secrets --install
そして必要に応じて秘密情報を示す正規表現を登録しておきます。
git secrets --add '^private_key'
終わりに
Terraform for GCPのCI/CDをGitHub Actionsから行うテンプレートとその作成過程、最初にやっておくべき設定について記載しました。テンプレートは上記レポジトリにあります。何か誤記などありましたら、お気軽に教えていただけると幸いです。
Discussion