🕌

[Terraform/AWS] ステートファイルの管理方法(local,s3,terragrunt等)

2024/01/27に公開

本記事を読み終わった時のゴール

  • ステートファイルの概念、必要性を認識出来る事。
  • 状況に応じて適切なbackendの設定が出来る事。
     

目次

  1. ステートファイルの概要
  2. ステートファイルの構文,ロック機能の記述
  3. backend設定のパターン別解説
  4. backend設定の切り替え(例:local⇔S3)
     

本編

1. ステートファイルの説明

ステートファイルとは?

  • Terraform管理下で実際に構築されているリソースのマッピング情報がJSONフォーマットで記述されるファイル。
  • Terraformが内部的に使用するプライベートなAPIとして機能する。
    このファイルのマッピング情報を基にAWS等のプロバイダのAPIを呼び出しリソースの作成、修正、削除等を行う。

何故、必要なのか?

  • 結論としては、Terraformが管理するリソース情報をTerraform自身に識別させるため。

例えば、以下EC2インスタンスのリソースを管理する場合、TerraformはAWSアカウントに実際に存在するインスタンスのID,name,設定値と対応している事をこのJSONフォーマットを通して認識して管理する。
ちなみに、terraform planはこのステートファイルとの差分を検知してリソースの作成,修正,削除等の情報を表示する。
尚、hclが記載された.tfファイルとステートファイルの間に差異が見られない場合は、No Changens. と表示される。

Terraformで構築するリソース

stateファイルの元になるEC2インスタンス
resource "aws_instance" "test_blog" {
  ami = "ami-0dafcef159a1fc745"
  instance_type = "t2.micro"
}
上記で作成したEC2インスタンスのステートファイルの中身
"resources": [
    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "test_blog",
      "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "ami": "ami-0dafcef159a1fc745",
            "arn": "arn:aws:ec2:ap-northeast-1:{アカウントID}:instance/i-{インスタンスID}",
            "associate_public_ip_address": true,
            "availability_zone": "ap-northeast-1a",
      // 以下、省略

 

2. ステートファイルを設定する構文,ロックの説明

ステートファイルを設定する構文

  • リモートバックエンドの主な種類
ステートファイルの保管先
・ローカル環境→local
・AWS→S3 
・GCP→Cloud Storage 
・Azure→Azure Blob Storage 
・Terraform Cloud/Enterprise→remote
  • ステートファイルの保管場所としてのリモートバックエンドにS3バケットを設定する例
config.tf
terraform {
  required_version = "1.6.4"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }

  backend "s3" {
    bucket  = "terraform-state" // バケット名を記載。事前にバケットが作成されている事が前提。
    key     = "test" 
    region  = "ap-northeast-1"
    acl     = "private"
    encrypt = true
    dynamodb_table = "tfstate-locks" 
  }
}

ステートファイルのロック機能

  • 複数人でterraformを扱う際にメンバー同士で同じバージョンのステートファイルに同じタイミングでapplyしないためにロック機能の設定が推奨されます。
    特徴としては以下がる。
    • 強力な整合性
      データが更新された直後にそのデータを読み込むと、最新の状態が反映されていることを保証する機能。

    • 条件付き書き込み
      特定の条件下にある場合のみDBに書き込みを許可する機能。
      ロック状態が既に存在する場合にのみ書き込みを許可し競合を防ぐ。

database.tf
resource "aws_dynamodb_table" "tfstate_lock" {
    name = "tfstate-lock-table"
    billing_mode = "PAY_PER_REQUEST"
    hash_key = "LockID"

    attribute {
    name = "LockID"
    type = "S"
    }   
}
config.tf
// ステートファイルのリモートバックエンドの設定のみ抜粋
backend "s3" {
    bucket  = "backend-common"
    key     = "test"
    region  = "ap-northeast-1"
    acl     = "private"
    encrypt = true
    dynamodb_table = "tfstate-lock-table" 
  }
  • DynamoDBテーブルのLockIDの実際の設定
    画像の例では、ステートファイルの名称がbackend-commonに対して、DynamoDBのtfstate-lock-tablでロック機能を制御しています。

 

3. backend設定のパターン別解説

①ローカル環境で管理する方法

  • 主に個人開発や自己学習で簡易的な環境を作成する際またはストレージが作成されるまでの一時的な用途で設定。
backend "local" {
    path = "tfstate/terraform-state" 
} 

②クラウドサービスのストレージで管理する方法

  • ステートファイルに複数人でアクセスするする必要がある場合の設定。
同一ファイルで定義する場合
config.tf
terraform {
  required_version = "1.6.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }

  backend "s3" {
    bucket  = "backend-common"
    key     = "main"
    region  = "ap-northeast-1"
    acl     = "private"
    encrypt = true
  }
}
s3バケットポリシー
  {
   "Version": "2012–10–17",
     "Statement": [
     {
       "Sid": "RootAccess",
       "Effect": "Allow",
       "Principal": {
       "AWS": "arn:aws:iam::{AWSアカウントID}:root" // 例として、AWSアカウントのrootユーザーへの許可している。
        },
       "Action": "s3:*",
       "Resource": [
         "arn:aws:s3:::backend-common",
         "arn:aws:s3:::backend-common/*"
        ]
      }
    ]
  }
共通設定を外出しする場合
  • 環境毎にbackend設定のパラメータをコピペするのは面倒に感じると思います。
    その場合、以下のように共通設定は別ファイル(.hcl)に切り出し、terraform init -backend-config={パス/切り出したファイル名}で個別のモジュールから設定ファイルを指定する事が可能です。
./backend.hcl
// backendの共通設定を外出しする事で各モジュールで指定しなくて良い。
bucket  = "backend-common"
region  = "ap-northeast-1"
acl     = "private"
encrypt = true
./config.tf
terraform {
  required_version = "1.6.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }

  backend "s3" {
    key     = "main" //key以外の共通情報は外出ししているためここでは記載不要。
  }
}
./stg/config.tf
terraform {
  required_version = "1.6.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }
  
  backend "s3" {
    key     = "stg"
  }
}
./prod/config.tf
terraform {
  required_version = "1.6.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }
  
  backend "s3" {
    key     = "prod"
  }
}
  • 親モジュールのカレントディレクトリに存在する.hclファイルをパス指定する場合のコマンドを入力して読み込む。
terraform init -backend-config=../backend.hcl

③terragruntで管理する方法

Terraform のラッパーで設定を簡素化し、コードの重複を減らす事が可能になる。

メリット
  • DRY原則の実施
    複数の環境(stg,acc,prod等)で共通の Terraform 設定(本記事ではバックエンド設定)を一箇所で管理し、再利用出来る。
  • 環境の分離
    環境ごとに異なるディレクトリ構造を使用し、固有のステートファイルを保持することが出来る。
  • 全てのモジュールに対して一括操作が可能。(個別のterraformコマンドの操作も可能。)
    メインdirから以下コマンドを実行し、サブディレクトリに存在するモジュールを更新できる。
    terragrunt run-all {init/plan/apply/destroy等}
  • リモートバックエンドの自動作成(以下に説明あり)
デメリット
  • 環境を分けずに単一のディレクトリ配下でリソースを管理する場合には向かない。

以下のディレクトリ構成でメインディレクトリ内でterragruntコマンドを実行。

親モジュール
./config.tf
terraform {
  required_version = "1.6.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }
  backend "s3" {}
}

provider "aws" {
  region  = "ap-northeast-1"
  profile = "{任意の認証ユーザ名}"
}
./terragrunt.hcl
remote_state {
    backend = "s3"
    
    config = {
    bucket  = "terragrunt-test-new"
    key     = "${path_relative_to_include()}.tfstate"
    region  = "ap-northeast-1"
    acl     = "private"
    encrypt = true
    }
}

 

子モジュール
./project/dev/terragrunt.hcl
// メインディレクトリで設定しているステートファイルに向けている。
include {
  path = find_in_parent_folders()
}
./project/dev/config.tf
terraform {
  required_version = "1.6.5"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.26.0"
    }
  }

  backend "s3" {}
}

provider "aws" {
  region  = "ap-northeast-1"
  profile = "{任意の認証ユーザ名}"
}
./project/dev/ec2.tf
resource "aws_instance" "test" {
  ami = "ami-0dafcef159a1fc745"
  instance_type = "t2.micro"
}
コマンドの実行
  • 親モジュールで以下コマンドを実行した際に様子。
    親モジュールと子モジュールの双方のinitが実行されている事が分かる。
コマンド
terragrunt run-all init

リモートバックエンド(S3)の自動作成の挙動
  • 以下コマンドを実行するとステートファイル(S3バケット)を作成するか聞かれるため、yを選択し自動で作成する。
実行コマンド
terragrunt run-all init -reconfigure 
// 既存のステートファイルから未作成のS3バケットを指定し直して同時に作成するために実行

  • 実際にbackendになるS3バケット設定されている事をAWS CLIから確認。
    • 旧バケット:terragrunt-test-old
    • 新バケット:terragrunt-test-new

参考

  • 詳解 Terraform 第3版

https://www.oreilly.co.jp/books/9784814400522/

  • Githubリポジトリ
GitHubで編集を提案

Discussion