43
Help us understand the problem. What are the problem?

More than 1 year has passed since last update.

posted at

updated at

GitHub ActionsでGCPにTerraformでインフラCI/CDする

本稿について

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 初稿投稿

動作確認環境

準備するもの

  • GitHubアカウント
  • GitHub上のリポジトリ ... TerraformのコードとGitHub Actionsのワークフロー設定ファイルを入れる
  • GCPプロジェクト
    • Service Account
      • GitHub Actions内で実行するTerraformで利用する
      • Terraformで行う操作に必要なCloud IAMの権限をつける
      • 今回の構成では、少なくともGCSへの読み書き権限が必要
    • GCSバケット ... TerraformのBackendとして利用し、tfstateを保存する
    • 必要なAPIを有効化する
      • 例)GCEインスタンスの操作には、Compute Engine APIの有効化が必要

※Bitbucket Pipelines版から、Bitbucket→GitHubになっただけ

推奨開発環境

Bitbucket Pipelines版と同様です。

セットアップ手順

  1. Terraform用のService AccoutのService Account KeyをJSON形式で生成し、保存する
  2. 1. をGitHubリポジトリのSecretsに設定する
    • キー名は任意だが、ここでは GOOGLE_CREDENTIALS とする
    • 値のBase64化は不要
  3. リポジトリの .github/workflows/ に、下のような2つのYAMLファイルを用意する

PR時にterraform fmt/validate/planするワークフロー

HashiCorpが提供しているhashicorp/terraform-github-actionsアクションを使います。

.github/workflows/terraform-plan.yml
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 fmtterrafom initterrafom validateterrafom plan を順番に実行します。

PRマージ時にterraform plan/applyするワークフロー

上と同様に、hashicorp/terraform-github-actionsアクションを使います。

また、bobheadxi/deploymentsアクションを用いて、 terraform apply を実行する時に合わせてDeploymentを作成します。

.github/workflows/terraform-apply.yml
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 initterraform planterraform apply を順番に実行できます。
また、 terraform plan を実行して差分がない場合、 terraform apply の実行、及びDeploymentの作成は行いません。

Deploymentの作成タスクはインフラCI/CDの作業フロー上で本質的な部分ではないので、省いても構いません。とはいうものの、GitHubのSlackインテグレーションで通知ができるのと、デプロイ履歴が見れるので便利だと思います。

以上のワークフロー設定により、インフラコードをGitで管理し、プルリクエストで変更をレビューしてCI/CDで適用する、といった流れのGitOpsが実現できます。
実際にどのような挙動になるかは、作業フローと合わせて後述の「デモ」の節で見ていきます。

5/4追記: デプロイ失敗通知が飛ばなかった問題を修正

上の .github/workflows/terraform-apply.ymlFinish Deployment のタスクを次のように修正しました。

.github/workflows/terraform-apply.yml
       :
       # 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 の結果をコメントで通知してくれるので、プルリクエスト駆動のフローを組んだ方が便利だと思います。

参考文書等のリンク

デモ: 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)

1. topicブランチを作成し、コード変更をプッシュ

  1. topic/add-test-sa というブランチを作成します
  2. .tfファイルに google_service_account リソースを定義します
    • 実はこのコードには敢えて誤りを含め、 terraform fmt でも差分が出るようにしています
  3. 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画面上でプルリクエストを作成します。

Screenshot from 2020-05-03 20-21-37.png

プルリクエストの作成により、 .github/workflows/terraform-plan.yml で設定したワークフローがトリガーされます。

3. ビルド結果(失敗)の確認①

プルリクエスト画面上で、 terraform fmt に失敗したことが確認できました。

Screenshot from 2020-05-03 19-19-50_crop.png

./main.tf 行を展開すると、fmt適用時の差分が確認できます。

Screenshot from 2020-05-03 19-20-31.png

プルリクエスト画面の「×」アイコンや「Details」のリンクをクリックすると、ワークフローの詳細な実行ログを確認することができます。

Screenshot from 2020-05-03 19-22-41.png

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 が失敗してしまいました。

Screenshot from 2020-05-03 19-26-13.png

Bitbucket Pipelines版のデモと同様ですが、google_service_account.test.account_id のバリデーションでエラーになっています。

今回、 terraform fmt では差分がなかったので、コメントは付いていません。

先ほどはキャプチャを撮り損ねましたが、Slackにも通知が来ています。

Screenshot from 2020-05-03 19-26-48.png

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 まで成功しました。
プルリクエストに届いたコメントで差分が確認できます。

Screenshot from 2020-05-03 19-31-35.png

terraform fmt|validate ではエラーは出ていないので、今回それらのコメントは付いていません。

8. プルリクエストのマージ

上で差分を確認し、問題ないのでマージします。

masterブランチへのマージによって、 .github/workflows/terraform-apply.yml で設定したワークフローがトリガーされます。

9. デプロイ結果の確認

デプロイの開始時と完了時にSlackに通知が来ます。(下は完了後)

Screenshot from 2020-05-03 19-36-08.png

また、プルリクエストにも terraform {plan,apply} の結果がコメントで付加されます。

Screenshot from 2020-05-03 19-35-02.png

最後に、Deployments Dashboardを見ておきましょう。3
リポジトリのメニューの「:rocket:environment」のリンクから見ることができます。

Screenshot from 2020-05-03 19-38-25_mark.png

クリックすると、次のような画面が確認できます。

Screenshot from 2020-05-03 19-38-45.png

以上で、デプロイが成功したことが確認できました :tada:

まとめと所感

GitHub ActionsでTerraformを実行し、GCP環境のインフラCI/CDを行う方法と、GitOpsによる作業フローのイメージを紹介しました。

今回は、Bitbucket Pipelines版から設定を移植したので、基本的な部分はすぐにできたのですが、CI/CDフローを作り込む過程で、意外にBitbucket Pipelinesにある機能がGitHub Actionsにないことに気付きました。4

とはいえ、Bitbucket Pipelines側で(自分が)実現できていない機能もあるので、一長一短はあると思います。5

脚注


  1. https://github.blog/jp/2019-11-14-universe-day-one/ 

  2. 正確には synchronize というevent typeなのですが、どういうイベントなのかちゃんと調べていません(汗)参考: https://help.github.com/en/actions/reference/events-that-trigger-workflows#pull-request-event-pull_request 

  3. Viewing deployment activity for your repository - GitHub Helpによれば、2020-05-03現在、このダッシュボードはβ版だそうです。 

  4. 本稿の設定を組む上で気づいたのは、YAMLアンカー、ビルドの手動トリガー、ビルド結果のSlack通知(サードパーティー製のアクションを使えばできる)。参考: GitHub Actions覚え書き - Qiita#既知の制限事項 

  5. プルリクエストにコメントを付けるのが特に難しそうな気がしています。 

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
43
Help us understand the problem. What are the problem?