1. 概要
movedブロックとは何か、なぜ必要か- 基本構文と使い方
- リソース名変更・モジュール移動への対応
for_each/countリソースの移動- 実行後のクリーンアップ
Terraformのコードをリファクタリングするとき、リソースのアドレス(名前)を変えると、Terraformはそれを「旧リソースの削除 + 新リソースの作成」と判断します。movedブロックを使うと、stateファイル内のアドレスを変更を宣言的に指示でき、実リソースを削除・再作成せずに名前を変更できます。
2. movedブロックが必要な場面
コードをリファクタリングする際、以下のような変更が必要になることがあります。
| 変更の種類 | moved なし | moved あり |
|---|---|---|
| リソース名変更 | 削除 → 再作成 | stateのみ更新、インフラそのまま |
| モジュールへの移動 | 削除 → 再作成 | stateのみ更新、インフラそのまま |
count → for_each 切り替え | 削除 → 再作成 | stateのみ更新、インフラそのまま |
3. 基本構文
moved {
from = <旧アドレス>
to = <新アドレス>
}
fromとtoはリソースのアドレスを指定します。
4. リソース名を変更する
最もシンプルな使い方です。aws_instance.webをaws_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. 関連記事
- state管理とは — tfstateファイルの役割とリスク
- import ブロック — 既存リソースをTerraform管理下に取り込む
- module の使い方 — モジュール化のベストプラクティス
- count vs for_each — 違いと使い分けの完全ガイド
- terraform state コマンド完全ガイド
- removed ブロック — stateから安全にリソースを外す
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