Table of Contents

Terraform を Github Action 上で実行、自動で適用するまでの仕組みを作った

趣味開発で GCP を使用しており、全てのインフラ構成を Terraform により IaC 化している。現在、次のフローによって Terraform を更新し、その更新をインフラへ反映させている。

  • (1) .tfファイルを更新
  • (2) 手動でterraform applyコマンドを実行し、.tfファイルに対する変更を反映。
  • (3) Terraform の変更を main ブランチへ直接コミット。

新機能開発で必要となるリソースを追加するだけであれば、上記で述べた手動運用でも問題ないが、毎日の Terraform の運用が面倒くさい。具体的には下記のような問題。

  • dependabot により.terraform.lock.hcl中に書かれている Provider などのバージョンが更新されたり。割と頻繁に更新される。
  • モジュールのリファクタリング。とあるモジュールをリファクタリングしたとき、そのモジュールへ依存しているルートモジュールを全て更新する必要があったり。

ということで今回、自動化しちゃった。

趣味開発でそこまでやるか?という意見もあるが、自分としては、趣味開発だからこそ自動化が必要であると考える。 日々の多くの時間を本業や家事へ費やしているため、趣味開発への時間があまり取れない。そうなると、コードを書く時間は必然的に土日の空いた時間へ偏ることになる。コードを触れるのは週 1 日ぐらい。必然的にインフラを触るのも週 1 日となる。インフラを触れるのが週 1 日だけとなると、terraform による手動更新手順すら忘れてしまう。だから自動化する。

何を作ったか?

Terraform を変更し PR を作成すると、Github Action 上で自動で plan が実行される。terraformga applyとコメントすると、Github Action 上で自動で apply が実行され、PR がマージされる。

https://github.com/suzuito/sandbox2-terraform/pull/69

Root moduleorganizations/tach.dev/products/blog/services/server/environments/stg/local.server_env.hogeを変更する PR を作る。すると Github Action 上でterraform planが実行される(下図の check_in_PR)。

その他、tflintやコードレビューで問題がなければ、terraformga applyとコメントする(下図)。

問題なく apply ができたら、PR が自動でマージされる。

仕様

Atlantisを参考にしている。

Terraform 更新フロー

  • .tf(.terraform.lock.hclも含む)ファイルの修正
    • (手動) mainブランチをベースとするfeatureブランチを作成し、featureブランチ上で.tfファイルを変更する。
  • レビュー
    • (手動) main <- featureの PR を作る。
    • レビュー項目(Github Action の status check)
      • (自動 on Github Action) terraform planのエラーがない。
      • (自動 on Github Action) tflint のエラーがない。
      • (手動) 目視によるレビュー。問題なければ承認。
      • (自動 on Github PR) PR をマージする前にベースブランチの最新の変更が取り込まれていること(Update branch 機構の有効化)。
      • (自動 on Github Action) 1 つの PR の中で、1 つの 1 つの root module に対する変更のみをapplyすれば良い状態になっていること(※1)。
  • リリース
    • (自動 on Github PR) リリース承認判定。
      • 上記のレビュー項目を全てパスしていれば、teraform applyが実行可能となる。terraform applyの実行方法。
    • (手動) PR にてterraformga applyをコメントすると、Github Action がterraform applyを実行する。
  • マージ
    • terraformga applyが成功後
      • (自動 on Github PR) ブランチがマージされる。
    • terraformga applyが失敗した場合、ブランチはマージされない。
      • (手動) 問題を解決し、terraform applyまで実行すること。
      • (手動) 問題を解決できたらブランチをマージする。

(※1)この制約を設けている理由は、1 回の terraform の apply 実行における更新数をなるべく少なくしたいため。terraform によるリソースの変更範囲が大きくなるほど、リソース更新失敗のリスクが大きくなる。今回、試験的に terraform の自動実行を導入しているという経緯から、保守的な姿勢を取る。という感じなので、もしかするとこの制約は不要なのかもしれない。心配しすぎ?

設計

Terraform ディレクトリ構成

自動化の対象となるルートモジュールは 7 つあり、これら Root module の中で使用される Child module がいくつかある。

Terraform モジュールに対する制約。

  • 1 つの Root module の中で管理するリソースは、1 つだけの Google Cloud Project に紐づいている。例えば、ある 1 つの Root module 配下の A リソースが a プロジェクト、B リソースが b プロジェクト、みたいな Root module を禁止している。
    • 何故この制約を設けたか。Google Cloud Platform のリソースが、どの Root module から作成、更新されているのか?がわかり難くなることを防ぐため。というのと、後述する Terraform 実行用のサービスアカウントの権限範囲を絞るため。

Github Action

  • terraform_plan ジョブ
    • Github Action の pull_request トリガー。Terraform に対する何らかの変更があった場合、関係する Root module モジュールに対してplanを実行する。
  • terraform_apply ジョブ
    • Github Action の issue_comment トリガー。PR にterraformga applyとコメントすると、Github Action 上でterraform applyが自動実行される。PR のレビュー項目を全てパスしていない場合、terraform applyを実行できない(あかん・・・実装忘れてた・・・今後の課題とします・・・)。

Terraform 実行用のサービスアカウント

1 つの Google Cloud Project に、1 つの Terraform 実行用のサービスアカウントがいる。このアカウントは、アカウントが所属するプロジェクトのリソースに対する更新権限を持つ。他のプロジェクトに対する更新権限は持っていない。全プロジェクトの更新権限を持つ神のようなサービスアカウントを作らないようにしている(※2)。

(※2)厳密に言うと、神のようなサービスアカウントは 1 つだけある。Google cloud folder、Google cloud project を管理している Root module があるのだが、神のようなアカウントはこの Root module を apply する。この Root module だけは、自動化対象外としている。

terraform plan ジョブの中身

Go 言語で書いた。

(図 1)モジュール木構造。Root module を最上位のノードとし、依存するモジュールがリンクで接続されている木構造。図中の modules/backend が変更された場合、root_modules/web を plan する必要がある。図中の modules/cd が変更された場合、root_modules/web、root_modules/batch を plan する必要がある。

モジュール木構造の作り方(参考 ParseModuleDir)

レポジトリ上のディレクトリを探索し、HashiCorp 社が提供する HCL パーサを用いて.tfファイルをパースし、Terraform module 情報を抽出して木構造を作っている。モジュールが Root であるかどうか?の判定条件は、.terraform.lock.hclがディレクトリに存在するかどうか?としている。この判定条件が正しいことの妥当性は下記を根拠としている。

The dependency lock file is a file that belongs to the configuration as a whole, rather than to each separate module in the configuration. For that reason Terraform creates it and expects to find it in your current working directory when you run Terraform, which is also the directory containing the .tf files for the root module of your configuration. 参考

-> .terraform.lock.hclがディレクトリ上に存在するならば、そのディレクトリは Root module である。と解釈できる(多分、あんまり自信ない)。

今後の課題

  • (実装を忘れた) PR のレビュー項目を全てパスしていない場合、terraform applyを実行できないようにしたい。

感想

これまでは、後の運用のことを考えるとインフラ構築が億劫になることがあった(自分が構築したインフラ構成と Terraform をすぐに忘れてしまうので)が、今後は心置きなくインフラをガンガン構築できる!わーい。