GitHub ActionsでTerraformのCI環境を構築してみた

# 概要
以前、[個人開発 Advent Calendar 2019](https://qiita.com/advent-calendar/2019/private-development) に寄稿させて頂きました [副業でTerraform化した時のお話](https://qiita.com/yumatsud/items/4ae77d68456d0d556c41) の環境に対して、GitHub Actions上でTerraformのCI(fmt, validate, planなど)を実行してくれる環境を作ってみたもののまとめです。

# 現状のTerraformの構成
上記の記事でも書かせて頂いておりますが、現状のAWSやアプリケーションの構成上、下記の方針に沿った構成にしております。

* 各アプリケーションの構成がそれぞれでそんなに異ならないので、何度も同じ記述をするのは避けたい → moduleを使って各アプリケーションの構築を簡単に作れるようにする
* test、staging、productionのように、各環境にアプリケーションを構築したい → workspaceを使って環境毎の構築を簡単に作れるようにする

その構成は構成※にしてみました（一部抜粋）。

```
├── app
│   └── serviceA
│       ├── _tfvars
│       │   └── <<各種環境毎のtfファイル>>
│       └── <<各種tfファイル>>
├── modules
│   ├── codebuild
│   │   └── <<各種tfファイル>>
│   ├── codepipeline
│   │   └── <<各種tfファイル>>
│   └── ecs
│       └── <<各種tfファイル>>
├── network
│   └── <<各種tfファイル>>
├── pipeline
│   └── serviceA
│       └── <<各種tfファイル>>
└── その他のファイル（READMEなど)
```
※前回の記事に引き続き、「なんでそこ分けた？」などあるかもしれませんが、現状のネットワークやアプリケーション構成などに合わせたこともありこのような構成にしております。もしこういう構成にするともっと良いかも！などありましたら、ご教授いただけると嬉しいですmm

# やりたいこと
今回のCI環境構築にむけてやりたかったこと、いわゆる要件は下記になります。

1. 変更した箇所に応じて、fmt, validate, planを実行してほしい（修正した箇所と関係ない箇所は、特に何もしない)
2. planの実行結果などはPull Requestのコメントとかに貼ってくれると嬉しい 
3. アプリケーション環境はtest, staging, productionの3つあるので、アプリケーションに関係する箇所の修正が入ったら、その3つの環境(workspace)でplanの実行はしてほしい
4. CI実行タイミングとしては、Pull Requestを作成（reopenや再度commitなども含む）したタイミングと、masterにマージしたタイミング
5. masterにマージした際の結果は、リポジトリのトップページ(README)でbadgeとして見れるようにしたい

# やったこと
## 0. 作成したワークフロー
今回対応してみたのは、上記構成の中でも下記の3つを対応してみました。

1. network
2. pipeline
3. application

applicationはmoduleを使いつつ、かつworkspaceも使っていたので、そこを中心に記載していきたいと思います。

## 1. 変更した箇所に応じてCI実行
この変更した箇所に応じて〜というのは、GitHub Actionsの **[on.\<push|pull_request\>.paths](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths)** を指定することで実現できます。

```application.yaml
・・・・・
on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [master]
    paths:
      - modules/hogehoge/**
      - modules/fugafuga/**
      - app/serviceA/**
  push:
    branches: [master]
    paths:
      - modules/hogehoge/**
      - modules/fugafuga/**
      - app/serviceA/**
・・・・・・
```
上記のように `paths` に対して、指定のファイル or ディレクトリに修正が入ったら（正確にPull Request or Pushされたら）、
該当のワークフローが動くようにしました。
`modules` に修正が入った場合でも、それを使用しているアプリケーション（この場合だとserviceA)のplan結果が問題ないかもチェックしたいので、pathsにmodulesも指定しております。

## 2. planの実行結果などはPull Requestのコメントとかに貼ってくれると嬉しい
GitHub Actions上でTerraformを動かすサンプルに関しては、 [Terraform公式のGet Started](https://www.terraform.io/docs/github-actions/getting-started.html)が参考になります。
実際の `init` , `fmt` , `validate` , `plan` はこのサンプルをほぼそのまま使っております。

```application.yaml
・・・・・
    steps:
      - name: checkout
        uses: actions/checkout@v2
      - name: Terraform Init
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.24
          tf_actions_subcommand: "init"
          tf_actions_working_dir: ${{ matrix.workdir }}
          tf_actions_comment: true
      - name: Terraform Validate
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.24
          tf_actions_subcommand: "validate"
          tf_actions_working_dir: ${{ matrix.workdir }}
          tf_actions_comment: true
      - name: Terraform Plan
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.24
          tf_actions_subcommand: "plan"
          tf_actions_working_dir: ${{ matrix.workdir }}
          tf_actions_comment: true
・・・・・
```
→ `${{ matrix.workdir }}` はこの後に出てきます＞＜

特に `plan` の結果はPull Requestに貼ってほしいので、 `tf_actions_comment: true` にしております。
これで下記のようにコメントを貼ってくれます。

![スクリーンショット 2020-04-19 16.32.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71637/bf4b1de5-0456-5cc4-6876-c69a7caf94aa.png)

`Show Output` をクリックすると、詳細なplanの結果が表示されます

## 3. 各環境毎にplanを実行してほしい
GitHub Actionsには **[jobs.\<job_id\>.strategy.matrix](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idstrategymatrix)** というものがあり、その中の説明を引用すると、下記になります。

> 様々なジョブの設定のマトリックスを定義できます。 マトリックスによって、単一のジョブの定義内の変数の置き換えを行い、複数のジョブを作成できるようになります。 

これを利用して各環境(workspace)毎にplanを実行してもらえば良さそうです。
ここで1つ気になったのが、
「アプリケーションのplanは各環境(workspace)毎に実施してほしいが、fmtは環境毎じゃなくて、対象のディレクトリ配下だけ良い」
としたい場合、job(job_id配下）にまとめて実行してほしいことを指定しまうと、環境毎にfmtを実行されてしまう？という部分でした。そのため、

* まずは`対象のディレクトリ配下に対してfmt` を実行
* fmtが問題なければ、`各環境(workspace)毎にplan` を実行

のように直列でやってみることにしました。

まずは対象の`ディレクトリ配下に対してfmt`を実行は下記。

```application.yaml
・・・・・
jobs:
  format:
    name: Terraform Format
    runs-on: ubuntu-latest
    strategy:
      matrix:
        workdir: [./modules/hogehoge, ./modules/fugafuga, ./app/serviceA]
    env:
      GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    steps:
      - name: checkout
        uses: actions/checkout@v2
      - name: Terraform Format
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.24
          tf_actions_subcommand: "fmt"
          tf_actions_working_dir: ${{ matrix.workdir }}
          tf_actions_comment: true
・・・・・・
・・・・・
```
→ `workdir` に実行したディレクトリ毎にfmtを実行してくれます。ちなみにfmtはデフォルトで `-recursive` がつくみたいなので、指定したディレクトリを再起的にチェックしてくれます。

その後に `各環境(workspace)毎にplan` を実行したいので、下記のようにします。

```application.yaml
・・・・・
  check:
    name: Terraform Init, Validate, Plan
    runs-on: ubuntu-latest
    needs: format
    strategy:
      matrix:
        env: [test, staging, production]
        workdir: [./app/serviceA]
    env:
　　　 # GitHubのsecretsに登録したもので必要なものをここで変数定義してあげる
      TF_WORKSPACE: ${{ matrix.env }}
      TF_CLI_ARGS_plan: "--var-file=\"_tfvars/${{ matrix.env }}.tfvars\""
    steps:
      # 上記の2で紹介した内容と同様、下記は一部抜粋
      - name: Terraform Plan
        uses: hashicorp/terraform-github-actions@master
        with:
          tf_actions_version: 0.12.24
          tf_actions_subcommand: "plan"
          tf_actions_working_dir: ${{ matrix.workdir }}
          tf_actions_comment: true
・・・・・
```
まずは **[needs](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#jobsjob_idneeds)** に `format` を指定することで、 `fmt` が完了したらplanなどが実行されるようにしております。

あとはこちらも `jobs.<job_id>.strategy.matrix`に `env` のように環境毎（workspace毎)の値を指定して、環境毎にパラレルでplan等が実行できるようにしてみました。

その際、 `TF_WORKSPACE: ${{ matrix.env }}` のようにすることで、Terraformのplan実行時のworkspaceを指定できます。
それと、各環境毎で必要な `hogehoge.tfvars` は、

`TF_CLI_ARGS_plan: "--var-file=\"_tfvars/${{ matrix.env }}.tfvars\""` 

のように指定するば、plan実行時のパラメータとして渡されます。

このtfvarsですが、現状はcredentialのようなGitHubにあげるとまずい情報は入っていないので良いですが、
ここにGitHubにあげるとまずい情報が入り、それが環境毎に異なるとなると、実行方法を考え直さないといけないかもしれません。

## 4. CI実行タイミング
こちらは1ですでに出ておりますが、

```application.yaml
・・・・・
on:
  pull_request:
    types: [opened, synchronize, reopened]
    branches: [master]
    paths:
      - modules/hogehoge/**
      - modules/fugafuga/**
      - app/serviceA/**
  push:
    branches: [master]
    paths:
      - modules/hogehoge/**
      - modules/fugafuga/**
      - app/serviceA/**
・・・・・・
```
こちらのように **[on](https://help.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestbranchestags)** にトリガーとしたいアクションを追加すれば良いだけです。
上記ではbranchのみ指定しておりますが、tagなども指定できるようです。

## 5. GitHub Actionsの結果badge
GitHub Actionsはbadgeも提供してくれているようで、[こちら](https://help.github.com/en/actions/configuring-and-managing-workflows/configuring-a-workflow#adding-a-workflow-status-badge-to-your-repository)にbadge URLの命名規則が載っております。

もしくは下記のように `Actions` のページで `対象ワークフロー` を選択し、 `Create Status Badge`をクリックするとbadgeのURLを表示してくれます。
![スクリーンショット 2020-04-19 17.14.14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71637/2962a5d0-b920-64d3-d707-921f02ed76e8.png)

しかもbranchやEventが選択可能で、それに応じたURLを生成してくれます。
![スクリーンショット 2020-04-19 17.17.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71637/40f75065-ece6-9a88-0fcd-30fddf518cad.png)

あとは、そのURLをREADMEに貼っておくと下記のように表示されます。
![スクリーンショット 2020-04-19 17.20.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/71637/9f9b7ffa-d006-7792-a052-d9e2659305d0.png)


# 今後
以上が現状の構成に対してGitHub Actionsを導入してCI環境を構築してみたまとめになります。

あとは、masterにマージしたら自動で実行する部分も入れていきたいのですが、
Terraform運用がまだ短く、自動で実行するのがちょっとだけ怖いので、applyは今も手動で実行しております。
ゆくゆくはapplyする環境(CD環境)もGitHub Actionsに組み込んでいきたいです。

最後に、拙い文章で大変恐縮ではございましたが、読んでいただきましてありがとうございました。

# 参考
* [GitHub Actions公式ドキュメント](https://help.github.com/ja/actions)
* [Terraform公式 GitHub Actions](https://www.terraform.io/docs/github-actions/index.html)