【Terraform】既存resourceの名前だけを変えたい

社内勉強会で教えてもらった内容。
Terraformで既存のresourceの名前を変えたいとき、普通にtfファイル内でresourceの名前を変更するだけだと、resource全体が作り直し(destory->create)されてしまう。
例えば以前の記事で使った aws_internet_gateway resourceの名前を変更しようとすると、resource全体に差分が生じてしまう。

$ cat aws_internet_gateway.tf 
resource "aws_internet_gateway" "container-era-webapp-internet-gateway" {
  vpc_id = aws_vpc.container-era-webapp-vpc.id
}
$ cat aws_route.tf
resource "aws_route" "container-era-webapp-public-route" {
  route_table_id         = aws_route_table.container-era-webapp-public-route-table.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.container-era-webapp-internet-gateway.id
}

# 名前をcontainer-era-webapp-gatewayに変更
$ cat aws_internet_gateway.tf 
resource "aws_internet_gateway" "container-era-webapp-gateway" {
  vpc_id = aws_vpc.container-era-webapp-vpc.id
}
$ cat aws_route.tf
resource "aws_route" "container-era-webapp-public-route" {
  route_table_id         = aws_route_table.container-era-webapp-public-route-table.id
  destination_cidr_block = "0.0.0.0/0"
  gateway_id             = aws_internet_gateway.container-era-webapp-gateway.id
}

$ terraform plan
Terraform will perform the following actions:

  # aws_internet_gateway.container-era-webapp-gateway will be created
  + resource "aws_internet_gateway" "container-era-webapp-gateway" {
      + arn      = (known after apply)
      + id       = (known after apply)
      + owner_id = (known after apply)
      + vpc_id   = "vpc-01ff3b511d075fb99"
    }

  # aws_internet_gateway.container-era-webapp-internet-gateway will be destroyed
  - resource "aws_internet_gateway" "container-era-webapp-internet-gateway" {
      - arn      = "arn:aws:ec2:ap-northeast-1:xxxxxxxxxxxx:internet-gateway/igw-xxxxxxxxxxxx" -> null
      - id       = "igw-xxxxxxxxxxxxxxx" -> null
      - owner_id = "xxxxxxxxxxxx" -> null
      - tags     = {} -> null
      - vpc_id   = "vpc-xxxxxxxxxxxx" -> null
    }

これが仮に本番環境で使われているリソースである場合、一時的にリソースが消えてしまい本番環境に影響が生じる可能性もある。

このような場合、 terraform state mv コマンドを使い、tfstateファイルを変更する必要がある。
今回はlocalでtfstateファイルの変化を見たいので、まずbackendをS3からlocalに変える。

$ cat config.tf
provider "aws" {
  region = "ap-northeast-1"
}

# terraform {
#   backend "s3" {
#     bucket = "container-era-terraform-udomomo"
#     key    = "sample-app/vpc/terraform.tfstate"
#     region = "ap-northeast-1"
#   }
# }

$ terraform init
...
Do you want to copy this state to the new "local" backend? Enter "yes" to copy and "no" to start with an empty state.

  Enter a value: yes

S3に置かれていたtfstateファイルがlocalにコピーされた。 terraform state mv コマンドを試してみる。

$ terraform state mv aws_internet_gateway.container-era-webapp-internet-gateway aws_internet_gateway.container-era-webapp-gateway
Move "aws_internet_gateway.container-era-webapp-internet-gateway" to "aws_internet_gateway.container-era-webapp-gateway"
Successfully moved 1 object(s).

terraform state mv コマンドを実行すると、自動でバックアップファイルが作られる。差分を見てみると以下のようになっている。

diff -u terraform.tfstate.1595057556.backup terraform.tfstate
--- terraform.tfstate.1595057556.backup    2020-07-18 16:32:36.000000000 +0900
+++ terraform.tfstate   2020-07-18 16:32:36.000000000 +0900
@@ -1,7 +1,7 @@
 {
   "version": 4,
   "terraform_version": "0.12.28",
-  "serial": 0,
+  "serial": 1,
   "lineage": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
   "outputs": {
     "cidr_block": {
@@ -25,7 +25,7 @@
     {
       "mode": "managed",
       "type": "aws_internet_gateway",
-      "name": "container-era-webapp-internet-gateway",
+      "name": "container-era-webapp-gateway",
       "provider": "provider.aws",
       "instances": [
         {
@@ -70,12 +70,7 @@
             "transit_gateway_id": "",
             "vpc_peering_connection_id": ""
           },
-          "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
-          "dependencies": [
-            "aws_internet_gateway.container-era-webapp-internet-gateway",
-            "aws_route_table.container-era-webapp-public-route-table",
-            "aws_vpc.container-era-webapp-vpc"
-          ]
+          "private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
         }
       ]
     },

container-era-webapp-internet-gateway に依存していた aws_route resourceのdependenciesにもdiffがある。これはaws_internet_gatewayのみに変更を加えたためと思われる。公式レポジトリのissueによれば、tfstateファイルはどのような意図でリファクタリングが行われたかは一切関知しておらず、またdependenciesはresourceの削除時にしか使われないため、リファクタリング後に terraform apply すれば影響がなくなるとのことだった。

github.com

terraform plan で確認する。

$ terraform plan

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

resourceに影響がないと確認できたので、backendをS3に戻し、mvされた後のtfstateファイルをS3にコピーする。

$ cat config.tf
provider "aws" {
  region = "ap-northeast-1"
}

terraform {
  backend "s3" {
    bucket = "container-era-terraform-udomomo"
    key    = "sample-app/vpc/terraform.tfstate"
    region = "ap-northeast-1"
  }
}

$ terraform init
...
Do you want to overwrite the state in the new backend with the previous state?
  Enter "yes" to copy and "no" to start with the existing state in the newly
  configured "s3" backend.

  Enter a value: yes
...
Terraform has been successfully initialized!

最後にtfファイルをapplyすればよい。

$ terraform apply
...
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.

ちなみに、apply後にS3バケットに生成されていたtfstateファイルを確認したところ、先程diffが出ていた部分のdependenciesも正しく復活していた。

...
"private": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
    "dependencies": [
        "aws_internet_gateway.container-era-webapp-gateway",
        "aws_route_table.container-era-webapp-public-route-table",
        "aws_vpc.container-era-webapp-vpc"
    ]
...