本稿について
2019年11月、GitHub上で利用できる無料のワークフローツールのGitHub Actionsが正式にリリースされました。1
これを使って、CI/CDなどの処理を自動化することができます。
本稿では、GitHub ActionsでTerraformを実行し、Google Cloud Platformの構成管理を行う方法を紹介します。
また、GitOpsによるインフラCI/CDの作業フローも紹介します。
昨日、Bitbucket PipelinesでGCPに対してTerraformでインフラCI/CDする - Qiitaという記事を書きましたが、そのGitHub Actions版となります。
共通する内容が多いので、以降ではその記事を「Bitbucke Pipelines版」として参照させて頂きます。
更新履歴
- 20200504
.github/workflows/terraform-apply.yml
で、terraform apply
失敗時にDeployment失敗通知が飛ばなかった問題を修正 - 20200503 初稿投稿
動作確認環境
- terraform v0.12.24
- terraform-provider-google v3.19.0
準備するもの
- GitHubアカウント
- GitHub上のリポジトリ ... TerraformのコードとGitHub Actionsのワークフロー設定ファイルを入れる
- GCPプロジェクト
- Service Account
- GitHub Actions内で実行するTerraformで利用する
- Terraformで行う操作に必要なCloud IAMの権限をつける
- 今回の構成では、少なくともGCSへの読み書き権限が必要
- GCSバケット ... TerraformのBackendとして利用し、tfstateを保存する
- 必要なAPIを有効化する
- 例)GCEインスタンスの操作には、Compute Engine APIの有効化が必要
- Service Account
※Bitbucket Pipelines版から、Bitbucket→GitHubになっただけ
推奨開発環境
Bitbucket Pipelines版と同様です。
セットアップ手順
- Terraform用のService AccoutのService Account KeyをJSON形式で生成し、保存する
-
- をGitHubリポジトリのSecretsに設定する
- キー名は任意だが、ここでは
GOOGLE_CREDENTIALS
とする - 値のBase64化は不要
- リポジトリの
.github/workflows/
に、下のような2つのYAMLファイルを用意する
PR時にterraform fmt/validate/planするワークフロー
HashiCorpが提供しているhashicorp/terraform-github-actionsアクションを使います。
name: terraform plan
# プルリクエストのOpen、更新、Reopen時にトリガー
on: pull_request
env:
tf_version: '0.12.24'
tf_work_dir: '.'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
jobs:
plan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: terraform fmt
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: fmt
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform init
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: init
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform validate
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: validate
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform plan
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: plan
tf_actions_working_dir: ${{ env.tf_work_dir }}
このワークフロー設定により、プルリクエストの作成、マージ元ブランチの更新2、プルリクエストのReopenをトリガーとして、 terraform fmt
→ terrafom init
→ terrafom validate
→ terrafom plan
を順番に実行します。
PRマージ時にterraform plan/applyするワークフロー
上と同様に、hashicorp/terraform-github-actionsアクションを使います。
また、bobheadxi/deploymentsアクションを用いて、 terraform apply
を実行する時に合わせてDeploymentを作成します。
name: terraform apply
on:
pull_request:
branches:
- master
types: [closed]
env:
tf_version: '0.12.24'
tf_work_dir: '.'
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
jobs:
apply:
runs-on: ubuntu-latest
if: ${{ github.event.pull_request.merged == true }}
steps:
- uses: actions/checkout@v2
- name: terraform init
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: init
tf_actions_working_dir: ${{ env.tf_work_dir }}
- name: terraform plan
id: plan
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: plan
tf_actions_working_dir: ${{ env.tf_work_dir }}
# Deploymentを開始する
- name: Start Deployment
# terraform planで差分があるときのみ実行
if: ${{ steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
uses: bobheadxi/deployments@master
id: deployment
with:
step: start
token: ${{ secrets.GITHUB_TOKEN }}
env: production
- name: terraform apply
# terraform planで差分があるときのみ実行
if: ${{ steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
uses: hashicorp/terraform-github-actions@master
with:
tf_actions_version: ${{ env.tf_version }}
tf_actions_subcommand: apply
tf_actions_working_dir: ${{ env.tf_work_dir }}
# Deploymentを終了する
- name: Finish Deployment
uses: bobheadxi/deployments@master
# terraform planで差分があるときのみ実行
if: ${{ always() && steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
このワークフロー設定により、masterブランチへのプルリクエストのマージをトリガーとして、 terrafom init
→ terraform plan
→ terraform apply
を順番に実行できます。
また、 terraform plan
を実行して差分がない場合、 terraform apply
の実行、及びDeploymentの作成は行いません。
Deploymentの作成タスクはインフラCI/CDの作業フロー上で本質的な部分ではないので、省いても構いません。とはいうものの、GitHubのSlackインテグレーションで通知ができるのと、デプロイ履歴が見れるので便利だと思います。
以上のワークフロー設定により、インフラコードをGitで管理し、プルリクエストで変更をレビューしてCI/CDで適用する、といった流れのGitOpsが実現できます。
実際にどのような挙動になるかは、作業フローと合わせて後述の「デモ」の節で見ていきます。
5/4追記: デプロイ失敗通知が飛ばなかった問題を修正
上の .github/workflows/terraform-apply.yml
の Finish Deployment
のタスクを次のように修正しました。
:
# Deploymentを終了する
- name: Finish Deployment
uses: bobheadxi/deployments@master
# terraform planで差分があるときのみ実行
- if: ${{ steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
+ if: ${{ always() && steps.plan.outputs.tf_actions_plan_has_changes == 'true' }}
with:
step: finish
token: ${{ secrets.GITHUB_TOKEN }}
status: ${{ job.status }}
deployment_id: ${{ steps.deployment.outputs.deployment_id }}
ワークフローでタスクが失敗すると、デフォルトでは後続のタスクはスキップされます。
この仕様により、修正前の設定では terraform apply
が失敗したときに Finish Deployment
タスクがスキップされていました。
if
の条件に always()
を加えることで、当該ステップを常に評価することができます。
ドキュメント「Context and expression syntax for GitHub Actions - GitHub Help#always」に次のように記載されています:
A job or step will not run when a critical failure prevents the task from running. For example, if getting sources failed.
補足①GCPへの認証設定について
Bitbucket Pipelines版を参照して下さい。
補足②ブランチ更新でワークフローを実行する場合
例えば、上記のYAMLで on:
のブロックを以下のように書き換えれば、所望のブランチでワークフローを実行できます。
# masterブランチへの更新をトリガーとする場合
on:
push:
branches:
- master
---
# masterブランチ以外の更新をトリガーとする場合
on:
push:
branches-ignore:
- master
このような設定でも、元のYAMLによるプルリクエストをトリガーとする場合と同じようなインフラCI/CDの作業フローを実現できます。
ただ、hashicorp/terraform-github-actionsアクションを使う場合、プルリクエストに対して terraform fmt|validate|plan|apply
の結果をコメントで通知してくれるので、プルリクエスト駆動のフローを組んだ方が便利だと思います。
参考文書等のリンク
- https://www.terraform.io/docs/github-actions/
- https://help.github.com/en/actions
- GitHub Actions覚え書き - Qiita#プルリクエストで実行する
デモ: GitOpsによるインフラCI/CD
上で示したプルリクエスト駆動のワークフローを設定した状態で、インフラCI/CDの作業フローがどのようになるかを見ていきます。
0. 下準備
先に示した事前準備とセットアップ手順に加えて、以下を行っています:
- Terraformでテスト用のService Accountを作成するため、GCPプロジェクトでCloud Resource Manager APIを有効化しています
-
GitHubのSlackインテグレーションをリポジトリに設定し、Slack通知を有効にしています
- featureとして、
issues
,pulls
,deployments
,statuses
,public
,releases
をsubscribeしました(デフォルトからcommits
のみunsubscribe)
- featureとして、
1. topicブランチを作成し、コード変更をプッシュ
-
topic/add-test-sa
というブランチを作成します - .tfファイルに
google_service_account
リソースを定義します
- 実はこのコードには敢えて誤りを含め、
terraform fmt
でも差分が出るようにしています
- GitHubにブランチをプッシュします。
% git checkout -b topic/add-test-sa
% $EDITOR main.tf
% git diff
diff --git a/main.tf b/main.tf
index b6bb61a..d5c0613 100644
--- a/main.tf
+++ b/main.tf
@@ -31,3 +31,7 @@ resource "google_project_iam_member" "sa_terraform" {
role = "roles/owner"
member = "serviceAccount:${google_service_account.terraform.email}"
}
+
+resource "google_service_account" "test" {
+ account_id = "test"
+}
% git add .
% git commit -m "Add test service account"
% git push origin topic/add-test-sa
2. プルリクエストの作成
GitHub画面上でプルリクエストを作成します。
プルリクエストの作成により、 .github/workflows/terraform-plan.yml
で設定したワークフローがトリガーされます。
3. ビルド結果(失敗)の確認①
プルリクエスト画面上で、 terraform fmt
に失敗したことが確認できました。
./main.tf
行を展開すると、fmt適用時の差分が確認できます。
プルリクエスト画面の「×」アイコンや「Details」のリンクをクリックすると、ワークフローの詳細な実行ログを確認することができます。
terraform fmt
に失敗しているので、その後の terraform {init,validate,plan}
のタスクがスキップされていることがわかります。
4. コードを修正して再プッシュ①
ローカルで terraform fmt
を適用して、ブランチを再プッシュします。
% terraform fmt
main.tf
% git diff
diff --git a/main.tf b/main.tf
index 7ee15f7..d5c0613 100644
--- a/main.tf
+++ b/main.tf
@@ -33,5 +33,5 @@ resource "google_project_iam_member" "sa_terraform" {
}
resource "google_service_account" "test" {
- account_id = "test"
+ account_id = "test"
}
% git add .
% git commit -m "terraform fmt"
% git push origin topic/add-test-sa
プルリクエストのマージ元ブランチの更新により、 .github/workflows/terraform-plan.yml
で設定したワークフローがトリガーされます。
5. ビルド結果(失敗)の確認②
次は terraform validate
が失敗してしまいました。
Bitbucket Pipelines版のデモと同様ですが、google_service_account.test.account_id
のバリデーションでエラーになっています。
今回、 terraform fmt
では差分がなかったので、コメントは付いていません。
先ほどはキャプチャを撮り損ねましたが、Slackにも通知が来ています。
6. コードを修正して再プッシュ②
google_service_account.test.account_id
をバリデーションが通るように修正して、ブランチを再プッシュします。
% $EDITOR main.tf
% git diff
diff --git a/main.tf b/main.tf
index d5c0613..fd9a3ec 100644
--- a/main.tf
+++ b/main.tf
@@ -33,5 +33,5 @@ resource "google_project_iam_member" "sa_terraform" {
}
resource "google_service_account" "test" {
- account_id = "test"
+ account_id = "test01"
}
% git add .
% git commit -m "Fix test service account id"
% git push origin topic/add-test-sa
7. ビルド結果(成功)の確認
ようやく terraform plan
まで成功しました。
プルリクエストに届いたコメントで差分が確認できます。
terraform fmt|validate
ではエラーは出ていないので、今回それらのコメントは付いていません。
8. プルリクエストのマージ
上で差分を確認し、問題ないのでマージします。
masterブランチへのマージによって、 .github/workflows/terraform-apply.yml
で設定したワークフローがトリガーされます。
9. デプロイ結果の確認
デプロイの開始時と完了時にSlackに通知が来ます。(下は完了後)
また、プルリクエストにも terraform {plan,apply}
の結果がコメントで付加されます。
最後に、Deployments Dashboardを見ておきましょう。3
リポジトリのメニューの「environment」のリンクから見ることができます。
クリックすると、次のような画面が確認できます。
以上で、デプロイが成功したことが確認できました
まとめと所感
GitHub ActionsでTerraformを実行し、GCP環境のインフラCI/CDを行う方法と、GitOpsによる作業フローのイメージを紹介しました。
今回は、Bitbucket Pipelines版から設定を移植したので、基本的な部分はすぐにできたのですが、CI/CDフローを作り込む過程で、意外にBitbucket Pipelinesにある機能がGitHub Actionsにないことに気付きました。4
とはいえ、Bitbucket Pipelines側で(自分が)実現できていない機能もあるので、一長一短はあると思います。5
脚注
-
正確には
synchronize
というevent typeなのですが、どういうイベントなのかちゃんと調べていません(汗)参考: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request ↩ -
Viewing deployment activity for your repository - GitHub Helpによれば、2020-05-03現在、このダッシュボードはβ版だそうです。 ↩
-
本稿の設定を組む上で気づいたのは、YAMLアンカー、ビルドの手動トリガー、ビルド結果のSlack通知(サードパーティー製のアクションを使えばできる)。参考: GitHub Actions覚え書き - Qiita#既知の制限事項 ↩
-
プルリクエストにコメントを付けるのが特に難しそうな気がしています。 ↩