チームで使用する[Terraform]ベストプラクティスを策定しました

はじめに

こんにちは(`・ω・´)
技術本部 技術戦略ディビジョンでシステムエンジニアをしています山中です。

趣味はゲームでスプラトゥーンをやってます。3/30-3/31に開催があったスプラトゥーン甲子園を見て熱狂してました笑

そしてその日とほぼ同時に、Terraformを開発したHashiCorpからStyleGuideの発表がありました!!
そこで、チームで使用しているTerraformのベストプラクティスの見直しと更新をしました。
今回はその内容をご紹介させていただきます。

背景

Terraformは非常に柔軟性が高いです。その分、作成者によって記法が変わることがあると思います。
そのため、チーム全体で可読性・統一感を高める目的でベストプラクティスを定めることにしました。

対象者

この記事は下記のような人を対象にしています。

  • Terraformのプラクティスが決まっていない方。
  • チームでTerraformを使っているけど基準が曖昧な方々。

ベストプラクティスの内容

公式のStyleGuideとプラスでチーム内のルールを決めました。今回はそのプラスでチーム内で決めたルールのみをご紹介させていただきます。

使用するTerraformのバージョン

基本的にTerraformの最新バージョンを使用。
Terraform versions : TerraformVersions|HashiCorpReleases
またtfenvというTerraformのバージョン管理ツールで利用。

コーディング規約

  • moduleで定義したディレクトリの中にREADME.mdを作成し、そのディレクトリ内の概要を説明。
  • AWSでのTerraform利用ルール
    • providerでdefault_tagsを使用して、resource名にタグを付与すること。

セキュリティチェック

セキュリティチェックにはtrivyを使用。

tfsecのREADMEでは、trivyへの移行が推奨されています。

📣 tfsec to Trivy Migration As part of our goal to provide a comprehensive open source security solution for all, we have been consolidating all of our scanning-related efforts in one place, and that is Trivy.

ディレクトリ・ファイル構成

moduleの呼び出しパターンと環境別構築パターンの2種類を紹介。

1. module呼び出しパターン

  • moduleディレクトリ内でmoduleブロックを定義して呼び出す形式。
  • ディレクトリ構成は、moduleディレクトリと環境管理ディレクトリを含む。

メリット

  • stagingproductionなど複数の環境間で、共通の構成で再利用可能。
  • moduleごとに機能を分割することで、コードが小さく管理できる。

デメリット

  • 規模が大きくなりすぎると、module間の依存関係が複雑になり、コードの可読性が下がる可能性がある。

ディレクトリ構成

moduleディレクトリと環境を管理するディレクトリを作成。

サンプルディレクトリ構成図

./
├── .gitignore
├── README.md
└── terraform/ # terraformディレクトリ
    ├── modules # 呼び出される側のディレクトリ
    │   ├── cloud_storage # cloud_storageを構築するディレクトリ
    │   └── storage_bucket_object # storage_bucket_objectを構築するディレクトリ
    ├── staging # staging環境を構築するディレクトリ
    └── production # production環境を構築するディレクトリ

stagingやproductionで使用するresourceは、modulesブロックで定義しているresourceを呼び出す。

ファイル構成

環境側のファイル構成

  • .terraform-version : terraformのバージョンだけを定義。
  • backend.tf : tfstateの管理を定義。
  • main.tf : moduleを呼び出すファイル。
    • main.tfが大きくなりすぎた場合、以下のようにファイルを複数作成してもよいルールに制定。
      • network.tf
      • storage.tf
      • compute.tf
  • locals.tf : localブロックを定義。
  • providers.tf : providerブロックを定義。
  • terraform.tf : required_versionなどterraformブロックを定義。
  • variables.tf : variableブロックを定義。
  • README.md : ディレクトリ内の概要を説明。

module側のファイル構成

  • main.tf : resourceブロックをdataブロックを定義。
  • variables.tf : variableブロックを定義。
  • outputs.tf : outputブロックを定義。
  • README.md : moduleディレクトリ内の概要を説明。

サンプルディレクトリ構成図

./
├── .gitignore
├── README.md
└── terraform/
    ├── modules/
    │   ├── cloud_storage/ # cloud_storageを構築するディレクトリ
    │   │   ├── main.tf # resourceブロックとdataブロックを定義. 
    │   │   ├── README.md # ディレクトリ内の概要を説明。
    │   │   ├── outputs.tf # outputブロックを定義。
    │   │   └── variables.tf # variableブロックを定義.
    │   └── storage_bucket_object/ # storage_bucket_objectを構築するディレクトリ
    │       ├── main.tf # resourceブロックとdataブロックを定義. 
    │       ├── README.md # ディレクトリ内の概要を説明。
    │       ├── outputs.tf # outputブロックを定義。
    │       └── variables.tf # variableブロックを定義.
    ├── staging/  # staging環境を構築するディレクトリ
    │   ├── .terraform-version # terraformのバージョンだけ記入
    │   ├── README.md # ディレクトリ内の概要を説明
    │   ├── backend.tf # tfstateの管理を定義
    │   ├── locals.tf # localブロックを定義
    │   ├── main.tf # リソースブロック・dataブロックを定義。
    │   ├── providers.tf # providerブロックを定義
    │   ├── terraform.tf # terraformブロックを定義
    │   └── variables.tf # variableブロックを定義
    └── production/ # production環境を構築するディレクトリ
        ├── .terraform-version # terraformのバージョンだけ記入
        ├── README.md # ディレクトリ内の概要を説明
        ├── backend.tf # tfstateの管理を定義
        ├── locals.tf # localブロックを定義
        ├── main.tf # リソースブロック・dataブロックを定義。
        ├── providers.tf # providerブロックを定義
        ├── terraform.tf # terraformブロックを定義
        └── variables.tf # variableブロックを定義

2. 環境別ディレクトリ構成

stagingやproductionなど、環境ごとにそれぞれresourceブロックを定義する形式。

メリット

  • 各環境で必要なリソースを直接定義するので、小規模なプロジェクトやシンプルな構成では理解しやすい。

デメリット

  • 環境間で共通する構成がある場合、module化していないので管理の手間が発生する。

ディレクトリ構成

環境ごとにディレクトリを作成。

サンプルディレクトリ構成図

./
├── .gitignore
├── README.md
└── terraform/
    ├── staging/ # staging環境を構築するディレクトリ
    └── production/ # production環境を構築するディレクトリ

stagingやproductionで使用するresourceは、それぞれのディレクトリごとに作成。

ファイル構成

環境ごとに作成したディレクトリごとにファイルをそれぞれ作成。

  • .terraform-version : terraformのバージョンだけを定義。
  • backend.tf : tfstateの管理を定義。
  • main.tf : resourceブロックとdataブロックを定義。
    • main.tfが大きくなりすぎた場合、以下のようにファイルを複数作成してもよいルールに制定。
      • network.tf
      • storage.tf
      • compute.tf
  • locals.tf : localブロックを定義。
  • providers.tf : providerブロックを定義。
  • terraform.tf : required_versionなどterraformブロックを定義。
  • variables.tf : variableブロックを定義。
  • README.md : ディレクトリ内の概要を説明。

サンプルディレクトリ構成図

./
├── .gitignore
├── README.md
└── terraform/
  ├── staging # staging環境を構築するディレクトリ
  │   ├── .terraform-version # terraformのバージョンだけ記入
  │   ├── README.md # ディレクトリ内の概要を説明
  │   ├── backend.tf # tfstateの管理を定義
  │   ├── locals.tf # localブロックを定義
  │   ├── main.tf # リソースブロック・dataブロックを定義。
  │   ├── providers.tf # providerブロックを定義
  │   ├── terraform.tf # terraformブロックを定義
  │   └── variables.tf # variableブロックを定義
  └── production # production環境を構築するディレクトリ 
      ├── .terraform-version # terraformのバージョンだけ記入
      ├── README.md # ディレクトリ内の概要を説明
      ├── backend.tf # tfstateの管理を定義
      ├── locals.tf # localブロックを定義
      ├── main.tf # リソースブロック・dataブロックを定義。
      ├── providers.tf # providerブロックを定義
      ├── terraform.tf # terraformブロックを定義
      └── variables.tf # variableブロックを定義

tfstateファイルの保管方法

  • AWSとGCPを使用する場合:AWS S3またはGCP GCS
  • AWSのみを使用する場合:AWS S3
  • GCPのみを使用する場合:GCP GCS
  • AWSとGCPのどちらも使用しない場合:AWS S3

CIでチェックする内容

  • formatチェック : terraform fmt
    • GithubActionsの例
- run: terraform fmt -check
  working-directory: { ディレクトリ名を指定 }
  • 構文チェック : terraform validate
    • GithubActionsの例
- run: terraform validate
  working-directory: { ディレクトリ名を指定 }
  • Linterチェック : Tflint
    • GithubActionsの例
- run: tflint --recursive
  working-directory: { ディレクトリ名を指定 }
  • セキュリティチェック : Trivy
    • GithubActionsの例
- name: trivyをセットアップしてセキュリティスキャンを実行
  uses: aquasecurity/trivy-action@ハッシュ値を指定
  with:
    scan-type: "config" # trivyのスキャンタイプを指定
    exit-code: "1" # trivyのエラーコードを指定
    scan-ref: { ディレクトリ名を指定 }
    trivyignores: { ディレクトリ名を指定 }/.trivyignore.yml # trivyのスキャンを無視するファイルを指定

議論した内容

今回ベストプラクティス策定するにあたり、議論した点は以下です。

Terraformのディレクトリ構成をmodule呼び出しに限定するべきか。

プロジェクトの規模によってmodule化しない方がわかりやすいケースもあるので限定しなくてよいという結論に至りました。

単一resourceの命名はthismainなどで統一した方がわかりやすいのではないか。

terraform-aws-moduleでは、単一のresourceをthisで表現しています。またTerraformを使用するためのベストプラクティス|Google Cloudでは単一のリソースをmainで表現するように記載があります。ですのでこのような議題がありました。
ですが公式のStyleGuideでは「resource名はresourceを説明する命名にした方がよい」と記載がありましたので、単一のresourceの場合でも命名をthismainなどで統一しない方針に決まりました。

まとめ

本記事では、チームで策定したTerraformのベストプラクティスを紹介しました。コーディング規約・ディレクトリ構成・tfstateファイルの保管方法など、チームで実施している取り組みを紹介しました。参考になる部分がありましたら嬉しいです。

参考文献