movedブロックを使ってリファクタリングしてみた
こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。
今回は、 moved
ブロックを使用してリソースのリファクタリングをやってみようと思います。
movedブロックとは
Terraform バージョン 1.1で現れたリファクタリングを補助してくれるブロックです。
使い方イメージとしては、「もしもリソースAがリソースBという名前だったら...」のような雰囲気で、リソースのリファクタリングを補助してくれるブロックとなっています。
今までどうやっていたの?
今までのリソースのリファクタリングでは、terraform state
コマンドを駆使して、リファクタリングを行っていました。
たとえば以下のtfファイルがあり、リファクタリングを +/-
に従い行いたかったとします。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.11.0"
}
}
required_version = ">= 1.1.0"
}
provider "aws" {
region = "ap-northeast-1"
}
+ resource "aws_vpc" "default" {
- resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "default" {
vpc_id = aws_vpc.example.id
cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 8, 0)
}
aws_vpc.example
からaws_vpc.default
に変更する際は、以下の手順でリファクタリングを行います。
- コードの書き換え
terraform state mv
の実行terraform plan
で差分確認
コードの書き換え
aws_vpc
のローカル名の変更および、aws_subnet.default
のVPC参照先を変更します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.11.0"
}
}
required_version = ">= 1.1.0"
}
provider "aws" {
region = "ap-northeast-1"
}
+ resource "aws_vpc" "default" {
- resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "default" {
+ vpc_id = aws_vpc.default.id
- vpc_id = aws_vpc.example.id
+ cidr_block = cidrsubnet(aws_vpc.default.cidr_block, 8, 0)
- cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 8, 0)
}
terraform state mvの実行
aws_vpc
のローカル名を変更するため、terraform state mv
を実行してきます。
takakuni@moved_blocks % terraform state mv aws_vpc.example aws_vpc.default
Move "aws_vpc.example" to "aws_vpc.default"
Successfully moved 1 object(s).
terraform planで差分確認
ローカル名の変更前後で差分が出ていないかterraform plan
で確認します。
takakuni@moved_blocks % terraform plan
aws_vpc.default: Refreshing state... [id=vpc-XXXXXXXXXXXXXXXXX]
aws_subnet.default: Refreshing state... [id=subnet-XXXXXXXXXXXXXXXXX]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
movedブロック使ってみた
moved
ブロックを使用して、aws_vpc.default
からaws_vpc.example
に戻してみようと思います。
moved
ブロックでは、以下の手順でリファクタリングを行います。
- コードの書き換え
terraform plan
で差分確認terraform apply
でリファクタリングterraform plan
で差分確認
コードの書き換え
aws_vpc
のローカル名の変更および、aws_subnet.default
のVPC参照先を再度変更します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.11.0"
}
}
required_version = ">= 1.1.0"
}
provider "aws" {
region = "ap-northeast-1"
}
+ resource "aws_vpc" "example" {
- resource "aws_vpc" "default" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true
enable_dns_support = true
}
resource "aws_subnet" "default" {
+ vpc_id = aws_vpc.example.id
- vpc_id = aws_vpc.default.id
+ cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 8, 0)
- cidr_block = cidrsubnet(aws_vpc.default.cidr_block, 8, 0)
}
moved {
from = aws_vpc.default
to = aws_vpc.example
}
terraform planで差分確認
terraform plan
で差分を確認します。
moved
ブロックを使用した場合、aws_vpc.default
に対してリネームが行われることを検知してくれます。
takakuni@moved_blocks % aws-vault exec takakuni -- terraform plan
aws_vpc.example: Refreshing state... [id=vpc-XXXXXXXXXXXXXXXXX]
aws_subnet.default: Refreshing state... [id=subnet-XXXXXXXXXXXXXXXXX]
Terraform will perform the following actions:
# aws_vpc.default has moved to aws_vpc.example
resource "aws_vpc" "example" {
id = "vpc-XXXXXXXXXXXXXXXXX"
tags = {}
# (16 unchanged attributes hidden)
}
Plan: 0 to add, 0 to change, 0 to destroy.
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now.
terraform applyでリファクタリング
terraform plan
の状態では、実際のリネームは行われていないためterraform apply
で実行します。
takakuni@moved_blocks % terraform apply
aws_vpc.example: Refreshing state... [id=vpc-XXXXXXXXXXXXXXXXX]
aws_subnet.default: Refreshing state... [id=subnet-XXXXXXXXXXXXXXXXX]
Terraform will perform the following actions:
# aws_vpc.default has moved to aws_vpc.example
resource "aws_vpc" "example" {
id = "vpc-XXXXXXXXXXXXXXXXX"
tags = {}
# (16 unchanged attributes hidden)
}
Plan: 0 to add, 0 to change, 0 to destroy.
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value: yes
Apply complete! Resources: 0 added, 0 changed, 0 destroyed.
terraform planで差分確認
terraform plan
で再度、リファクタリングが影響で差分が発生していないことを確認します。
takakuni@moved_blocks % terraform plan
aws_vpc.example: Refreshing state... [id=vpc-XXXXXXXXXXXXXXXXX]
aws_subnet.default: Refreshing state... [id=subnet-XXXXXXXXXXXXXXXXX]
No changes. Your infrastructure matches the configuration.
Terraform has compared your real infrastructure against your configuration and found no differences, so no changes are needed.
terraform state mvよりも優れている点
弊社、かずえさんのお言葉をお借りすると以下のメリットがmoved
ブロックに存在します。
例えば自分が公開モジュールを作っていて、自分以外の人がそのモジュールを利用しているような状況だったらどうでしょうか?リファクタリングした後に利用者全員に「terraform state mvを使ってね」と伝えるのは無理があります。他のケースだと、Terraformの実行は基本的にCI/CDパイプラインで自動化していて、terraform state mvコマンドを気軽に実行できないとか、シングルテナント構成で大量に同じ構成のStateがあるのですべてでterraform state mvコマンドを打つのは煩雑だ、みたいなケースが考えられます。
加えて、私は「tfstateファイルに対しての変更は、terraform apply
時である」ことが優れていると感じました。
tfstateファイルの変更がapply後であること
terraform state mv
コマンド実行した場合に気になることは、コマンド実行時にtfstateファイルが書き変わってしまうことです。
そのため、リファクタリング前に影響範囲(参照先)を入念に調べ、コマンド実行後にリファクタリングの完遂または、エラー時は戻し作業の完遂が求められます。
個人的には、かなり緊張感のある作業のため、なるべくやりたくない作業の1つです。
しかし、moved
ブロックを使うことで、tfstateファイルの更新をかける前に、十分な検証が行えるためより安全なリファクタリングが見込めます。
terraform state mvよりも気をつけたいポイント
moved
ブロックにも気をつけたいポイントは以下の通りです。
moved
ブロックは削除非推奨- リファクタするリソースの近くで定義しよう
movedブロックは削除非推奨
moved
ブロックは、tfstateファイルに保存されないブロックです。そのため、削除してもterraform plan/apply
で変更検知ができないブロックであることに注意してください。
moved
ブロックを削除すると、古いアドレスを参照している部分が、リネームではなく既存のオブジェクトの削除を計画するため破壊的な変更になります。
リファクタするリソースの近くで定義しよう
実は、以下のように複数moved
を定義することも可能です。
参照可能な値は最終地点の、aws_vpc.example_vpc
のみとなります。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.11.0"
}
}
required_version = ">= 1.1.0"
}
provider "aws" {
region = "ap-northeast-1"
}
+ resource "aws_vpc" "example_vpc" {
- # resource "aws_vpc" "default" {
- # resource "aws_vpc" "example" {
cidr_block = "10.0.0.0/16"
instance_tenancy = "default"
enable_dns_hostnames = true
enable_dns_support = true
}
moved {
from = aws_vpc.default
to = aws_vpc.example
}
moved {
from = aws_vpc.example
to = aws_vpc.example_vpc
}
resource "aws_subnet" "default" {
+ vpc_id = aws_vpc.example_vpc.id
- vpc_id = aws_vpc.default.id
- vpc_id = aws_vpc.example.id
+ cidr_block = cidrsubnet(aws_vpc.example_vpc.cidr_block, 8, 0)
- cidr_block = cidrsubnet(aws_vpc.default.cidr_block, 8, 0)
- cidr_block = cidrsubnet(aws_vpc.example.cidr_block, 8, 0)
}
リソース名の変更履歴を追跡しやすいよう、リファクタするリソースの近くで定義するのがオススメです。
参考
まとめ
以上、「moved
ブロックを使用してリファクタリングをやってみた。」でした。
terraform state mv
コマンドよりも個人的には使い勝手が良くリファクタリングに貢献できる機能だと思いました。
以上、AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!