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