🛠️

tfactionを導入しようと3日試行錯誤して結局導入しなかった話

2025/02/27に公開

tl;dr

去年tfactionを導入しようとしましたが、試行錯誤ののち以下の点を重く見て結局導入は見送りました。

  1. 導入のためのドキュメントがまとまっていない
  2. 構造が複雑すぎる
  3. unix哲学に沿っていない(直交性がない)

注意事項

本記事は導入しなかった話という内容の記事ですが、tfactionを全否定したりFUDをする目的のものではありません。
あくまで私達の環境とマッチするか検討して最終的に導入しなかった事実と、そこに至った過程・考えを書くことで、私と同様にtfactionの導入を検討している人の参考になったらと思い書いております。
なので、この記事を元にtfactionは全くだめだといった曲解や制作者への攻撃のために引用しないでください。

そもそもtfaction自体OSSで制作者の個人的な努力の成果物を無償で使わせてもらっており、そこについては感謝しかありません。さらに言えば、今回直接の導入はしなかったのですが中の設計やコード、アイデアについては多数参考にさせてもらっており(Issueを使ったdrift detectionの記録など)、依然多くの恩恵を受けています。この場を借りて、制作者のsuzuki-shunsuke氏に感謝を伝えたいと思います。

背景

2024年11月、新しいチームへ異動しました。
そこではまだterraformのCIが完備されておらず、自動planや自動applyの導入余地がありました。
そこで今まで3チームでやってきたようにterraform周りの自動化を進めようとしたのですが、流石に同じことを何度もやるのが非効率に感じたので、以前から気になっていたtfactionを導入しようと挑戦しました。

tfactionとは

https://suzuki-shunsuke.github.io/tfaction/docs/

tfaction is a framework for a Monorepo to build high-level Terraform workflows using GitHub Actions.
tfactionは、GitHub Actionsを使って高レベルのTerraformワークフローを構築するMonorepoのためのフレームワークです。

とある通り、

  • Terraform
  • GitHub Actions
  • Monorepo

を満たす環境で、高レベルのCICDを自動的に構築してくれるワークフローです。
制作者はsuzuki-shunsuke氏で、彼の開発したツールとしては他にterraformの出力結果を整形するtfcmtも有名です。

車輪の再発明を避けるため、以前から自分の環境をtfactionへ寄せられないか検討していました。今のチームはtfactionとぴったりフィットするように見えたので、これを機会に導入することにしました。

導入の試行錯誤から断念まで

導入を開始したのが2024/11/14、そして断念したのが11/18でした (チケットの記録より)。
断念したのは、当初私がtfactionに想像していたものと実態にズレがあり、想定していたようなメリットが得られなさそうなこと、また、長期的な観点からみても導入し、tfactionに合わせることのコストがそれによるリターンと釣り合わないだろうと感じたためです。
以下で試行錯誤の中で感じた点を挙げます:

  1. 導入のためのドキュメントがまとまっていない
  2. 構造が複雑すぎる
  3. unix哲学に沿っていない(直交性がない)

この3つはそれぞれ後者が前者の原因となっていると考えています。
以下で順番に説明します。

1. 導入のためのドキュメントがまとまっていない

tfactionは、比較的ドキュメントがよく書かれているOSSです。ここは多くの人が同意できる点だと思います。

しかし、tfactionの導入は実際のところ簡単ではありませんでした。
最大のつまづきは、私がtfactionを1つもしくは少数のActionを中心としたツールだと思っていたことです。
ご存知の通り、GitHub Actionsで処理を再利用可能にする場合はActionsを使うことが一般的です。
また、ActionではREADMEのUsageやQuickstartにworkflowでの記述例を書くことがよくあります。

HashiCorp - Setup Terraformの例:

steps:
- uses: hashicorp/setup-terraform@v3

これが多くのActionのドキュメントで行われているため、初めて使う場合でもかなりの短時間、場合によってはものの数分で使えるようになります。

tfactionでも同様のことを想定したのですが、まず、tfactionはGitHub ActionsのMarketplaceへ登録されていませんでした。

多くのActionは利用者がアクセスしやすいよう、ここに登録することが一般的なのでここで少し困惑します。また、リポジトリのREADMEにもドキュメントサイトにもworkflowの記述例が見つかりません。
この時点でかなり戸惑いました[1]

導入手順のページである Set up | tfaction を見るのですが、やはりworkflowの記述は一切なく、必要な設定ファイルの記述が主です。

ここに至って、やむなく実際に動いているサンプルを見つけ、それを参考に導入する方針へ切り替えました。
幸い、作者公式のサンプルリポジトリが見つかったため、それを参考にすることにします。

ただ、この例についても20以上のyamlファイルがあり、かつそれらが相互に呼び出したり影響し合ってtfaction全体の機能を実現しているため、読み解くのはかなり大変でした。

これらを参考に一旦手元で最低限動く状態を目指します。
なのですが、ここからも大変でした。

2. 構造が複雑すぎる (密結合すぎる)

今回のtfactionの導入に当たり、まずはその機能を部分的に導入しようと思いました。
それは以下の要因からです:

  • すでに一部のterraformディレクトリではplan/applyの自動化が導入されており、そことの競合を避けたい
  • tfmigrateは不必要なので入れたくない (movedやimport,removeブロックがあるため)
  • Drift Detectionやtfsecなどのlinterも最初は動かしたくない (最初から全て来ると対処できないので、段階的に有効化していきたい)

もともとミニマムに導入してそれが成功してから、徐々に範囲と機能を拡大していくことが、リスクの最小化とリターンの最大化の観点で最も良い方法だと考えていることも一因です。

そのため、最初のマイルストーンをplanの自動化に絞ります。しかし、結論から書けばそれはかなり困難でした。

planのtfaction化の試行と断念

tfactionのplan部分のworkflowの例がこれです:

もしかしたらもっと良い例があるのかもしれませんが、公式のサンプルなこともありこれをお手本にしました。

まず、コアであるplanの実行ステップを動かそうとします。

  test:
    uses: ./.github/workflows/wc-test.yaml
    needs: path-filter
    permissions:
      id-token: write
      contents: read
    with:
      ref: ${{needs.path-filter.outputs.merge_commit_sha}}
    secrets:
      gh_app_id: ${{secrets.APP_ID}}
      gh_app_private_key: ${{secrets.APP_PRIVATE_KEY}}
      terraform_private_module_ssh_key: ${{secrets.TERRAFORM_PRIVATE_MODULE_SSH_KEY}}
      TFE_TOKEN: ${{secrets.TFE_TOKEN}}

この参照先が以下で、

さらに以下を参照します:

そして、このワークフローは以下のアクションに依存します:

  • actions/checkout
  • tibdex/github-app-token
  • aquaproj/aqua-installer
  • suzuki-shunsuke/lock-action
  • suzuki-shunsuke/tfaction/setup
  • suzuki-shunsuke/tfaction/get-target-config
  • suzuki-shunsuke/tfaction/test
  • suzuki-shunsuke/tfaction/plan

前述の通り最小構成でまずは試したかったため、suzuki-shunsuke/tfaction/plan だけで動かそうとします。しかし、これは残念ながら動きませんでした。suzuki-shunsuke/tfaction/setup でインストールされているコマンドなどに依存していたためです。同様に aquaproj/aqua-installer にも依存しています。ここの詳細・各々の依存関係は2ヶ月前のこともあり若干あやふやな点もあるのですが、大筋としては前のステップでインストールされたコマンドや環境変数に、後のステップが暗黙的に依存しているため、 suzuki-shunsuke/tfaction/plan だけを使うことを阻んでいました。

https://github.com/suzuki-shunsuke/tfaction/blob/e12c2191dbb5cf4b885f43d5bddb3bfb21a04681/plan/action.yaml#L17

  steps:
    - uses: suzuki-shunsuke/tfaction/terraform-plan@main
      if: env.TFACTION_JOB_TYPE == 'terraform'
      with:
        github_token: ${{inputs.github_token}}

    - uses: suzuki-shunsuke/tfaction/tfmigrate-plan@main
      if: env.TFACTION_JOB_TYPE == 'tfmigrate'
      with:
        github_token: ${{inputs.github_token}}

↑ 暗黙的に宣言された環境変数への依存の例

この構造があまりにネストが深く暗黙的な相互依存も多いため、tfactionを使うに当たって部分的な導入は厳しいと感じました。一括で導入するか、もしくは導入しないかでないと厳しいという判断です。
それは上記の構造もですが、もし仮に部分的な導入を頑張って行ったとしても、そのインターフェイスの仕様はREADMEなどに書かれていないため今後変わる可能性が高いためです。もしそこが変更された場合、対応のコストがかかってしまいます。

まとめると、以下のようになっていたら部分的な導入も可能だったかもしれません:

  1. 個別のアクションがmarketplaceに登録され、仕様がREADMEに明記されている(= 安定的なインターフェイスが定義されており、セマンティックバージョニングで破壊的変更が制御されている)
  2. 前のステップと次のステップのアクション間の情報の引き渡しが暗黙的(環境変数)ではなく、stepのoutputやwithを使って明示的に行われている
  3. 前のステップでインストールされたコマンドへの依存が明示されている

ただ、上記のようになっているのは意図してそうなっていると思われます(おそらく、いわゆる内部仕様だと解釈している)。なので、個別のパーツを使おうとするその使い方自体が意図に沿っていないと言えるかもしれません。

3. unix哲学に沿っていない(直交性がない)

最後は上記のような構造になっている理由の推測です。
tfactionは、私見ですが特にunix哲学を意識して設計されていません。
ここで言うunix哲学は、

  • それぞれのプログラムが1つのことをうまくこなすように。新しい仕事をするために、新しい「機能」を追加して古いプログラムを複雑にするのではなく、新しいプログラムを構築する。

のことです。
この1つのプログラム(コマンド)が1つのことをよくこなせることで、それを組み合わせることによりニーズに合わせてどんな処理も楽に書ける。unixのコマンド・パイプの思想についてこのunix哲学を引き合いに出して説明します。現代の設計思想で言えば、疎結合やSOLID則とも共通するものがあります。

その観点で見ると、tfactionは多くのことをtfacitonですべてうまくこなそうとしています。
その結果、前述の通りAWSでもGCPでもないproviderをサポートしていなかったり、tfmigrateが不要であってもとりあえず環境変数やコード中に出てきたりします。

これはある程度方法論が固まったものに対してはall in oneに解決できて便利ではありますが、terraform周りのCIについては意外と組織・プロダクトのフェイズによって違うということを私は経験してきました。
その意味で、tfactionのその将来的な保守とtfactionのカバー範囲にプロダクトが留まる可能性、そこに留めておかないといけないリスクなどを加味した結果、今回はtfactionを使わないほうがベターだと判断しました。

まとめ

というわけで、以下の3つを理由として今回はtfactionの導入を見送りました。

  1. 導入のためのドキュメントがまとまっていない
  2. 構造が複雑すぎる
  3. unix哲学に沿っていない(直交性がない)

実際のところ、ドキュメントは追記・整理すればいいですし、構造の複雑性は大変ではありますがリファクタリング・整理を繰り返すことで改善できます。
ただ、3のunix哲学ベースでないこと、直交性がないことはソフトウェア・フレームワークの根本である設計思想のところであり、コード修正やリファクタリングで変わるものではありません。
前述の通り直交性がないものは他と組み合わせることが難しく、必然そちらのツールのやり方へ合わせて使う必要性が強いです(引っ張られる)。
一回投資してしまったものについてはサンクコスト効果もあり戻すのが大変なため、今回は一旦見送りました。

ただ、同じ会社でも別のチームではtfactionを現在進行系で活用できているケースもあります。
そちらでは、開発フローやスタイルを完全にtfactionへ合わせているようです。
tfactionを活用するならば、やはり極力tfaction wayに合わせることが鍵かなと思います。


蛇足: tfactionを使わずどうしているか

前述の通り、tfactionの思想や機能はterraform CICDのお手本として非常に優れているので、それを既存のよく知られた公開Actionを活用しながら同等のことを実装しています。
主な利用Actionは以下です:

脚注
  1. 実はこの2024年11月より前にも何度かtfactionのことを調べていたのですが、この導入方法がはっきりわからず導入コストが重いと見ていたため、ずっと見送っていた背景がありました ↩︎

株式会社エス・エム・エス

Discussion