GitHub ActionsでTerraformを実行する時にTerraform Cloudをバックエンドに指定する

某所で「GitHub ActionsからTerraform Cloudを使おうとしたが上手くいかなかった」という投稿を拝見しました。
うまいこと設定すればちゃんと動かせますのでその方法などについてまとめておきます。

TL; DR

  • Terraform CloudでのExecution ModeはデフォルトでRemoteになってる
  • クレデンシャルをGitHub側のSecretから環境変数として渡す場合はExecution ModeLocalに変更する

最初に: やりたいこと

このために、リポジトリ上でGitHub Actionsの設定を行い、リポジトリにtfファイルをコミット、PullRequestの作成を行います。

利用するtfファイル

# バックエンドの設定
terraform {
  backend "remote" {
    hostname     = "app.terraform.io"
    organization = "your-organization-name"

    workspaces {
      name = "your-workspace-name"
    }
  }
}

# プロバイダー
provider "google" {
  project = "your-project-name"
  region  = "us-west1"
}

# リソースの例
data "google_project" "project" {}

output "project_number" {
  value = "${data.google_project.project.number}"
}

この例はGCPプロバイダーを使うようにしています。

Terraform CloudとGCPプロバイダーのクレデンシャルについてはGitHub ActionsのSecretに保存しておいて環境変数(TF_ACTION_TFE_TOKENGOOGLE_CREDENTIALS)に指定します。

ワークフローファイルの定義

GitHub Actionsのワークフローファイルは以下を利用します。

参考: https://www.terraform.io/docs/github-actions/getting-started/index.html

on:
  pull_request:
    types: 
      - opened
      - synchronize
      - closed
    branches:
      - master
name: Terraform
jobs:
  terraform-fmt:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1.0.0

    - name: terraform-fmt
      uses: hashicorp/terraform-github-actions/fmt@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        # env
        TF_ACTION_WORKING_DIR: "."

  terraform-init:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1.0.0

    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

  terraform-validate:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1.0.0

    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

    - name: terraform-validate
      uses: hashicorp/terraform-github-actions/validate@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

  terraform-plan:
    needs:
    - terraform-fmt
    - terraform-init
    - terraform-validate
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1.0.0

    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

    - name: terraform-plan
      uses: hashicorp/terraform-github-actions/plan@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

  terraform-apply:
    needs:
    - terraform-plan
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v1.0.0

    - name: terraform-init
      if: github.event.pull_request.merged == true
      uses: hashicorp/terraform-github-actions/init@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

    - name: terraform-apply
      if: github.event.pull_request.merged == true
      uses: hashicorp/terraform-github-actions/apply@v0.4.4
      env:
        # secrets
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
        # env
        TF_ACTION_WORKING_DIR: "."

Secretに忘れずにTerraform CloudとGCPプロバイダーのクレデンシャルを登録しておいてください。

これでPR時にinit,fmt,validate,planが実行され、マージしたらapplyも実行されます。

クレデンシャルが見つからないエラーとなる

このままこのアクションを動かすとplan実行時に以下のようなエラーが出ます。

Error: google: could not find default credentials. See https://developers.google.com/accounts/docs/application-default-credentials for more information.

  on terraform.tf line 14, in provider "google":
  14: provider "google" ***

原因

tfファイルなどでTerraform Cloudをバックエンドとして指定する際、指定したワークスペースが存在しない場合は新たに作成されます。
そして、新規作成されたワークスペースの場合、Remote Operationsがデフォルトで有効となっています。

Remote Operationsとは、Terraform CLIからplanapplyを実行することで実際の処理をリモートのインフラ上で行える仕組みで、現在はTerraform Cloudでのみサポートされています。

www.terraform.io

Remote Operationsでは環境変数はTerraform Cloudのワークスペース上で定義されたものが利用されます。
なのでGitHub ActionsのSecretを設定、環境変数に指定してもそれが利用されず、クレデンシャルが見つからないというエラーとなります。

対応

ワークスペースの設定でExecution Modeという項目があります。
これはRemote Operationsの有効/無効を切り替えられるもので、この項目をLocalに設定することでplanapplyはローカル(GitHub Actions上)で実行され、tfstateはTerraform Cloudに保存されるようになります。

f:id:febc_yamamoto:20191010204044p:plain

まとめ

以下2点を押さえておきましょう。

  • Terraform CloudでのExecution ModeはデフォルトでRemoteになってる
  • クレデンシャルをGitHub側のSecretから環境変数として渡す場合はExecution ModeLocalに変更する

あと、パブリックなリポジトリGitHub Actionsを使ってapplyを実行するのは推奨されていない*1のでこの辺も理解した上で利用しましょう。

以上です。