tfstateを分割したい

Terraformはリソース数が多くなってくるとplanやapplyの実行時間が長くなってくる。
planやapplyの実行時間が長くなると手元での確認やCI/CDに時間がかかるようになり、開発効率や開発体験が悪くなる。
そのためtfstateを分割してそれぞれのtfstateを小さくし、planやapplyの実行時間を短くしたい。

terraform_remote_state

terraform_remote_stateは他のtfstateのoutputの値をdata resourceとして参照する機能。 この機能を使ってtfstateを分割できる。

tfstateを分割する

元のコード

簡単な例として以下のコードを考える。

コードの構成は以下の通り。

.
└── original
    ├── main.tf
    └── provider.tf
data "aws_caller_identity" "current" {}

resource "aws_s3_bucket" "example" {
  bucket = "example-bucket-${data.aws_caller_identity.current.account_id}"
}

resource "aws_s3_object" "object" {
  bucket = aws_s3_bucket.example.bucket
  key    = "test.txt"
  source = "test.txt"
  etag   = file("test.txt")
}

S3バケットとその中にオブジェクトを置いている。

providerは以下のように設定している。

terraform {
  required_version = ">= 0.12"

  backend "s3" {
    bucket = "terraform-example-bucket"
    key    = "terraform/original.tfstate"
    region = "ap-northeast-1"
  }
}

applyするとS3バケットとオブジェクトが作成され、terraform-example-bucketバケットのterraform/original.tfstateにtfstateが保存される。
このコードからS3のオブジェクトを別のtfstateに分割する。

outputブロックを追加する

S3バケットリソースを外部から参照できるようにoutputブロックを追加する。

コードはoriginalディレクトリ以下にoutput.tfを作成して以下のように記述する。

output "example_bucket" {
  value = aws_s3_bucket.example.bucket
}
.
└── original
    ├── main.tf
    ├── output.tf // 追加
    └── provider.tf

分割先のコードを追加する

新しいディレクトリを作成し、移動先のコードを追加する。

.
├── original
│   ├── main.tf
│   ├── output.tf
│   └── provider.tf
└── sub
    └── provider.tf

subディレクトリ以下にprovider.tfを作成し、以下のように記述する。

terraform {
  required_version = ">= 0.12"

  backend "s3" {
    bucket = "terraform-example-bucket"
    key    = "terraform/sub.tfstate"
    region = "ap-northeast-1"
  }
}

terraform_remote_state で参照する

subディレクトリ以下にmain.tfを作成し、originalのtfstateのoutputを参照する。


```text
.
├── original
   ├── main.tf
   ├── output.tf
   └── provider.tf
└── sub
    ├── main.tf // 追加
    └── provider.tf
data "terraform_remote_state" "original" {
  backend = "s3"

  config = {
    bucket = "terraform-example-bucket"
    key    = "terraform/original.tfstate"
    region = "ap-northeast-1"
  }
}

tfstateがS3に保存されている場合はbackend"s3"を指定する。

terraform_remote_stateconfigの値は以下の通り。

  • bucket: 参照するtfstateが保存されているS3バケット
  • key: 参照するtfstateのパス
  • region: S3バケットのリージョン

apply後にstateを確認するとdata resourceとしてS3バケットの情報が取得できる。

$ terraform state show data.terraform_remote_state.original
# data.terraform_remote_state.original:
data "terraform_remote_state" "original" {
    backend = "s3"
    config  = {
        bucket = "terraform-example-bucket"
        key    = "terraform/original.tfstate"
        region = "ap-northeast-1"
    }
    outputs = {
        example_bucket = "example-bucket-XXXXXXXXXXXX"
    }
}

参考: tfstateをローカルに保存している場合の terraform_remote_state の設定

tfstateをローカルに保存している場合はbackend"local"を指定する。

data "terraform_remote_state" "original" {
  backend = "local"

  config = {
    path = "path/to/tfstate"
  }
}

terraform_remote_stateで参照した値を使ってリソースを移動する

terraform_remote_stateで参照した値を使ってリソースを移動する。

まずsubディレクトリ以下のmain.tfにoriginal/main.tfのaws_s3_objectリソースをコピーする。

resource "aws_s3_object" "object" {
  # bucket = aws_s3_bucket.example.bucket
  bucket = data.terraform_remote_state.original.outputs.example_bucket
  key    = "test.txt"
  source = "test.txt"
  etag   = file("test.txt")
}

ただしaws_s3_bucket.example.bucketだった部分をterraform_remote_state dataブロックで参照するように変更する。

オブジェクト自体はすでに存在するのでimportブロックを追加してリソースをインポートする。

import {
  id = "example-bucket-XXXXXXXXXXXX/test.txt"
  to = aws_s3_object.object
}

元のコードからリソースを削除する

importブロックを追加する

実リソースが削除されてしまわないようにremovedブロックでtfstateからのみ削除されるようにする。

removed {
  from = aws_s3_object.object
  lifecycle {
    destroy = false
  }
}

destroy = falseを指定してリソース自体は削除されないようにしている。

terraform planコマンドで実際にリソースが削除されないかを必ず確認しておく。

 # aws_s3_object.object will no longer be managed by Terraform, but will not be destroyed

リソースを削除する

移動したリソースが正しく動作するか確認したら元のコードからリソースを削除する。

  resource "aws_s3_bucket" "example" {
    bucket = "example-bucket-${data.aws_caller_identity.current.account_id}"
  }
  
- resource "aws_s3_object" "object" {
-   bucket = aws_s3_bucket.example.bucket
-   key    = "test.txt"
-   source = "test.txt"
-   etag   = file("test.txt")
- }

removedブロックを追加したので、applyするとtfstateのみから削除される。