blog(unstable)

複数環境にデプロイすることを考慮したterraformディレクトリ設計

October 30, 2023

category : 技術

tags :


この記事は以前Qiitaにて公開したものになります。
元記事はこちら : 複数環境にデプロイすることを考慮したterraformディレクトリ設計


tl;dr

backend config, tfvarsを使って同じtfファイルで複数環境にデプロイするとちょっと手間が増えるけどなんやかんや便利

前書き

私の所属するチームでは、新規プロジェクトに積極的にAWS SAM(Cloudformation)、terraformなどのIaCツールを採用しています。 背景としては、

などがあります。 今回の記事では触れませんが、AWS SAMを利用することで、ローカル開発環境の構築を簡略化できる、などの副次的な効果もあります。

terraformについて

terraformは構成管理ツールのひとつです。 単にterraformと呼ばれるとき、

のセットを指すことが多いように思います。 この記事では、単純にterraformと指したときはこの定義を指すものとします。

既存のterraformのディレクトリ戦略

これまで、社内の基本的なterraform管理リポジトリのディレクトリ構造は以下のようなものでした。

- hoge-service
  - develop
    - main.tf
    - terraform.lock.hcl
    - .terraform-version
    - s3.tf
    - iam.tf
    - ...
  - production
    - main.tf
    - terraform.lock.hcl
    - .terraform-version
    - s3.tf
    - iam.tf
    - ...
- common
  - modules
    - basic-roles
    - basic-security-groups
    - ...

サービスディレクトリ - 各ステージディレクトリ - 各種tfファイル、というベーシックな構成です。 この構成には、以下のようなメリットがあります。

メリット

一方、以下のようなデメリットも抱えています

デメリット

シンプルでわかりやすい構成な反面、複数の環境を管理していると

などのいまいちな点があり、運用負荷が高いなと感じていました。

今回採用したterraformのディレクトリ戦略

今回新規で作成するインフラでは、既存の運用負荷の高いポイントを解消するため、以下のような構成を採用しています。 リソース定義はserviceモジュールに閉じ、環境差分の吸収は backend configtfvars を経由して行うことで、ひとつのディレクトリで複数環境へのデプロイを実現しつつ、全体としてはシンプルな構成に留めています。

- hoge-service
  - main.tf
  - variables.tf
  - terraform.lock.hcl
  - .terraform-version
  - service
    - variables.tf
    - s3.tf
    - iam.tf
    - ...
  - backends
    - production.tfbackend
    - develop.tfbackend
  - tfvars
    - production.tfvars
    - develop.tfvars

役割

main.tf

ここでは、プロバイダーの設定やbackendの定義のみ行います。 実際のリソース定義はserviceモジュールで行うため、必要に応じてtfvarsで設定したグローバル変数の受け渡しもここで定義します。

terraform {
  required_version = ">= 1.6.1"

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.21.0"
    }
  }

  backend "s3" {
    # バケット、プロファイルなどはbackends/{stage}.configから自動で取得される
    # Qiita用NOTE: tfstateはS3で管理しています
  }
}

provider "aws" {
  region  = "ap-northeast-1"
}

# service以下のtfファイルを読み込む
module "service" {
  source = "./service"

  stage = var.stage
}

variables.tf

tfvarsで受けとる変数名や、必要に応じて型などを明示的に宣言します。 この内容を満たすようにtfvarsを設定します。

variable "stage" {}

backends

terraform init時に使用する値をここに保存します。 たとえば、aws providerに渡すアカウント情報やリージョン情報、backendに渡すtfstateの保存場所や名前などはここで取り扱います。

bucket  = "your-iac-resources-bucket"
region  = "us-east-1"
key     = "super-iketeru-service-name/terraform.tfstate"
encrypt = true

tfvars

環境差分として与えたい値をここで管理します。 たとえば、リソース名に使う環境情報や各種コンピューティングリソースのインスタンスサイズなどが該当します。

stage = "develop"

service

アプリケーションの動作に必要なリソースは、すべてこのディレクトリに置かれたtfファイルで管理します。 このディレクトリはモジュールの形でmain.tfから呼び出します。

このterraform構成は、「各環境ごとに独立したtfファイルを持たないようにすることで、運用負荷の低減・環境差分の抑止を実現したい」というところから設計しました。 今回、構築対象となったサービスが非常にシンプルだったため、すべての環境でほぼ同じリソースをデプロイする前提でtfファイルを記述できましたが、ある程度複雑な構成になってくると難しいポイントも出てくるだろう、という考えています。

たとえば、この構成を取ると冗長構成や開発環境のみの設定(たとえばアクセス制限にかかわる設定)などについては、countfor_each を使って表現することになります。 環境差分の理由がすべてコードで表現されることになるので、多少なりとも差分の発生理由を追跡しやすくなる一方で、初心者から見ると若干のとっつきにくさが生まれてしまうかもしれない、という懸念もあります。

terraformは環境構築ツールではなく構成管理ツールです。継続運用が前提のツールです。 そんなツールなので、初回構築の段階ではわからなかったが、継続的に運用していくことで見えてくる課題もありそうです。 引き続き運用を続けて、よりよい形を目指したいなと思います。