moved ブロック — stateのアドレス変更をリファクタリングに活用

1. 概要

  • movedブロックとは何か、なぜ必要か
  • 基本構文と使い方
  • リソース名変更・モジュール移動への対応
  • for_each/countリソースの移動
  • 実行後のクリーンアップ

Terraformのコードをリファクタリングするとき、リソースのアドレス(名前)を変えると、Terraformはそれを「旧リソースの削除 + 新リソースの作成」と判断します。movedブロックを使うと、stateファイル内のアドレスを変更を宣言的に指示でき、実リソースを削除・再作成せずに名前を変更できます。


2. movedブロックが必要な場面

コードをリファクタリングする際、以下のような変更が必要になることがあります。

変更の種類moved なしmoved あり
リソース名変更削除 → 再作成stateのみ更新、インフラそのまま
モジュールへの移動削除 → 再作成stateのみ更新、インフラそのまま
countfor_each 切り替え削除 → 再作成stateのみ更新、インフラそのまま

3. 基本構文

moved {
  from = <旧アドレス>
  to   = <新アドレス>
}

fromtoはリソースのアドレスを指定します。


4. リソース名を変更する

最もシンプルな使い方です。aws_instance.webaws_instance.app_serverに改名します。

変更前

terraform {
  required_version = ">= 1.9"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.0"
    }
  }
}

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

variable "environment" {
  description = "環境名"
  type        = string
  default     = "dev"
}

data "aws_ami" "amazon_linux_2023" {
  most_recent = true
  owners      = ["amazon"]
  filter {
    name   = "name"
    values = ["al2023-ami-*-x86_64"]
  }
}

resource "aws_instance" "web" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name        = "${var.environment}-web"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

変更後(リファクタリング)

# リソース名をwebからapp_serverに変更
resource "aws_instance" "app_server" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name        = "${var.environment}-app"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}

# movedブロックで旧→新アドレスを宣言
moved {
  from = aws_instance.web
  to   = aws_instance.app_server
}

terraform planを実行すると、aws_instance.webを削除してaws_instance.app_serverを作成するのではなく、stateのアドレスを更新するだけの計画が表示されます。

# aws_instance.web has moved to aws_instance.app_server
resource "aws_instance" "app_server" {
    id = "i-0abc1234def56789"
    # (全属性が変更なし)
}

Plan: 0 to add, 0 to change, 0 to destroy.

5. リソースをモジュールに移動する

既存のルートモジュールのリソースを、新しく作成したモジュールに移動する場合です。

# modules/compute/main.tf
resource "aws_instance" "app_server" {
  ami           = var.ami_id
  instance_type = var.instance_type

  root_block_device {
    volume_size = var.volume_size
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name        = "${var.environment}-app"
    Environment = var.environment
    ManagedBy   = "terraform"
  }
}
# main.tf(ルートモジュール)
module "compute" {
  source        = "./modules/compute"
  ami_id        = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"
  volume_size   = 20
  environment   = var.environment
}

# ルートのリソースがモジュール内に移動したことを宣言
moved {
  from = aws_instance.app_server
  to   = module.compute.aws_instance.app_server
}

6. count → for_each への切り替え

countで作成したリソースをfor_eachに変換する場合、インデックス([0])からキー(["dev"])への移動をmovedで宣言します。

# 変更前: count で1つのインスタンスを作成
# resource "aws_instance" "web" {
#   count = 1
#   ...
# }

# 変更後: for_each に切り替え
resource "aws_instance" "web" {
  for_each = toset(["dev"])

  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"

  root_block_device {
    volume_size = 20
    volume_type = "gp3"
    encrypted   = true
  }

  tags = {
    Name        = "${each.key}-web"
    Environment = each.key
    ManagedBy   = "terraform"
  }
}

# count[0] → for_each["dev"] のアドレス変換を宣言
moved {
  from = aws_instance.web[0]
  to   = aws_instance.web["dev"]
}

7. 実行後のクリーンアップ

movedブロックは一度applyが完了した後も削除してかまいません。ただし、削除するタイミングには注意が必要です。

terraform apply が完了したら:
1. movedブロックを削除する(またはコメントアウト)
2. 次のterraform planで差分が出ないことを確認

注意: movedブロックを削除する前にapplyせずにいると、次回のapplyでブロックが再度処理されますが、stateが既に更新済みなので問題なく動作します。


8. 関連記事


9. まとめ

  • movedブロックはTerraform 1.1以降で利用可能
  • リソース名変更・モジュール移動・count→for_each切り替えを削除再作成なしで実現
  • fromに旧アドレス、toに新アドレスを指定するだけ
  • terraform planで「moved」の計画が表示され、applyでstateのみ更新される
  • apply後はmovedブロックを削除してよい

動作確認バージョン: Terraform >= 1.1 公式ドキュメント: https://developer.hashicorp.com/terraform/language/modules/develop/refactoring