spacelyのブログ

Spacely Engineer's Blog

モノレポでreviewdog/action-tflintを実行するためにOSSコントリビュートして解決した

インフラエンジニアの thaim です。

スペースリーではインフラの構築にTerraformを、Terraformのコードに対する静的解析にtflintを利用しています。 このtflintを上手く活用するために取り組んだこと、OSSコントリビュートに取り組んだことについて紹介します。

スペースリーにおけるインフラ開発の背景

始めにスペースリーのインフラ開発におけるtflintの活用状況について紹介します。

tflintを実行する

Terraformのコードの静的解析ツールはいろいろと存在しますが、lintツールとしてtflintがあります。tflintはTerraformコードのコード規約を設定したり、terraform applyコマンドを実行できない不正な設定を検出したりできます。

スペースリーではTerraformコードの静的解析ツールとして terraform fmt (インデントなどのフォーマットゆれを検知)、tflint (コード規約や不正な設定を検知)、tfsec (セキュリティ上不適切な設定を検知)の3種類を利用しています。

reviewdogでtflintを実行する

各種静的解析ツールは各開発者のローカル環境でも実行することを想定していますが、実行忘れは生じるのでGitHub Actions上で各静的解析ツールを実行して問題ないことを確認しています。

特にtflintおよびtfsecはどのコード部分の書き方に問題があるのかわかりやすく示して欲しいので reviewdogを利用してます。具体的には、github actions上で実行するために reviewdog/action-tflintを利用しています。 reviewdogはコードコメントとして検知結果を出力してくれるので、修正が必要な箇所がわかりやすく非常に便利です。また、Terraformコードを書き始めたときはCIを利用していなかったので大量の修正が必要な箇所が残っていました。一度にすべてを修正するのは現実的ではなく、機能修正しながら少しずつ修正する方針を取っていました。reviewdogがPR範囲外は検知せずフィルタリングしてくれるのでこのような取り組みが非常に簡単に実現できました。

モノレポで実行する

スペースリーのTerraformコードリポジトリはいわゆるモノレポ構成になっています。

モノレポにおけるCIの実装方法はいろいろありそう(詳しい人教えて欲しい)ですが、Terraformコードリポジトリのlint実行についてはイベントトリガー時に全ディレクトリで愚直にlintを実行しています。理想的には差分のあるディレクトリでのみ実行する仕組みがあるとよさそうですが、そのような作り込みはまだ行っていません。

モノレポにおけるaction-tflintの課題

上記で紹介したような、モノレポにおいてGitHub Actionsでtflintをreviewdog経由で実行する(action-tflint)方法における課題として大きく2つありました。

tflint設定ファイルの読み込み

tflintの設定(.tflint.hcl ファイル)を各ディレクトリに作成したくないのでルートディレクトリに配置しています。これによりlint設定を修正した場合は自動的に全ディレクトリに反映され、設定の統一が実現できます。 しかし、reviewdogでtflintを実行するとき利用する .tflint.hcl ファイルを指定できず、tflintを実行するディレクトリにある .tflint.hcl を参照する仕様となっていました。これではスペースリーで実現したい単一の設定ファイルを各ディレクトリで参照する方法が採用できませんでした。

この問題は自分たちが遭遇した当時に既にIssue報告されていましたがまだ解決されていませんでした。

APIレート制限

ディレクトリでtflintを個別に実行する場合、実行毎にtflint pluginをダウンロードする必要があります。tflint pluginはgithubAPIを叩いてダウンロードしますが、このAPIのレート制限が厳しく1時間に60回までしか許可されません。このためモノレポのように1つのプルリクで複数回APIにアクセスするような挙動では簡単にAPIレート制限にひっかかりエラーとなってしまいます。

対策としては、GITHUB_TOKEN を指定する方法があります。GITHUB_TOKEN を指定して認証ユーザとしてAPIを利用することでレート制限が1時間に5000回以上に緩和することができます。この課題についても既にIssue報告されており、PRも作成済でしたがまだ取り込まれていない状況でした。

action-tflintの解決策

上手くいかない方法: target dirを変更する

reviewdog/action-tflintのREADMEには記載されていませんが、 tflint_target_dir オプションを指定する方法がサポートされており、tflintの適用先ディレクトリを指定することができます。 このオプションを利用することで .tflint.hcl の指定などの問題が解決できないか検討しました。

しかし、tflint_target_dir を指定した場合は、実行元のカレントディレクトリにある .tflint.hcl および .terraform modulesを参照します。すなわち各ディレクトリで terraform modulesを利用しており事前に terraform init コマンドを実行して依存解決が必要な場合は、依存解決に失敗するのでtflintが実行できません。 以下は serviceAディレクトリを対象にtflintを実行したエラー例です。このように、tflint_target_dirの指定では解決になりませんでした。

# ルートディレクトリで serviceAディレクトリに対してtflintを実行した例。事前にterraform initしてないのでエラーになる。
$ tflint serviceA
Failed to load configurations; serviceA/network.tf:12,1-31: `vpc` module is not found. Did you run `terraform init`?; , and 6 other diagnostic(s):

Error: `vpc` module is not found. Did you run `terraform init`?

  on serviceA/network.tf line 12, in module "vpc":
  12: module "vpc" {

...

上手くいかない方法: 不要なAPIアクセスを減らす

現時点ではモノレポであることを考慮できておらず、変更が含まれていないので静的解析の実行が不要なディレクトリに対してtflintを実行しないという仕組みが実装されていません。このため不要なAPIアクセスが多数生じているという課題があります。

該当ディレクトリに変更が含まれていない場合にtflintを実行しないという方法を実現することでAPIアクセス数を削減することができます。 一方で、APIレートリミットが50アクセス/1時間なので必要なAPIアクセス数だけでも簡単にリミットを超過してしまう恐れがあります。CIコスト削減などのために取り組む価値はありますが、根本的な解決にはなりません。

上手くいく方法: tflintのシンボリックリンクを作成する

適切な .tflint.hcl を読み込ませる方法として、各ディレクトリにリポジトリルートの .tflint.hcl を参照するシンボリックリンクファイルを作成する方法があります。 これにより各ディレクトリにはカレントディレクトリに .tflint.hcl ファイルが存在し、ルートディレクトリの .tflint.hcl を編集すれば全ディレクトリの設定に変更反映されたかのように振る舞うため、問題を解決できます。 具体的には以下のような構成となります。

シンボリックリンクによる解決

この方法はシンプルでわかりやすく、action-tflint側を修正することなく課題を解決できます。 一方で、ディレクトリを作成するたびにシンボリックリンクを作成する手間が生じるなどの課題はあります。

上手くいく方法: tflintのconfigオプションを指定する

tflint本体には --config オプションが提供されており、参照する .tflint.hcl ファイルを指定できます。これを利用し、各ディレクトリでtflintを実行するときにルートディレクトリの .tflint.hcl を指定すれば設定ファイルを共有してtflintを実行できます。

GitHub Actions上でのtflint実行は具体的には以下のようになります。

$ tflint --config "${{ github.workspace}}/.tflint.hcl"

この方法であればディレクトリ作成時にtflintのシンボリックリンクを作成する手間が不要になります。 ただし、reviewdog/action-tflintがこの方法をサポートしていないので、action-tflint側を修正する必要がある点は考えておく必要があります。

上手くいく方法: GITHUB_TOKENを渡す

GitHubAPIアクセスレート制限は、認証トークン付きでアクセスすることで5000アクセス/1時間まで緩和されます。このため、基本的には認証トークン付きでアクセスした方がよいでしょう。

残念ながら reviewdog/action-tflintでは認証トークン付きでアクセスする実装がPRで提案されているものの放置されて取り込まれていませんでした。このためこの方法を利用することができませんでした。

解決手順

上記のいくつかの策を踏まえて、以下のような方法で解決しました。

action-tflintのforkと修正

tflint設定ファイルの読み込みも、APIレート制限も、どちらも解決手段はありますが reviewdog/action-tflint 自体の実装修正が必要です。しかし、PRの対応は放置されており、すぐにの解消は見込めない状況でした。

このため、このリポジトリをforkして修正反映し、そちらを利用することにしました。 実際にforkしたリポジトリこちらです。GitHub Actionsはforkすれば簡単にActionsをカスタマイズできるので、このようなときに便利ですね。

社内リポジトリにおいてforkしたGitHub Actionsを利用することで、どちらの問題も解決することを確認できました。 具体的には、action-tflintに tflint_config オプションを実装し GITHUB_TOKEN を利用するように修正し、以下のように指定するようにしました。

- name: run tflint with reviewdog
  uses: reviewdog/action-tflint@master
  with:
    github_token: ${{ secrets.github_token }}
    working_directory: ${{ matrix.workspace }}
    filter_mode: "file"
    reporter: "github-pr-review"
    tflint_init: true
    tflint_version: "v0.45.0"
    tflint_config: "${{ github.workspace}}/.tflint.hcl"

reviewdogへのPR作成

APIレート制限対策は既にPRが存在しますが、tflint設定ファイルを指定する方法はまだ実装PRが存在していませんでした。このため、現状のままではいつまで経っても自分が期待する方法での解決は見込めないという問題があります。 加えて、issueで提案されている方法では tflint --version コマンドの実行を考慮していないので、--config オプションをaction-tflintがサポートした方がよさそうだと考えました。

今回forkして修正した解決策は実際に社内で試して上手くいくことが検証できたので、この変更内容にて reviewdogにPRを作成しました。この変更をreviewdogが取り込んでくれればforkした実装を利用する必要がなく、action-tflintのメンテを自前で行う必要がなくなります。

その後、無事このPRはマージされました! このためforkを利用し続ける必要がなくなり、公式actionsの利用に戻すことができました。日頃からお世話になっているreviewdogにもコントリビュートすることができたのはよかったです。

まとめ

スペースリーのインフラ開発ではモノレポ構成でterraformを用いておりreviewdog/action-tflintで静的解析を実行しています。 APIレート制限の問題を解決するためにGITHUB_TOKENを利用する、 .tflint.hcl を指定するオプションをreviewdog/action-tflintに追加する、という方法を採用するためにactionsをforkして修正・検証して解決しました。また、修正内容はreviewdog/action-tflintにPRを作成することでコミュニティにフィードバックすることができました。

スペースリーでは静的解析を用いたコードの品質改善や利用したOSSへのコントリビュートなども積極的に行っています。 一緒に取り組んでくれる人を大歓迎していますので、気になる人は採用ページなどからぜひご連絡ください!