Terraform + GitHub ActionsでInfra CI/CDを試す
この記事では、GitHub Actions を利用した Infra CI/CD の実装例をまとめています。
今回は、GCP を利用した GKE の Cluster 作成を題材にしています。
.gitignore
に含まれているようなファイル類(ex. *.tfvars
)は push していないので自身で作成して下さい。
実装のサンプル
下記の Repository に Infra CI/CD 用の設定ファイル等をまとめています。
jobの概要
GitHub Actions では複数の job を別のファイルで定義することが可能なため、下記のような構成で job を定義しています。
(単一ファイルでの定義も可能ですが、可読性やカスタマイズの容易性などを考慮して分けています)
-
tf-ci.yaml
: commit ごとに実行する(format + validate +α) -
tf-pr-check.yaml
: Pull Reuqest の作成をトリガーとして実行する(terraform plan +α) -
tf-cd.yaml
: Pull Request の Merge をトリガーとして実行する(terraform apply +α)
想定しているフローは下記の通りです。
1. branchを切って、xxx.tfの修正をpushする
2. Pull Request(PR)を作成して、レビューを行う
3. レビューが通った段階でPRをmergeする( → terraform apply)
jobの詳細
job の step は各 job で大きな差分はなく、トリガーとなるイベントや plan 結果の通知先などが job によって変わっています。
<!-- tf-ci.yaml -->
name: terraform-plan
on:
push:
branches:
- '**'
- '!main'
paths:
- '.tfnotify.yaml'
- '**.tf'
env:
TF_VAR_project_id: ${{ secrets.GCP_PROJECT_ID }}
jobs:
tf-ci:
runs-on: ubuntu-latest
defaults:
run:
shell: bash
working-directory: .
steps:
- name: clone repository
uses: actions/checkout@v2
- name: setup gcloud sdk
uses: google-github-actions/setup-gcloud@master
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
- name: setup tfnotify
run: |
sudo curl -fL -o tfnotify.tar.gz https://github.com/mercari/tfnotify/releases/download/v0.7.0/tfnotify_linux_amd64.tar.gz
sudo tar -C /usr/bin -xzf ./tfnotify.tar.gz
- name: set up terraform
uses: hashicorp/setup-terraform@v1
with:
terraform_version: 0.13.4
cli_config_credentials_token: ${{ secrets.GCP_TF_TOKEN }}
- name: terraform fmt
id: fmt
run: terraform fmt
continue-on-error: true
- name: terraform init
id: tf-init
run: terraform init
- name: tflint
uses: reviewdog/action-tflint@master
with:
github_token: ${{ secrets.github_token }}
reporter: github-pr-review
fail_on_error: "false"
filter_mode: "nofilter"
- name: terraform validate
id: validate
run: terraform validate -no-color
continue-on-error: true
- name: terraform plan with notification to GitHub
id: plan
run: |
terraform plan -no-color >> result.temp
cat result.temp | tfnotify --config .tfnotify/github.yml plan --message "$(date)"
env:
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL }}
SLACK_BOT_NAME: tf-notify-bot
GITHUB_TOKEN: ${{ secrets.github_token }}
continue-on-error: true
Terraform用のService Accountの権限
検証用の利用用途であれば、GCP プロジェクトのオーナー権限(roles/owner
)を付与するのが手っ取り早いかと思います。
実環境では、作成対象のリソースと GCS など必要最低限な権限のみを付与することを推奨します。
また、tfstate に関しては GCS を Remote Backend として指定しています。
そのため、terraform plan
, apply
に関してはGCS Bucket(tfstate)の Read 権限は必須となります。
権限周りの調整は厳密にはできていないのですが、Terraform 用の Service Account には下記のような権限を付与しています。
- Compute 管理者
- Kubernetes Engine 管理者
- ストレージ管理者
- サービス アカウント管理者
- サービス アカウント ユーザー
- 組織の管理者
Service Account の key は base64 で encode したものを GitHub Repo の Secret として登録しています。
// 出力された文字列をSecretとして登録する
cat test-xx.json | base64
GitHub Actions の job 内では、Service Account のセットアップを全ての job で最初に実行します。
(terraform init
の段階で、Remote Backend の initialize 処理が走るため)
- name: setup gcloud sdk
uses: google-github-actions/setup-gcloud@master
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
service_account_key: ${{ secrets.GCP_SA_KEY }}
export_default_credentials: true
GKE用のService Accountの権限
node_config
内の service_account
で GKE NodePool 用の Service Account を指定します。
これによって、GKE Nodepool に指定した Role を紐づけることができます。
node_config {
preemptible = true
machine_type = "e2-medium"
// recommended
service_account = "least-privilege-sa-for-gke@${var.project_id}.iam.gserviceaccount.com"
metadata = {
disable-legacy-endpoints = "true"
}
oauth_scopes = [
"https://www.googleapis.com/auth/cloud-platform"
]
tags = ["istio"]
}
今回は下記のような Role を付与した Service Account を Node 用に作成しました。
(最低限の権限のみしか付与していないため、必要に応じて Role は追加して下さい。)
resource "google_project_iam_member" "gke_node_pool_roles" {
project = var.project_id
for_each = toset([
"roles/logging.logWriter",
"roles/monitoring.metricWriter",
"roles/monitoring.viewer",
"roles/storage.objectViewer"
])
role = each.value
member = "serviceAccount:${google_service_account.least-privilege-sa-for-gke.email}"
}
jobの実行トリガー
GitHub Actions では、on.<events>
のようにトリガーとなるイベントを指定することが可能です。
Pull Request の merge を job 実行のトリガーとする場合は少し工夫が必要で、下記のように定義します。
name: terraform-apply
on:
pull_request:
types: [closed]
branches:
- '**'
paths:
- '**.tf'
jobs:
tf-cd:
runs-on: ubuntu-latest
if: github.event.pull_request.merged == true
tflint
Infra の CI/CD を行っていく上で、(特に PROD 環境では)validation + linter によるチェックの実施は必須条件になります。
(厳密には異なりますが、App の CI/CD における自動テストに相当するものとして利用されている方もいるかと思います。)
今回作成した CI/CD Pipeline では terraform validate
+ tflint
を利用する想定です。
// tflintのinstall
brew install tflint
// rulesetのdownload
git clone -b master git@github.com:terraform-linters/tflint-ruleset-google.git
// rulesetのinstall
cd tflint-ruleset-google-master
make install
du -s .tflint.d
21M .tflint.d
GitHub Actions 側で、commit push + PR 作成時に lint を自動実行できれば better です。
(build 後の Ruleset のディレクトリ(.tflint.d
)を Project root 配下に配置しましたが動作は確認できませんでした)
下記のように不正な Machine type など terraform validate
では拾えない部分に対して対応します。
node_config {
preemptible = true
// invalid machime type
machine_type = "e2-medium-xxxxxxxxxx"
service_account = "least-privilege-sa-for-gke@${var.project_id}.iam.gserviceaccount.com"
disable-legacy-endpoints = "true"
}
}
ローカル環境では、Default の Rule で動作することを確認済です。
$ tflint --config ./.tflint.hcl
1 issue(s) found:
Error: "e2-medium-xxxxxxxxxxxx" is an invalid as machine type (google_container_node_pool_invalid_machine_type)
on k8s.tf line 59:
59: machine_type = "e2-medium-xxxxxxxxxxxx"
tfnotify
terraform plan
, terraform apply
の結果通知に関しては、tfnotify を利用します。
利用方法としては、各コマンドの出力結果をパイプで渡すだけです。
(GitHub, Slack など複数種類の通知を同時に行う場合は、一時ファイルに書き出して通知処理を実行しています)
- name: terraform apply
id: plan
run: |
terraform plan -no-color >> result.temp
cat result.temp | tfnotify --config .tfnotify/github.yml plan --message "$(date)"
cat result.temp | tfnotify --config .tfnotify/slack.yml plan --message "$(date)"
env:
SLACK_TOKEN: ${{ secrets.SLACK_TOKEN }}
SLACK_CHANNEL_ID: ${{ secrets.SLACK_CHANNEL }}
SLACK_BOT_NAME: tf-notify-bot
GITHUB_TOKEN: ${{ secrets.github_token }}
終わりに
今回は、シンプルなディレクトリ構造で Infra CI/CD の実装例をまとめました。
tflint の GCP での Deep checking が非対応であったり細かい部分の追加実装は今後もありそうなので、引き続き関連する部分は Watch していきたいと思います。
Discussion