TerraformをPull Requestベースで使えるようにするAtlantisの構築方法と、実際にterraform applyを実施する流れのまとめ

## はじめに

[Atlantis](https://www.runatlantis.io/) によるTerraform共同作業環境をサンプルで立ち上げるべくドキュメントを読みながら進めてみました。

### [Atlantis](https://www.runatlantis.io/) って？

`Terraform Pull Request Automation` と書かれています。
実際に使った感覚としては、 `Pull RequestベースでTerraformによる環境構築をする流れをGitHubに集約するための仕組みづくりをしてくれる子` という感じでしょうか。
詳しくは下に図示してみます。

### Atlantisで何ができるの？

こんな感じのことができます。

- あるリポジトリをTerraformを操作するファイル群として構成しておく（つまりtfファイルとか）
- そのリポジトリに対してPull Requesｔが来た場合に、AtlantisがそれをWeb hook経由で検知して、`terraform plan`を投げる
- その結果はPull Requstのコメントとして表示される
- 結果が問題なければ、自分なりレビュワーが、 `terraform apply` 決行のためのコメントをPull Requetに付与する
- AtlantisがTerraform Applyをして、更に結果をPull Requstにコメントする

イメージを図にしたのが以下です。

![スクリーンショット 2019-06-19 13.17.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/a82207c1-5615-fbab-6d93-4f44828dfe0b.png)

すべてをPull Requestベースで可視化できる！というのがメリットのようです。
・・・と [ここ](https://www.runatlantis.io/guide/#increased-visibility) に書いてます。


当然ですが、別に `Enterprise Cloud` じゃなくて良いんですよもちろん。 :sweat_smile: 
Terraformが制御できるものなら何でも同じフローで制御できます。


## 前提

上記のような環境を作るまでの流れを実際にやってみたので記録として残します。
その上での前提となる内容をこちらに記載しておきます。

- GCPのインスタンス上にAtlantis環境を作る
    - このインスタンス上にはTerraform自体も必要なので [tfenv](https://github.com/tfutils/tfenv) を使って構築した（解説の対象外）
- Atlantisドキュメント内のGit Hostとしては、 `GitHub` を利用
- Terraform自体の制御をする `.tf` 群等は、 `github.com/keiichi-hikita/terraform_work` リポジトリに格納（Private Repository)
- このリポジトリへのPull Requestは自身の端末から実施

では、以下Atlantis環境を作っていくまでの流れです。
こちらの [Installation Guide](https://www.runatlantis.io/docs/installation-guide.html) に従って進めていきます。
書かれていることを読みながら、自分がチェックしたことや、実際にした行動、コマンドの例みたいなことを書き連ねていきます。
(本記事の以降の見出しは、それぞれ上記ページの左側のメニューに対応しています)

## [Requirements](https://www.runatlantis.io/docs/requirements.html)

- Git Host的にはGitHub使うから問題なし
- local state以外は全部サポートしているということなので何かしらのremote stateにする
  - 今回はTerraform Cloudなので問題なし
- リポジトリの構成については特にどんな形でも問題はないらしいのでOK
- workspaceもサポートしている。その場合 `atlantis.yaml` に書く（のだがTerraform Cloudがworkspace固定になっちゃうので事実上あまり関係がない）
- `.tfvars`ファイルは自動検出すると書いてあるが、 `-var-ile`引数経由もしくは`atlantis.yaml` 経由で明示できるように見える
- Terraformのバージョンはすべてサポート(0.12含む)

だそうなので、特に問題になるような点はなさそうでした。

## [Git Host Access Credentials](https://www.runatlantis.io/docs/access-credentials.html)

- Create an Atlantis user(optional)・・・は無視
- Access Tokenを作る
  - [ここ](https://help.github.com/en/articles/creating-a-personal-access-token-for-the-command-line#creating-a-token) にやり方が書いてあるが、
  - [Settings / Developer Settings / Personal access tokenns](https://github.com/settings/tokens/new) からtokenを作る
    - Noteに `atlantis token` とでも入れておき、 `repo` に対するフルアクセスを与えて `Generate token` をクリック
    - 生成後にtoken文字列をコピーしておく。じゃないと二度と表示されない

## [Webhook Secrets](https://www.runatlantis.io/docs/webhook-secrets.html#generating-a-webhook-secret)

- Atlantisからの接続を明確に判断するための識別子を自分で生成しておく
  - [こんなやり方でできるよ](https://www.runatlantis.io/docs/webhook-secrets.html#generating-a-webhook-secret) という例があるのでそれに従って作る
  - 自分は `www.nrandom.org` 使って作成

## [Deployment](https://www.runatlantis.io/docs/deployment.html)

### [Architecture Overview](https://www.runatlantis.io/docs/deployment.html#architecture-overview)

Architectureについて書いてあった

- Runtime
  - (自分の場合は)GitHubからのWebhookを受け取ってTerraformコマンドをローカルにて実行する役割
  - OfficialなDockerイメージもある
- Routing
  - AtlantisとGitHubの間で相互に通信が必要になる
  - GitHub的ないわゆるパブリックサイトを使っている場合はAtlantis自体を外にさらさないとだめだと書いてある
- Data
  - Atlantis自体は外部Database（おそらく外部データソース的な意味合いで書いてある）を持たない
  - Atlantisは `Terraform plan files` をローカルに持つので、基本永続的なディスクを使えるようにしておくこと


### [Deployment](https://www.runatlantis.io/docs/deployment.html#deployment-2)

- Atlantis自体をどうやって自身の環境にデプロイするかという話
- この場所が外にさらされていないといけないことになる(GitHubからリーチ可能な場所)
- 自分はGCPの永久無料インスタンス内に、 [tfenv](https://github.com/tfutils/tfenv) を入れた上で、atlantis自体も普通に [ここ](https://github.com/runatlantis/atlantis/releases) からバイナリ(v0.8.2)を落としてパスの通るところにおいた
- 最終的に、GitHub相手の場合はAtlantisを以下のようにして立ち上げる

```bash
URL=http://[your host name or ip]:4141
USERNAME=keiichi-hikita
TOKEN=[取得したGitHub token]
SECRET=[作成したWebhook Secret]
REPO_WHITELIST="github.com/keiichi-hikita/terraform_work" # <-- tfとか入っているリポジトリ

atlantis server \
--atlantis-url="$URL" \
--gh-user="$USERNAME" \
--gh-token="$TOKEN" \
--gh-webhook-secret="$SECRET" \
--repo-whitelist="$REPO_WHITELIST"
```

ちなみに `Terraform Cloud` を使うので、 Atlantis稼働サーバーの `~/.terraformrc` に以下のような記載が必要

```bash
credentials "app.terraform.io" {
  token = "your token"
}
```

## [Configuring Webhooks](https://www.runatlantis.io/docs/configuring-webhooks.html#configuring-webhooks)

- webhookをorganizationもしくはリポジトリ単位でインストールできるということだが、自分のアカウントのみで完結させるのでリポジトリ単位になる
    - つまり `If installing on a single repository, navigate to the repository home page and click Settings.` 相当
    - リポジトリの `Settins` に行く
    - 左メニューの `Webhooks` を選択し、 `Add webhook` をクリック
    - `Payload URL` に先程指定した `$URL` + `/events` を入力
    - `Content type` を `application/json` に変更
    - `Secret` にWebhook用に作ったSecretをペースト
    - `Which events would you like to trigger this webhook?` の問いに対して `Let me select individual events.` を選択
    - 下図のように、4つを選択
        - Pull request reviews
        - Pushes
        - Issue comments
        - Pull requests
    - `Active` チェックボックスのONはそのままにしておく
    - `Add webhook` ボタンをクリック

![スクリーンショット 2019-06-19 10.49.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/191f88f2-fa5f-9424-4f6c-6b6bfb9b358d.png)

ここまで終わるとAtlantisにリクエストが飛んできてます。以下のようなログが出てればOKです。

```bash
$ atlantis server \
> --atlantis-url="$URL" \
> --gh-user="$USERNAME" \
> --gh-token="$TOKEN" \
> --gh-webhook-secret="$SECRET" \
> --repo-whitelist="$REPO_WHITELIST"

2019/06/19 10:41:07+0900 [INFO] server: Atlantis started - listening on port 4141
2019/06/19 10:41:16+0900 [INFO] server: GET / – from 115.69.226.68:23101
2019/06/19 10:41:16+0900 [INFO] server: GET / – respond HTTP 200
2019/06/19 10:51:46+0900 [INFO] server: POST /events – from 140.82.115.69:28540
2019/06/19 10:51:46+0900 [INFO] server: POST /events – respond HTTP 200
```

## [Provider Credentials](https://www.runatlantis.io/docs/provider-credentials.html#provider-credentials)

- Atlantisはシンプルにサーバー上で `terraform plan` , `terraform apply` を実行数r
- そのためにはprovider用のクレデンシャルが必要になる
- 色々なやり方があるよと書かれているが、自分の場合は普通にAtlantisホスト上に定義しておいた

## ではPull Requestを投げてみる

ここまで来ると環境設定は完了してますので、あとは前提にも書いた、`github.com/keiichi-hikita/terraform_work` にPRを投げてみます。

リポジトリはこんな構成になっています。（超シンプル）

```bash
.
├── main.tf # <-- 現状空っぽ
├── provider.tf # <-- 認証情報
└── terraform.tf # <-- Remoteステートの設定 for Terraform Cloud
```

では、 `main.tf` にキーペアでも足してみます。

```go
resource "ecl_compute_keypair_v2" "keypair_1" {
  name = "keypair_1"
}
```

空の状態から上記の状態にしたので、単純に　`１つのリソースを追加した` というPRを投げることになります。
ブランチ名は `add_keypair` にでもしておきます。

で、Pull RequestをOpenします。
ここまでは普通の流れです。

Pull RequestがOpenされると、自動的にAtlantis側で `terraform plan` が実行され、その結果がコメントに反映されます。
今回はエラーになってしまいました。 :sweat: 

![スクリーンショット 2019-06-19 11.27.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/535390f3-a45f-a146-21cc-9769421c9d1d.png)

`Enterprise Cloud` のproviderバイナリを置き忘れていましたね。
こういうのも含めてDockerイメージ化しておけば良いんでしょうね多分。

さて、バイナリを　`~/.terraform.d/plugins/linux_amd64/` 配下に設置し、気を取り直して再チャレンジ。

![スクリーンショット 2019-06-19 11.41.35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/af6ffb70-85d5-c4b6-cd85-6644f0792424.png)

今度はうまくいきました。
で、`このplan結果をapplyしたいなら ”atlantis apply -d ." ってコメントしてね` と書かれているのでコメントしてみます。

![スクリーンショット 2019-06-19 13.24.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/317673/a6b7cadf-a3b0-e2b6-d1e5-5b739a744001.png)

おー、無事applyされました。
実際にリソースができたかどうかも見ておきます。 :eyes: 

```bash
% ecl --os-cloud=jp4 compute keypair list
+-----------+-------------------------------------------------+
| Name      | Fingerprint                                     |
+-----------+-------------------------------------------------+
| keypair_1 | ab:ba:7f:a7:32:bf:8a:dc:53:e3:8f:02:45:a1:34:de |
+-----------+-------------------------------------------------+
```

問題なし！ :+1:

この間、Atlantis側を見ていると、いろいろとログが流れています。
どうやらPull Requestの内容を取得する際に、 `$HOME/.atlantis/repos/keiichi-hikita/terraform_work/2/default"` みたいな感じでリポジトリがCloneされているっぽいですねぇ。

Pull Requestとその際のTerraformの挙動を、一体的に管理できるのはわかりやすいですね。
Plan結果が表示されるのでレビューもしやすい。

stateをremote管理にして、`tf` ファイル群をgit管理にして全員で最新版を共有していても、バラバラにApplyしちゃうとわからなくなってしまいますし（ま、そんなやり方しない気がしますが）、とはいえCI通してやるとなると情報がそこに集まっちゃいますしね。