Github Actions で GCPに向けてTerraform を実行する

tldr

Github Actions で Terraformを実行してみました。

前提となること

Backend

Terraformでバックエンドをする際に知っておくべきことは以下の2点です。 (※私はGCSにしたので、providerが違うと他に気をつけることがあるかもしれません。)

  1. ストレージは予め作成しておく必要がある。
    バックエンドに指定するストレージは apply で一緒に作成することはできません。予め作っておく必要があります。
  2. アクセス権の付与の順番
    以下の記事によるとTerraformはmoduleが先に読み込まれるため、クレデンシャルはBackendで指定する必要があります。そこで指定するとproviderでクレデンシャルの指定はいらないそうです。providerで指定してbackendで指定しないとデフォルトサービスアカウントが使われます。なので特殊な場合を除いてあまりやらないと思います。ただ後述するように、環境変数としてクレデンシャルを渡すので今回はどちらもパスを設定する必要はありません。

参考: https://nickkou.me/2019/04/error-configuring-the-backend-gcs-storage-newclient-failed-dialing-google-could-not-find-default-credentials/ https://www.terraform.io/docs/github-actions/actions/plan.html

HCL -> yaml への文法の変更

Github Actionsワークフローの記法は2019年9月21日時点でyamlに変更されています(まだ使えるがdeprecatedになる)。そのため、以下の記事に従い、migration toolsを実行して HCL -> yaml へ変換が必要になります。

https://help.github.com/en/articles/migrating-github-actions-from-hcl-syntax-to-yaml-syntax#about-the-new-yaml-syntax-for-github-actions

はじめます

まずはTerraformが提供している公式のワークフローの サンプルをコピペします。

workflow "Terraform Cloud" {
  resolves = "terraform-plan"
  on = "pull_request"
}

action "filter-to-pr-open-synced" {
  uses = "actions/bin/filter@master"
  args = "action 'opened|synchronize'"
}

action "terraform-fmt" {
  uses = "hashicorp/terraform-github-actions/fmt@<latest tag>"
  needs = "filter-to-pr-open-synced"
  secrets = ["GITHUB_TOKEN"]
}

action "terraform-init" {
  uses = "hashicorp/terraform-github-actions/init@<latest tag>"
  needs = "terraform-fmt"
  secrets = ["GITHUB_TOKEN", "TF_ACTION_TFE_TOKEN"]
  env = {
    TF_ACTION_TFE_HOSTNAME = "app.terraform.io"
  }
}

action "terraform-validate" {
  uses = "hashicorp/terraform-github-actions/validate@<latest tag>"
  needs = "terraform-init"
  secrets = ["GITHUB_TOKEN"]
}

action "terraform-plan" {
  uses = "hashicorp/terraform-github-actions/plan@<latest tag>"
  needs = "terraform-validate"
  secrets = ["GITHUB_TOKEN", "TF_ACTION_TFE_TOKEN"]
  env = {
    TF_ACTION_TFE_HOSTNAME = "app.terraform.io"
  }
}

やっていることは単純でプルリクに対して fmt, init, validate, plan を実行しています。 それぞれの実際のスクリプトは uses で指定しています。

yamlへ書き換え

まずはこれをyamlに書き換えます。 ここから最新のバイナリをダウンロードしてパスを通します。

変換したいHCLのワークフローファイルを リポジトリルート/.github/main.workflow に置きます。 リポジトリルートで migrate-actions を実行します。

すると リポジトリルート/.github/workflows/pull_request.yml に変換後のファイルが作成されます。 こんな感じです。

on: pull_request
name: Terraform Cloud
jobs:
  filter-to-pr-open-synced:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: filter-to-pr-open-synced
      uses: actions/bin/filter@master
      with:
        args: action 'opened|synchronize'
    - name: terraform-fmt
      uses: hashicorp/terraform-github-actions/fmt@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_TFE_HOSTNAME: app.terraform.io
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}
    - name: terraform-validate
      uses: hashicorp/terraform-github-actions/validate@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    - name: terraform-plan
      uses: hashicorp/terraform-github-actions/plan@<latest tag>
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_TFE_HOSTNAME: app.terraform.io
        TF_ACTION_TFE_TOKEN: ${{ secrets.TF_ACTION_TFE_TOKEN }}

必要事項を埋めていく

あとは Getting Startedを読みながら必要な部分を埋めていくだけです。

クレデンシャルの設定

今回のProviderはGoogleなので、サービスアカウントのシークレットをクレデンシャルに設定します。サービスアカウントを作成し、Terraformに必要なロールを付与します(ここはどんなことをやるかによってそれぞれだと思います)。ダウンロードしたサービスアカウントのシークレットであるjsonファイルをgithubのリポジトリ設定 -> Secretsで 名前を GOOGLE_CREDENTIALS として追加します。あとはワークフローに環境変数として渡すだけです。

最終的なファイル

最終的なファイルはこれになりました。 ディレクトリやコマンドフラグなど、若干の違いはあると思います。

on: pull_request
name: Terraform
jobs:
  filter-to-pr-open-synced:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@master
    - name: filter-to-pr-open-synced
      uses: actions/bin/filter@master
      with:
        args: action 'opened|synchronize'
    - name: terraform-fmt
      uses: hashicorp/terraform-github-actions/fmt@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: ./main
    - name: terraform-init
      uses: hashicorp/terraform-github-actions/init@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}
        TF_ACTION_WORKING_DIR: ./main
    - name: terraform-validate
      uses: hashicorp/terraform-github-actions/validate@v0.4.0
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: ./main
    - name: terraform-plan
      uses: hashicorp/terraform-github-actions/plan@v0.4.0
      with:
        args: '-var-file=./config.tfvars'
      env:
        GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        TF_ACTION_WORKING_DIR: ./main
        TF_ACTION_WORKSPACE: default
        GOOGLE_CREDENTIALS: ${{ secrets.GOOGLE_CREDENTIALS }}

あとはこれを リポジトリルート/.github/workflows/pull_request.yml に置いてプルリクを作れば終わりです。ワークフローが実行されて、結果がコメントとして貼り付けられます。

まとめとこれから

やってみると意外に簡単でした。あとは applyをissueへのコメント(/applyとか)をイベントとして発火させたいですね。

Kenta Kozuka
Kenta Kozuka
Software Engineer

東京で働くエンジニア