継続は力なり

タイトル通り定期的な更新を心掛けるブログです。

Terraform の差分を解消するためにローカルで tfstate を修正する

タダです.

やったことないオペレーションで S3 で管理している Terraform の tfstate をローカルで変更する作業を経験したので備忘録として記事に残しておきます.

前提

そもそもなぜ tfstate をいじることになったのかというと,Terraform のコードと管理している AWS 環境で差分が発生したため,terraform import を行う必要があったからです.S3 に tfstate が入っているんだから直接いじることも可能だと思いますが,万が一誤ったリソースの情報を消してしまったりすると大変なためローカルでの tfstate の変更を行うことにしました.

手動で変更してしまったリソースが terraform plan で差分として出たイメージ

# aws_subnet.hoge_subnet will be updated in-place
  ~ resource "aws_subnet" "hoge_subnet" {
        id                                             = "subnet-123456789101112"
      ~ tags                                           = {
          - "test"  = "hoge" -> null
            # (2 unchanged elements hidden)
        }
      ~ tags_all                                       = {
          - "test"  = "hoge" -> null
            # (2 unchanged elements hidden)
        }
        # (14 unchanged attributes hidden)
    }

行ったオペレーション

1,tfstate をローカルに持ってくる

terraform init 後にまずは手元に tfstate を持ってきます.terraform state pull を使って,アウトプットで別ファイルに書き出します.

❯❯❯ terraform state pull > hoge.tfstate

その後,provider の設定を書いているファイルを編集し,backend "s3" のセクションをコメントアウトし,terraform init で作成された .terraform.lock.hcl.terraform ディレクトリを削除します.

terraform {
  required_version = ">= 1.0.0"

  # backend "s3" {
  #   ~中略~
  # }

  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = ">= 3.0"
    }
  }
}

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

その後,再度 terraform init を実行できたらローカルで tfstate を変更できるようになったので差分を埋めることができます.

❯❯❯ terraform init                                           

Initializing the backend...

Initializing provider plugins...
- Finding hashicorp/aws versions matching ">= 3.0.0"...
- Installing hashicorp/aws v4.5.0...
- Installed hashicorp/aws v4.5.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

2,tfstate を編集する

まずは terraform state showterraform import する対象の状態を確認します.差分として出ている test タグもない状態なので,これをいじって差分を埋めていきます.

❯❯❯ terraform state show -state=hoge.tfstate aws_subnet.hoge_subnet 
# aws_subnet.hoge_subnet:
resource "aws_subnet" "hoge_subnet" {
    arn                                            = "arn:aws:ec2:ap-northeast-1:123456789101112:subnet/subnet-123456789101112"
    assign_ipv6_address_on_creation                = false
    availability_zone                              = "ap-northeast-1a"
    availability_zone_id                           = "apne1-az4"
    cidr_block                                     = "10.0.0.0/24"
    enable_dns64                                   = false
    enable_resource_name_dns_a_record_on_launch    = false
    enable_resource_name_dns_aaaa_record_on_launch = false
    id                                             = "subnet-123456789101112"
    ipv6_native                                    = false
    map_customer_owned_ip_on_launch                = false
    map_public_ip_on_launch                        = true
    owner_id                                       = "123456789101112"
    private_dns_hostname_type_on_launch            = "ip-name"
    tags                                           = {
        "Name"  = "hoge-subnet"
        "Owner" = "tada"
    }
    tags_all                                       = {
        "Name"  = "hoge-subnet"
        "Owner" = "tada"
    }
    vpc_id                                         = "vpc-123456789101112"
}

次に terraform state rm で対象リソースの state を消します.この後,ローカルの tfstate の中から削除したリソースデータがなくなっているはずです.

❯❯❯ terraform state rm -state=hoge.tfstate aws_subnet.hoge_subnet
Removed aws_subnet.hoge_subnet
Successfully removed 1 resource instance(s).

そして, terraform import で対象リソースを取り込んでコード側も差分が出ている tag を追記した後,terraform state showterraform plan の実行計画を見て差分がないことを確認できるはずです.ここまでで tfstate をいじる作業は終わりです.

❯❯❯  terraform import -state=hoge.tfstate aws_subnet.hoge_subnet subnet-123456789101112
aws_subnet.hoge_subnet: Importing from ID "subnet-123456789101112"...
aws_subnet.hoge_subnet: Import prepared!
  Prepared aws_subnet for import
aws_subnet.hoge_subnet: Refreshing state... [id=subnet-123456789101112]

Import successful!

The resources that were imported are shown above. These resources are now in
your Terraform state and will henceforth be managed by Terraform.
terraform state show -state=hoge.tfstate aws_subnet.hoge_subnet 

❯❯❯ terraform state show -state=hoge.tfstate aws_subnet.hoge_subnet 
# aws_subnet.hoge_subnet:
resource "aws_subnet" "hoge_subnet" {
    arn                                            = "arn:aws:ec2:ap-northeast-1:123456789101112:subnet/subnet-123456789101112"
    assign_ipv6_address_on_creation                = false
    availability_zone                              = "ap-northeast-1a"
    availability_zone_id                           = "apne1-az4"
    cidr_block                                     = "10.0.0.0/24"
    enable_dns64                                   = false
    enable_resource_name_dns_a_record_on_launch    = false
    enable_resource_name_dns_aaaa_record_on_launch = false
    id                                             = "subnet-123456789101112"
    ipv6_native                                    = false
    map_customer_owned_ip_on_launch                = false
    map_public_ip_on_launch                        = true
    owner_id                                       = "123456789101112"
    private_dns_hostname_type_on_launch            = "ip-name"
    tags                                           = {
        "Name"  = "hoge-subnet"
        "Owner" = "tada"
        "test"  = "hoge"
    }
    tags_all                                       = {
        "Name"  = "hoge-subnet"
        "Owner" = "tada"
        "test"  = "hoge"
    }
    vpc_id                                         = "vpc-123456789101112"
}
❯❯❯ terraform plan -state=hoge.tfstate

3,リモートの tfstate にローカルの変更を反映する

最後にローカルで変更した tfstate をリモート側に反映します.tfstate をローカルに持ってくるときにコメントアウトした backend "s3" のセクションを戻し, .terraform.lock.hcl.terraform ディレクトリを削除して terraform init します.backend "s3"になっています.

❯❯❯ terraform init  

Initializing the backend...

Successfully configured the backend "s3"! Terraform will automatically
use this backend unless the backend configuration changes.

Initializing provider plugins...
- Finding hashicorp/aws versions matching ">= 3.0.0"...
- Installing hashicorp/aws v4.5.0...
- Installed hashicorp/aws v4.5.0 (signed by HashiCorp)

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

最後に,terraform state pushでリモートの tfstate に反映します.コマンド実行後,リモートの tfstate がコマンド実行時間帯で更新されているはずです.そして,この状態で terraform planで実行計画を確認するとローカルで確認した結果と同じになっていれば目的の作業は完了です.

❯❯❯ terraform state push hoge.tfstate

まとめ

リモートの tfstate をいじらずに安全に反映していくための作業を経験したので,やった作業をまとめました.初めてのオペレーションでわからないことが多い中,下記の参考記事をベースに対応したので大変助かりました...

参考記事

qiita.com