tflintとプラグインの Rate limit の話、またはプラグインを使わない選択もある話

2025/01/04に公開

前提条件

この記事の概要

  • tflint ではプラグインを使用することで aws や google といった特定のプロバイダー向けの追加のチェックを行えます。
  • tflint --init でプラグインのダウンロードを行いますが、このプラグインのダウンロード処理で 403 API rate limit exceeded によるエラーが起きる場合があります。
    • この挙動については tflint のドキュメントの Avoiding rate limiting でも言及されている。
  • CI/CD 処理に組み込んだ tflint の処理でこのエラーが起きると、運用に影響が出る場合があります。
  • 本記事ではその挙動についての詳細と、回避方法について記載します。
  • 特に、「tflint でプラグインを使わない」という判断がこのエラーの回避方法として有効であることを強調したい。

tflint --init で起きる Rate limit 問題

tflint --init は何をしているか

tflint にはプラグインの機能があり、lint で使用する追加のルールセットを設定できます。基本的には、Terraform テンプレート内で使用するプロバイダー向けのチェックのルールが追加されます。例えば Terraform の aws プロバイダー に対しては、 tflint の tflint-ruleset-aws を使用することで、aws プロバイダー固有のチェックを行えるようになります。

tflint --init を実行することで、設定ファイルで使用を宣言したプラグインをダウンロードします。このため設定ファイルでプラグインの利用を指定している場合は、 tflint の利用時に最初に tflint --init を実行することになります。

$ tflint --init
Installing "aws" plugin...
Installed "aws" (source: github.com/terraform-linters/tflint-ruleset-aws, version: 0.32.0)
$

ちなみに terraform 自体の文法などのチェックのためのルールは tflint 自身にバンドルされているため、プラグインとしてダウンロードを行う必要はありません。

発生する事象

tflint --init を実行した際に、以下のようなエラーでプラグインのダウンロードに失敗する場合があります。
特に CI/CD でこの事象に遭遇することが多いでしょう:

$ tflint --init
Installing "aws" plugin...
Failed to install a plugin; Failed to fetch GitHub releases: GET https://api.github.com/repos/terraform-linters/tflint-ruleset-aws/releases/tags/v0.32.0: 403 API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.) [rate reset in 48m51s]
$

これは GitHub の REST API の Rate limit に引っかかったことによるエラーです。
認証なしで利用する場合、GitHub の REST API は 1 時間あたり 60 回の呼び出し制限があります。この呼び出し回数は IP アドレス単位にカウントされます。

発生のメカニズム

tflint はプラグインの配布の仕組みとして GitHub の Release の機能を使用しています。
例えば aws 用のプラグインであれば https://github.com/terraform-linters/tflint-ruleset-aws/releases からダウンロードします。
この仕組みにより、プラグインの開発者は GitHub の Release を作成するだけで tflint の利用者にプラグインを配布できます。

tflint --init は GitHub Release からプラグインをダウンロードしますが、この際に GitHub の REST API を使用します。具体的には以下のような動作になっています (署名チェックの処理などは省略しています):

  1. Get a release by tag name API でタグ(=バージョン)に対応するリリースで提供されるアセット(ファイル)の一覧を取得する。
  2. Get a release asset API で checksums.txt をダウンロードする。
  3. Get a release asset API で checksums.txt.sig をダウンロードする。
  4. Get a release asset API でターゲットプラットフォーム用のプラグイン本体 (たとえば tflint-ruleset-aws_linux_amd64.zip) をダウンロードする。

このため、1 つのプラグインのダウンロードあたり、 4 回 GitHub API が呼び出されることになります。

本事象の影響

CI/CD に tflint によるチェック処理を組み込んだ場合、本エラーによって CI/CD が失敗する場合があります。
Rate limit は認証なしの場合 IP アドレスごとにカウントされるため、CI/CD 環境が IP アドレスを固定していないか固定しているかで問題の性質が変わります。

CI/CD 環境が IP アドレスを固定しておらずクラウドプロバイダーなどが自動で割り当てる IP アドレスを使用する場合は、他のクラウドプロバイダーの利用者と IP を共有することになるため、これまでに他の利用者がその IP アドレスで行った処理によって偶発的に失敗する状況が起きます。
ただし再実行すれば正常に動作することが多いので、そこまで致命的な問題にはなりません。

一方、CI/CD 環境が IP アドレスを固定している場合、開発や運用に深刻な影響が出ることがあります。
企業内の開発・運用で使用する環境では、セキュリティ上の都合から IP アドレスを固定している場合が多いでしょう。
そういった環境では本エラーによって以下のような状況に陥ることがあります:

  • 最大で 1 時間、tflint を組み込んだ CI/CD が機能しない状態になる。
  • リリース処理を CD にゆだねている場合、発生のタイミングによってはとても大きな影響になる。
  • CI/CD のフローを変更して tflint の処理を無効化すればよいだけではあるが、CD による処理の自動化を行っている場合、とっさにその対応が取れる人がその場にいるとは限らない。
    • 内部処理についての完全な理解をしていなくても作業ができるようになることも CD の強みの 1 つなので、この対応が取れる人が常にいることは基本的に期待できない。
    • また、事象に遭遇した人が原因の判断ができない状況も考えられ、その場合は更に対応が困難になる。

事象が発生するタイミングが「CI/CD をどれくらい実行したか」依存になるため事前に予測が難しいことも、この事象と引き起こされる影響を扱いづらくします。また、「複数の開発者が同時に作業している」「新しい類似プロジェクトを始めた」など、CI/CD の必要度が高まったときに起きやすいため、影響が大きくなりがちです。

lint で Terraform テンプレートの事前チェックができるというメリットに比べて大きなリスクになりえます。

Rate limit の回避方法

そんなわけで、 tflint をプロジェクトに組み込む際にはこの Rate limit について事前に対処しておくのがオススメです。

回避方法一覧

回避方法 メリット デメリット
プラグインを使用しない * 対応が簡単
* tflint --init を実行する必要がなくなる
* プロバイダー固有のチェックが行えない
GitHub トークンを設定する * 正攻法 * GitHub ユーザーやトークンの管理が必要
プラグインをキャッシュする * CI/CDシステムごとにキャッシュの方法が異なる
プラグインをコードリポジトリーにコミットしておく * わかりやすい * アーキテクチャーが固定されてしまう

上記の他に、GitHub API をプロキシーしてキャッシュすることも考えられますが、あまりにも手間に見合わないのでここでは記載していません。

回避方法: プラグインを使用しない

プラグインを使用しなければダウンロードの必要もないので、 Rate limit の問題を起こさずに済みます。
Rate limit の問題に遭遇すると、その時点ですでにプラグインを使っている状況のためこの選択肢に気づきづらいのですが、考慮に値する選択肢と考えています。

以下のような場合に有効な選択肢です:

  • 対応にコストをかけたくない。
  • 確実に Rate limit エラーを避けたい。
    • tflint を組み込んでいる処理が重要なものである、影響範囲が非常に大きいものであるなど。
    • 他の方法では、設定ミスなどによって Rate limit を起こしてしまうリスクを完全には排除できない。
  • tflint の導入が Terraform のコードスタイル のチェックや、 Terraform 一般の実装エラーの検出を目的としている。
    • Terraform の言語自体のルールセットは tflint 単体にバンドルされているためプラグインとしてのダウンロードが必要ない。

また、副次的に以下のメリットを得られます:

  • tflint --init を実行する必要がなくなる。CI/CD 処理の実装が多少簡略化できる。

一方で、プロバイダー固有の設定エラーのチェックを行えないため、 tflint にその機能を期待している場合にはこの方法は採用できません。

回避方法: GitHub トークンを設定する

tflint 自身のドキュメントの Avoiding rate limiting でも言及されている通り、 GitHub トークンを設定することで Rate limit の上限を上げることができます。

GitHub トークンの設定有無により、Rate limit の挙動は以下のように変わります (Rate limits for the REST API - GitHub Docs より):

GitHub トークンなし (認証なし) GitHub トークンあり (認証あり)
呼び出し回数をカウントする単位 IP アドレス ユーザー
Rate limit 60 回/時間 5,000 回/時間

ほとんどのユースケースでは Rate limit が 5,000 回/時間もあれば不足することはないでしょう。

トークンの取得方法としては、パーソナルアクセストークン を使用する方法と、(試したことないけれど) github.com の GitHub Actions ワークフロー内で ${{ secrets.GITHUB_TOKEN }} を使用する方法が考えられます。
この他に GitHub AppsOAuth Apps を作成してトークンを発行する方法もありそうですが、基本的にはパーソナルアクセストークンを使用したほうが簡単でしょう。

パーソナルアクセストークンを使用する場合、「Fine-grained personal access tokens」で「Public Repositories (read-only)」を設定して作成します。
作成したトークンを環境変数 GITHUB_TOKEN に設定すると Rate limit の上限が上がった状態で tflint --init が実行されます。
認証無しで Rate limit を超えてしまったが、トークンを設定することで Rate limit を回避できた場合の動作例:

$ tflint --init
Installing "aws" plugin...
Failed to install a plugin; Failed to fetch GitHub releases: GET https://api.github.com/repos/terraform-linters/tflint-ruleset-aws/releases/tags/v0.32.0: 403 API rate limit exceeded for xxx.xxx.xxx.xxx. (But here's the good news: Authenticated requests get a higher rate limit. Check out the documentation for more details.) [rate reset in 8m19s]
$ export GITHUB_TOKEN="作成したパーソナルアクセストークン"
$ tflint --init
Installing "aws" plugin...
Installed "aws" (source: github.com/terraform-linters/tflint-ruleset-aws, version: 0.32.0)
$

技術的にはこの方法で Rate limit の問題を解決できますが、企業内のシステムで設定する場合には以下のような課題が生まれます:

  • 特定の開発者のパーソナルアクセストークンを使用すると、その開発者しか保守ができない状況が生まれてしまう。これを避けるためには共有ユーザーにあたる GitHub のユーザーアカウントを作成することになるが、その共有ユーザーの適切な管理が必要になる。
  • 企業のポリシー上、github.com のアカウントを自由に取得することを許可していない場合がある。アカウントの運用方法などについて、企業の IT ガバナンスを担当しているチームと十分な協議の上での導入が必要。
    • 企業で GitHub Enterprise Cloud を導入していれば、すでに適切な運用・管理体制ができあがっているかも。

これらのハードルから、現実問題としてこの方法は導入が難しい(手間が見合わない)ケースがあります。

回避方法: プラグインをキャッシュする

一度 tflint --init を実行した環境下で再度 tflint --init を実行すると、以下のようにプラグインのダウンロード処理がスキップされます:

$ tflint --init
Plugin "aws" is already installed
$

プラグインのダウンロード先はデフォルトでは ~/.tflint.d/plugins に設定されます。 tflint --init はすでにプラグインがダウンロードされている場合、ダウンロード処理をスキップするため、同じ環境で繰り返し tflint --init を実行しても Rate limit の問題に遭遇しません。

一方、CI/CD ではクリーンな環境で処理を行うようにセットアップすることが一般的なので毎回プラグインのダウンロード処理が必要になり、Rate limit の問題に遭遇しやすくなる原因となります。

このため、このダウンロードディレクトリーをバックアップしておいて CI/CD の開始時に復元することで、ダウンロード処理が不要になって Rate limit の問題を回避できます。tflint 自身のドキュメントの Avoiding rate limiting でもプラグインディレクトリーをキャッシュするという記載でこの手法が案内されています。

CI/CD 処理でプラグインのキャッシュを行うための処方は以下のようになります:

  1. TFLINT_PLUGIN_DIR 環境変数 を指定してプラグインのダウンロードディレクトリーを指定する。

    • 指定は絶対パスで行うのが安全です。tflint を --recursive オプションで実行すると、各サブディレクトリーをカレントディレクトリーとして処理を行うため、相対パスだと正常に動作しなくなります。

    • 設定ファイルの config.plugin_dir でも設定可能ですが、絶対パスを指定する都合上、環境変数のほうが扱いやすいでしょう。

    • 具体的には以下のような実行になります:

      $ export TFLINT_PLUGIN_DIR="$(pwd)/.tflint.d/plugins"
      $ tflint --init
      $ tflint
      
  2. CI/CD 内の処理に TFLINT_PLUGIN_DIR 環境変数で指定したディレクトリーをキャッシュする処理を実装する。

    • 具体的には以下の2つの処理になる:
      • tflint --init を実行したあとに TFLINT_PLUGIN_DIR 環境変数で指定したディレクトリーをバックアップする。
      • tflint --init の実行の前に (存在すれば) バックアップをTFLINT_PLUGIN_DIR 環境変数で指定したディレクトリーに展開する。
    • CI/CD ソリューションによってはディレクトリーをキャッシュする機能が提供されているので、それを使うのが簡単。

GitHub Actions で actions/cache を用いてこれを実装する例が terraform-linters/setup-tflint アクションのドキュメント に挙がっています (この例では TFLINT_PLUGIN_DIR を設定せずデフォルトの ~/.tflint.d/plugins が使用されている):

    - uses: actions/cache@v4
      name: Cache plugin dir
      with:
        path: ~/.tflint.d/plugins
        key: ${{ matrix.os }}-tflint-${{ hashFiles('.tflint.hcl') }}

注目するべき点としては、アーキテクチャー (linux-amd64 など) と設定ファイルのハッシュをキャッシュのキーとして含んでいる点です。
これにより tflint の複数のアーキテクチャーでも動作する仕様を維持し、また、プラグインの指定の変更を行ったときに設定ファイルのハッシュが変わることでキャッシュを作り直すようになっています。

キャッシュを取る方法の難点は以下の点です:

  • キャッシュの設定方法が CI/CD ソリューションごとに異なり、導入のハードルが高い。
    • ソリューションによってはキャッシュの機能がないことも考えられるので、最悪、CI/CD 内の処理として自前でキャッシュの実装が必要になり、それはそれでハードルが高い。
  • CI/CD が正常終了しないとキャッシュが作成されない場合がある。
    • GitHub Actions の actions/cache はこれに該当。
    • 新規プロジェクトの立ち上げ時に、別要因で CI/CD が失敗続きになる状況などでは、キャッシュがいつまでも作成されず Rate limit に引っかかる状況が起きやすくなる。
  • もし設定不備で Rate limit に引っかかってしまった場合、復旧に一度 Rate limit の解除を待つ必要がある。
    • キャッシュが正常に動作するようになるには、一度正常にプラグインのダウンロードができる必要があるため。このため設定の修正後、一度は Rate limit が解除された状態で実行できる必要がある。
    • 他の回避方法では、適切な設定を入れた時点で Rate limit を回避して復旧できる。
  • ディレクトリーの指定ミスなどでキャッシュが正常に動作していない事故を起こしやすい。
    • 設定に失敗していても、実行頻度が低い間は Rate limit に引っかからないので本格的に開発が始まるまで気づきづらい。

回避方法: プラグインをコードリポジトリーにコミットしておく

前項のダウンロードディレクトリーのキャッシュを一歩進めて、ダウンロードディレクトリーをリポジトリーにコミットしておく方法が考えられます。
コードリポジトリー上にファイルが存在するので、他の開発者にも仕組みがとても理解しやすいというメリットがあります。

この方法は以下の難点があります:

  • コードリポジトリーにバイナリーファイルをコミットすることになる。
    • 数十MB 程度のファイルなので、そこまで極端な負担にはならないことは期待できるが、なんかやだなあ。
  • プラグインの指定の変更(追加、削除、バージョン変更など)がある場合は、手動で適切にファイルを更新する必要がある。
  • ダウンロードディレクトリーは異なるアーキテクチャーのファイルが共存できないパス設計になっているため、プラグインを特定のアーキテクチャーに固定する必要がある。
    • tflint の異なるアーキテクチャーでも実行できる強みを薄めてしまう。
    • 一方で、(特に IaC について) CI/CD を複数のアーキテクチャーで実行することはあまりないので、実質の問題にはならないとは見込める。

今後の展望

  • tflint の issue に挙がっている Feature: allow plugin sources other than GitHub #1202 が何らかの方法でサポートされれば、 GitHub API を使わないプラグインのダウンロードが可能になって Rate limit の問題が根本解決するかも。

Discussion