はじめに

株式会社AJA でバックエンドエンジニアをしている片山です。

Infrastructure as Code (IaC) の運用において、Terraform は広く使われていますが、その運用方法は組織によって様々です。私たちのチームでは、当初 Dev Container 上で Terraform を実行していましたが、運用上の課題を抱えていました。また、module を積極的に活用していたことで、管理コストの増加にも悩まされていました。

この記事では、Dev Container での Terraform 運用から Atlantis へ移行した経緯と、module 戦略の見直しによって得られた恩恵を紹介します。

想定読者:

  • Terraform をローカルまたは Dev Container で運用している方
  • IaC の運用課題(誰が何を変更したか分からない、オペレーションが属人化など)を抱えているチーム
  • module の管理コストに課題を感じているチーム

この記事で得られること:

  • Dev Container 運用の課題とその解決アプローチ
  • Atlantis を使ったプルリクエスト(PR)ベースの Terraform 運用の実例
  • module 化を最小限に抑える方針とそのメリット

Dev Container 運用での課題

まず、Dev Container で Terraform を運用していた際に抱えていた課題を整理します。

① いつ、誰が修正したか分からない
ローカル実行では、GitHub の PR やコメントに実行履歴が残りません。そのため、いつ誰がどのような変更を加えたのか追跡が困難で、オペレーションが暗黙知になりがちでした。

② ローカル実行は事前の plan と差分が生まれやすい
Dev Container での実行は、事前にレビューした plan 結果と、実際の apply 時の結果に差分が生まれるリスクがあります。

③ 破壊的な変更への対応が属人化
tfstate が一致していない状態で破壊的な変更が生まれると、一致させる作業が必要になります。しかし、旧オペレーションを知っている人しか対応できず、これもまたチーム全体でのリードタイムを増加させていました。

Atlantis とは

これらの課題を解決するために、Atlantis の導入を決めました。Atlantis は、PR ベースで Terraform 運用を自動化するツールです。CNCF Sandbox プロジェクトとして開発が進められており、エンタープライズでの採用実績も豊富です。

主な機能:

  • GitHubコメントで Terraform 操作:PR にコメントするだけで terraform planterraform apply を実行
  • plan/apply 結果の可視化:実行結果がすべて GitHub のコメントに記録されるため、レビュー可能
  • 同一ディレクトリ実行の禁止:ロック機構があるため、同一環境を同時に変更してしまうリスクがない
  • 未 approve の場合 apply を禁止:承認フローを組み込める(生成 AI による自動化のガードレールとしても働く)

など、多くの機能が提供されています。

実行環境の選定

Atlantis の導入にあたり、まず実行環境を検討しました。GKE と Cloud Run の 2 つの選択肢を検討しましたが、以下の理由で最終的に GKE を選択しました。

Cloud Run を選択しなかった理由:

  • Cloud Run はステートレスなコンテナ環境のため、Atlantis のロック情報を永続化するには外部ストレージの追加構成が必要
  • チームとして Cloud Run の運用経験が少ないため、導入・運用の負荷が高くなる

GKE を選択した理由:

  • PersistentVolume を使用でき、Atlantis のデフォルト構成をそのまま利用可能
  • チームに GKE の運用経験があり、トラブルシューティングがしやすい

アーキテクチャ

GKE にデプロイされた Atlantis のアーキテクチャは以下のようになります。

atlantis-architecture

コンポーネント:

  • Atlantis Server:GitHub からの Webhook を受け取り、Terraform コマンドを実行
  • Google Cloud Resource:Terraform で管理するリソース群

各経路の認証方式は以下のとおりです。

経路 認証方式
GitHub → Atlantis(Webhook) Webhook Secret の HMAC 認証
Atlantis → Google Cloud(plan/apply) Workload Identity
Atlantis → GitHub(コメント) GitHub App の Installation Access Token

この設計により、永続的な権限を払い出すことがなく、セキュリティリスクを最小化しています。

アクセス制御

Atlantis は Terraform を実行するため、Google Cloud リソースを変更できる強い権限を持ちます。そのため、アクセス制御は慎重に設計しなければなりません。

一方で、Atlantis は GitHub からの Webhook を受け取るため、エンドポイントをインターネットに公開する必要があります。しかし、そのままでは Atlantis UI も外部からアクセス可能になってしまいます。Identity-Aware Proxy を使うと GitHub の Webhook が通らなくなるため、Cloud Armor で IP ベースのアクセス制御を実装しました。

許可する IP:

  • GitHub Webhook の送信元 IP(GitHub Meta API で取得可能)
  • 社内ネットワークの IP

これにより、Webhook の受信を維持しつつ、UI へのアクセス制限を可能にしました。

リポジトリ構成と module 戦略

ここまで Atlantis のインフラ設計について説明しました。次に、Terraform コード自体の管理方針について紹介します。

リポジトリ構成

Atlantis の導入に合わせて、リポジトリ構成と module 戦略も見直しました。これまではアプリケーションコードと同じモノレポで Terraform を管理していましたが、以下の理由から独立したリポジトリに切り出しました。

  • インフラとアプリケーションの変更サイクルが異なる
  • マージルールやレビュー体制を分けられる

ディレクトリ構成は以下のとおりです。

├── modules                     # 共通module
│   ├── CLOUD_SERVICE           # Cloud Service ごとにディレクトリを区別
│   │   ├── MODULE_NAME         # サービス側の利用を想定したmodule
:   :   :
│
├── platform                    # 基盤システムのリソース
│   ├── CLOUD_SERVICE           # Cloud Service ごとにディレクトリを区別
│   │   ├── SYSTEM_NAME         # システム名
│   │   │   ├── ENVIRONMENT     # 動作環境名
:   :   :
│
├── services                    # 各チームが管理するサービスのリソース
│   ├── CLOUD_SERVICE           # Cloud Service ごとにディレクトリを区別
│   │   ├── SERVICE_NAME        # サービス名
│   │   │   ├── ENVIRONMENT     # 動作環境名

Terraform のディレクトリ分割には唯一の正解がないため、チームで議論しながら決めました。また、生成 AI にも伝わりやすい明確なルールにしておくとコード生成時にも役立ちます。

module 化の方針

リポジトリ移行前は module を積極的に活用していましたが、運用を続ける中で課題が見えてきました。

  1. 認知負荷の増加
    • provider と異なる独自仕様にドキュメントが追いつかない
    • どんな module があるか暗黙知になりやすい
  2. バージョン管理の複雑化
    • provider のアップデートに追従しようとすると影響範囲が甚大
    • 同一機能の複数バージョンが混在し、どれを使うべきか不明確
  3. 設計の問題
    • 異なるユースケースを 1 つの module に押し込めた結果、例外対応のためのフラグが増殖
    • 過度に細分化すると module 化のメリットが薄れる

これらを踏まえ「module の利用は最小限に留め、provider のリソースを直接利用する」という方針に切り替えました。結果として、公式ドキュメントベースで開発できるようになり、生成 AI との相性も良くなりました。

Atlantis での Plan / Apply フロー

ここからは開発時の操作フローを説明します。

atlantis-plan-apply-flow

Plan は、PR を作成する(もしくは plan とコメントする)と自動で terraform plan が実行され、結果が PR にコメントされます。レビュアーはこのコメントを見て変更内容を確認できます。

Apply は、レビュー完了後に atlantis apply とコメントすることで実行されます。apply の結果も PR にコメントされるため、誰がいつ適用したかが記録に残ります。

実際の PR では以下ように表示されます。

atlantis-plan-example

移行してみた結果

Atlantis 導入、module 化を最小限にしたことによって、実際に感じた恩恵は以下です。

  1. PR に操作が記録される
    • すべての操作履歴が GitHub に残るため、振り返りが容易
    • 過去の操作を生成 AI に few-shot prompting として渡せる
    • いつ誰が何を変更したかが明確
    • tfstate の不整合の調整も GitHub 上に残る
  2. 直前の plan が必ずコメントに残るため意図しない変更を防げる
    • レビュー時に plan 結果を確認可能
    • apply 前の最終確認が容易
  3. module の管理コストが下がった
    • 新しいリソースは公式ドキュメントを見れば分かる
    • 生成 AI を活用した実装もスムーズ
    • バージョン更新の影響範囲が限定的

まとめ

Atlantis 導入と module 戦略の見直しにより、Terraform 運用の属人化を脱却させることができました。module 化を最小限に抑えるという判断は、保守性と学習コストのバランスを取る上で重要なポイントでした。公式 provider の仕様に沿った実装により、新規メンバーでも理解しやすく、生成 AI の活用もしやすい環境を実現できています。

Terraform や IaC の運用に課題を感じている方は、ぜひ Atlantis の導入を検討してみてください。

また、今回は jun06t さんにご協力いただき、Terraform 運用の改善を行いました。設計から実装まで多くの議論を重ね、より良い運用体制を構築できました。この場を借りて改めて感謝いたします。

アバター画像
2024年サイバーエージェント新卒入社。株式会社AJAにて「AJA DSP」の開発に従事しています。