Upgrade to Pro — share decks privately, control downloads, hide ads and more …

tfactionで作るTerraform CI統制 〜 apply実行基盤の構築に向けて 〜

Avatar for Sugita Sugita
September 17, 2026
250

tfactionで作るTerraform CI統制 〜 apply実行基盤の構築に向けて 〜

Avatar for Sugita

Sugita

September 17, 2026

Transcript

  1. 自己紹介 名前: 杉田 青哉 所属: ENECHANGE株式会社 SRE・infraチーム 技術スタック: AWS, Ruby

    経歴 2023年1月 ENECHANGE入社 ウェブアプリケーション開発に従事 2025年4月 SREチーム発足 SREとしてオブザーバビリティや信頼性向上に注力 X: @Mnbvc124 Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 2
  2. INDEX 目次 01 現状の課題と目指す理想像 02 Terraform CIの全体像 03 設計上の4つの判断 04

    導入後の変化と今後 3 Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 3
  3. 課題 Terraform運用には大きく2つの課題があった 1 変数の値がローカル管理で、CIでplanを実行できない input variablesの値は各自のローカルにしかなく、CI上でplanが動か せない。plan結果がレビューに乗らず、レビュアーも変更の影響を 確認できなかった # 例)

    xxxx.tf variable "instance_type" {} resource "aws_instance" "app" { instance_type = var.instance_type } ※ input variables = 外部から値を渡せるTerraformの変数 2 applyの実行経路が統制されていない 承認がなくてもterraform applyを実行できる状態で、 いつ・誰がapplyしたかを管理できていない Copyright © ENECHANGE Ltd., All rights reserved. local$ terraform apply # 承認なしでも実行できてしまう Confidential / not for distribution LON 6
  4. 理想に至る3ステップと現在地 目指す姿: 開発者はPRを出すだけ、統制は仕組みが担保する 今ここ(本発表) ステップ 1 ステップ 2 ステップ 3

    input variablesの廃止 Terraform CIの強化 apply実行経路の標準化 秘匿情報はSSM Parameter Storeへ 固定値はlocalsに置き換え tfactionでplanを自動検証し エラーはマージブロック ローカルからのapplyを閉じ 統制された経路へ一本化 完了 対応中 今後 input variablesが残るとCIのplanが失敗するため 1 → 2 の順、 planの品質担保がapply統制の前提となるため 2 → 3 の順で進める Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 8
  5. CIの全体像 PRの作成・更新をトリガーに、4つのジョブが実行される PRを作成 / 更新 list 変更ファイルから plan 対象ディレクトリを検出 check-tfaction-yaml

    tfaction.yaml の置き忘れを検知 check-and-plan GitHub Actionsのmatrixで、対象ディレクトリごとにジョブを生成して 並列実行 → init → fmt / validate → tflint → trivy → conftest → plan plan結果は tfcmt がPRコメントに投稿 ※ tfcmt = plan/apply結果を整形してPRコメントに投稿するツール ※ trivyのみ警告モード(詳細は後述) status-check Copyright © ENECHANGE Ltd., All rights reserved. 全ジョブの成否を集約 = required check Confidential / not for distribution LON 11
  6. 判断①: plan対象はホワイトリスト方式で管理する tfactionは tfaction.yaml を置いたディレクトリだけをplan対象とみなす。除外設定は存在しない ディレクトリ構成のイメージ やったこと ・既存のroot module(backend定義を持つディレクトリ)すべ てに、中身

    {} の tfaction.yaml を一括配置 product-a/ ・docsやmodules配下には置かない prod/ stg/ # backend定義を持つ terraform { required_providers { ... } backend "s3" { bucket = "xxxx" key = "xxxx" } } Copyright © ENECHANGE Ltd., All rights reserved. docs/ Confidential / not for distribution (backend 定義あり + tfaction.yaml) (backend 定義あり + tfaction.yaml) (置かない ) LON plan対象 plan対象 plan対象外 14
  7. 判断②: 「置き忘れ」を構造的に防ぐ ホワイトリスト方式には弱点がある 新しくroot moduleを作ったときに tfaction.yaml を置き忘れると、planが一度も実行されないまま静かにマージされてしまう → ガードジョブ check-tfaction-yaml

    を追加 「backend定義があるのに tfaction.yaml がない」ディレクトリを検知して失敗させる。置き忘れはCIの失敗として顕在化し、チェッ ク漏れの経路が構造的に塞がる 新規作成時の対応は1行だけ $ echo '{}' > path/to/new-dir/tfaction.yaml Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 15
  8. 判断③: マージブロックは集約ジョブで実現する どれか1つでもplanが失敗したら、確実にマージを止めたい planが1つ失敗しても、マージできてしまう plan (dirA) ✓ plan (dirB) ✓

    plan (dirC) ✗ → それでもマージ可 マージ条件は固定のチェック名単位でしか見られない planジョブはディレクトリごとに別名で生成されるため、すべてを登録して見張ることができない → 全ジョブの成否を status-check に集約し、これ1つをマージ条件に登録 どれか1つでも失敗すれば status-check が失敗し、確実にマージが止まる Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 16
  9. 判断④: CI本体は専用リポジトリに集約する(Reusable Workflow化) CIを入れる対象のTerraformリポジトリは複数ある 各リポジトリにワークフローやポリシーをコピーして回ると、修正のたびに全リポジトリへ反映する作業が発生し、バージョンもば らけていく → 専用リポジトリに集約し、各リポジトリはReusable Workflowとして呼び出すだけ ・集約したもの:

    CIのロジック / 自社ポリシー(Regoとそのテスト)/ 各パッケージのバージョン管理(aqua)/ tfcmtのテンプレート ・CI本体を1つのリポジトリにまとめて、共通ワークフロー(Reusable Workflow)として全リポジトリに展開することで、 組織全体で統制 Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 17
  10. Lintとポリシーチェックの組み込み 実行順: init → fmt -check / validate → tflint

    → trivy → conftest → plan チェック 扱い 内容 terraform fmt / validate マージブロック フォーマット崩れ、構文・参照エラー tflint マージブロック required_versionの欠如、未使用宣言などの品質ルール違反 conftest マージブロック 自社ポリシー違反(次ページ) terraform plan マージブロック plan自体の失敗 trivy 警告のみ セキュリティ設定ミス(公開S3、SG全開放など) trivyだけを警告モードにしている理由 既存コードへの指摘が一定数あり、導入と同時にブロックすると既存コードに触るすべてのPRが止まる。 まず指摘を可視化して棚卸しし、完了後にマージブロックへ昇格させる段階導入 Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 18
  11. conftestによる自社ポリシー①: variable宣言の禁止 ステップ1で廃止したinput variablesが新しいコードで再び書かれることを、レビューではなく仕組みで防ぐ ✗ NG: variable宣言 → conftestがブロック variable

    "instance_type" { type = string } ✓ OK: 置き換え先までエラーメッセージで案内 # 固定値は locals に locals { app_port = 3000 } # 既存リソースの参照は data に data "aws_ssm_parameter" ... ポリシー自体の品質もCIで担保 意図的に除外したい場合の設計 ・Regoは本体とテストをペアで専用リポジトリに配置 ・原則「できるだけ狭い範囲で、理由をコメントに書いて除外」 Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 19
  12. conftestによる自社ポリシー②: コスト配分タグの強制 moduleのリリースタグの最新バージョンを使用していることもチェックする ✗ 違反時のエラーメッセージ(実際の出力) ✓ 求めるパターン(例) FAIL xxx/xxx.tf module

    "tags" の ref が最新ではありません (現在: v0.2.1 )。source の ?ref= を 最新バージョン v0.3.0 に更新してください provider "aws" { default_tags { tags = module.tags.default_tags } 18 tests, 17 passed, 1 failure } module "tags" { source = "…/module?ref=v0.3.0" } → 有効化はリポジトリ側の1行だけ。オプトイン方式を採用 with: cost_tags_policy: true 管理したいタグがアカウントごとに異なるためオプトイン方式に。 ポリシー本体は専用リポジトリ(判断④)にあり、必要なリポジトリから1行で有効化できる Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 20
  13. おまけ: tfactionの利用企業一覧に掲載されました 利用表明とフィードバックも、OSSへの貢献のひとつ "Thank you for using tfaction and sharing

    a nice post!" — suzuki-shunsuke さん(tfaction作者)からの返信 本CIは tfaction / tfcmt / aqua など、suzuki-shunsukeさんのOSSに支えられています。感謝を込めて Copyright © ENECHANGE Ltd., All rights reserved. Confidential / not for distribution LON 24
  14. Confidential © ENECHANGE Ltd. Copyright © ENECHANGE Ltd., All rights

    reserved. Confidential / not for distribution LON 25