Terraformのディレクトリ構成の模索

こんにちは、インフラの天津です。

今日はTerraformのディレクトリ構成について書きたいと思います。

きっかけ

現在、私が所属しているチームでは社内にTerraformを含むInfrastructure as Codeを
普及させるための活動を行っています。

チームの紹介については過去のブログを参照いただければ幸いです。

blog.engineer.adways.net

blog.engineer.adways.net

その際に、「Terraformのディレクトリ構成はどうあるのが良いのか」と悩んだので、
そのことをメモ代わりに書けたらと思いました。

謝辞

下記書籍とBlogを非常に参考にさせていただきました。ありがとうございました。

ディレクトリ構成における現在の課題

今の所、下記の2つが存在します。今回はこの課題について取りうる対応のパターンを洗い出してみます。

  • 異なる環境への対応
  • コンポーネント単位の分割

なお、2020-07現在、Hashicorp社公式ドキュメントにはディレクトリ構成についてのベストプラクティスは提示されていません。

先に結論

後述するパターンとユースケースが見いだせたものの、最終的にはサービス特性や
チーム状況によって決まることではあると思います。

まだ実際に適用してのプラクティスが少ないので粗いものではありますが、一つの指針として参考になればと思います。

弊社の状況

インフラ

インフラにはパブリッククラウド(AWS、GCP)、オンプレ(VMware vSphereによるプライベートクラウド)を利用しています。

オンプレは仮想マシン作成やロードバランサーの設定を管理部署で管理・実施しているため、
今回のブログではパブリッククラウド、特にAWSをターゲットにしていきます。

パブリッククラウドの利用状況ですが、最近ではproductionとstaging,developmentをアカウント単位で
分離しているパターンも増えてきています。

Terraformの利用状況

次にTerraformの使われ方です。現在は

  • サービスインフラの一部設定のコード化(スクリプト的な使用)
  • 全体設定(環境の再現性が高い)

といった使われ方がされています。上記が8:2くらいの割合で存在しています。

サービスインフラの一部設定(スクリプト的な使用) としては下記のようなものがあります。

  • AWS CloudWatchの設定
  • AWS AutoScaling関連の設定
  • GCP Cloud Monitoring(旧Stackdriver)シリーズの設定
  • インスタンスの構築のみ

また、全体設定としては下記のようなものがあります。

  • AWS ネットワークからS3, インスタンス、ALB作成など、サービス全体のインフラ構築
  • GCP GKE利用時ののVPC, GKEクラスター作成など全体の構築

個人的にはTerraform(Infrastructure as Code)のメリットとして、コード化のみならず、
環境の再現が容易になる点を享受するため、可能な限り対象のインフラ全体をカバーすることが
好ましいと考えています。

メリットの享受としては例えば、テスト環境を一時的に起動する、完了後に破棄する、などは
よく知られたものでしょう。

そういった全体構成をTerraformで構築する際に課題となるのがディレクトリ構成だと考えています。

次項からディレクトリ構成について記載します。

異なる環境へ対応するディレクトリ構成パターン

大まかに分けて下記パターンに分類してみました。

パターン1.環境分離パターン

いわゆるproduction,staging,developmentごとにディレクトリを分離します。

各環境ごとに設定を持ち、tfstateも環境ごとに作成となります。

terraform
  ├── development
  │   ├── alb.tf
  │   ├── ec2.tf
  │   ├── iam.tf
  │   ├── network.tf
  │   ├── provider.tf
  │   ├── rds.tf
  │   ├── s3.tf
  │   ├── tfstate_backend.tf
  │   └── vars.tf
  ├── production
  │   ├── alb.tf
  │   ├── ・・・・・・・・
  │   └── vars.tf
  └── staging
      ├── alb.tf
      ├── ・・・・・・・・
      └── vars.tf
  • メリット

    • 環境ごとの差異を吸収しやすい
    • 環境ごとにAWSアカウントが異なる場合もそれぞれのAWSアカウントでtfstateを管理可能
    • 構成がわかりやすい
  • デメリット

    • 同一の変更を複数環境で実施する必要がある(DRYではない)
  • ユースケース

    • 環境の差異が大きい場合
    • GCPプロジェクト、AWSアカウントが環境ごとに分かれている場合
    • Terraformに対しての習熟度が高くないチームで運用する場合

パターン2.workspace利用パターン

単一ディレクトリでworkspaceを用いて環境を切り替えるパターンです。

terraform
  ├── alb.tf
  ├── ec2.tf
  ├── network.tf
  ├── provider.tf
  ├── rds.tf
  ├── s3.tf
  ├── tfstate_backend.tf
  └── vars.tf
  • メリット

    • 同一設定が複数環境に適用される(ある程度DRY)
  • デメリット

    • 環境ごとの差異がある場合のケアが必要になる。
    • 環境ごとにAWSアカウントが異なる場合は利用ができない
  • ユースケース

    • 環境の差異が比較的少ない場合
    • GCPプロジェクト、AWSアカウントが環境ごとに分かれていない場合
    • workspaceへの理解が可能なチームで運用する場合

パターン3.環境ごと分離 + module利用パターン

「環境ごと分離」パターンにmodule利用をプラスしたものです。

参考:Creating Modules - Terraform by HashiCorp

terraform
  ├── module
  │   ├── s3
  │   │   ├── variables.tf
  │   │   ├── outputs.tf
  │   │   └── main.tf
  │   ├── rds
  │   │   ├── variables.tf
  │   │   ├── outputs.tf
  │   │   └── main.tf
  │   ├── network
  │   │   ├── variables.tf
  │   │   ├── outputs.tf
  │   │   └── main.tf
  │   ├── iam
  │   │   ├── variables.tf
  │   │   ├── outputs.tf
  │   │   └── main.tf
  │   ├── ec2
  │   │   ├── variables.tf
  │   │   ├── outputs.tf
  │   │   └── main.tf
  │   └── alb
  │       ├── variables.tf
  │       ├── outputs.tf
  │       └── main.tf
  └── environments
      ├── development
      │   ├── alb.tf
      │   ├── ec2.tf
      │   ├── iam.tf
      │   ├── network.tf
      │   ├── provider.tf
      │   ├── rds.tf
      │   ├── s3.tf
      │   ├── tfstate_backend.tf
      │   └── vars.tf
      └── production
          ├── alb.tf
          ├── ec2.tf
          ├── iam.tf
          ├── network.tf
          ├── provider.tf
          ├── rds.tf
          ├── s3.tf
          ├── tfstate_backend.tf
          └── vars.tf
  • メリット

    • modulueによりDRYに書ける
  • デメリット

    • 環境ごとの差異がある場合のケアが必要になる。
    • 初学者にはわかりにくい場合がある
  • ユースケース

    • 環境の差異が比較的少ない場合
    • GCPプロジェクト、AWSアカウントが環境ごとに分かれている場合
    • moduleへの理解が可能なチームで運用する場合

コンポーネント分割のパターン

ディレクトリ構成におけるもう一つの課題はコンポーネント単位の分割です。( ここでのコンポーネントとはリソースの意味のあるグループを意味しています。)

これまでは環境ごとにディレクトリを分割することで各環境のtfstateを分離していました。

環境ごとにtfstateが1つある状態です。

環境内でコンポーネント単位に分割することでtfstateファイルが分離され、お互いに影響を与えることが少なくなります。

分割の際の考慮点としては下記があるかと思います。

  • 安定度(変更頻度)
  • ステートフルかどうか
  • 影響範囲

例えば よくあるWebサービス(ALB + EC2 + RDS + Elasticash + S3)の場合は、下記のように分けることが考えられます。

  • 安定度の高いもの、ステートフルなリソースを分離
  • 変更が頻繁にあると予測されるものをroot ディレクトリに配置
terraform
  └── development
      ├── alb.tf
      ├── ec2.tf
      ├── iam.tf
      ├── provider.tf
      ├── security_group.tf
      ├── tfstate_backend.tf
      ├── vars.tf
      ├── cache
      │   ├── elasticache.tf
      │   ├── provider.tf
      │   ├── vars.tf  # 上位dirのシンボリックリンク
      │   └── tfstate_backend.tf
      ├── db
      │   ├── db.tf
      │   ├── provider.tf
      │   ├── vars.tf  # 上位dirのシンボリックリンク
      │   └── tfstate_backend.tf
      ├── network
      │   ├── vpc.tf
      │   ├── igw.tf
      │   ├── subnet.tf
      │   ├── natgateway.tf
      │   ├── routetable.tf
      │   ├── provider.tf
      │   ├── vars.tf  # 上位dirのシンボリックリンク
      │   └── tfstate_backend.tf
      └── s3
          ├── provider.tf
          ├── s3.tf
          ├── vars.tf  # 上位dirのシンボリックリンク
          └── tfstate_backend.tf

では何を選ぶべきなのか?

観点として大きいのは習熟度、サービスの状況だと考えており、どの構成を採用するかはチームごとに議論が必要と思います。

個人的には下記を考えています。

  • 環境ごとの分離+module化+適切なコンポーネント分割がDRYに書けて運用しやすそう
  • 環境ごとの分離のみも実はシンプルかつ柔軟性が高いため使い勝手が良さそうかも?

終わりに

弊社でのTerraform活用はまだまだこれからなのですが、全体で議論を進めプラクティスを積み上げてまた記事にしたいと思います。

最後までお読みいただきありがとうございました。