🦁

Terraform の moved ブロック、removed ブロック、import ブロックを活用しよう

に公開

こんにちは、クラウドエース第三開発部の渡辺です。これまで、Terraform の State 操作は主にコマンドで実行していました。そこで、コード内で State 操作を行う方法を知り、その詳細と使用例を調査しました。
本記事では、Terraform の State 操作における movedimportremoved ブロックを解説します。また、Google Cloud 環境でのハンズオン例を紹介します。

対象読者

  • Terraform 初心者: Terraform の基本を学び、State 管理・操作の基礎を理解したい方
  • コマンドで State 操作経験のある方: 既に Terraform を使用し、State 操作をコマンドで行っているが、コード内での操作方法に興味がある方

Terraform の State について

Terraform は、インフラストラクチャをコードとして管理する際に、State ファイルを使用します。State ファイルは、現在のインフラの状態を追跡します。また、管理する各リソースのメタデータや属性を保持します。これにより、コードと実際のインフラの整合性が保たれます。

State ファイルの重要性

  • リソースの追跡
    State ファイルを用いて、Terraform は現在のインフラと構成ファイルの差分から必要な変更を確認します。これにより、既存リソースや新規作成・更新対象を正確に判断できます。
  • 並行管理の防止
    Terraform のロック機能(特にリモート バックエンドを使用時)により、複数のユーザーが同じ State ファイルを同時に操作することで競合が発生するのを防ぎます。これにより、予期せぬ上書きや不整合を避けることができます。
  • 依存関係の管理
    リソース間の依存関係を正確に把握し、適切な順序で操作を行います。これにより、予期せぬ上書きや不整合を避けられます。

詳細は、以下の公式ドキュメントをご覧ください。

State 管理の方法

State 管理には、主に 2 つの方法があります。

  1. コマンドベースの管理
    コマンドラインで State ファイルを操作ができます。例えば、既存リソースを Terraform の管理下に取り込む際には terraform import コマンドを使用します。

  2. コード内での管理
    Terraform のアップデートにより、State 管理を Terraform のコード内で定義できるようになりました。具体的には、moved ブロック、import ブロック、removed ブロック、を使用します。これにより、リソースの移動や削除、インポートをコードベースで操作できます。

本記事ではコード内での管理に焦点を当てます。コマンドベースの管理は本記事では扱いません。

リモート バックエンドと State 管理

State ファイルはローカルに保存も可能ですが、チームでの共同作業やセキュリティを考慮し、リモートバックエンドの利用が推奨されます。リモートバックエンドを利用することで、State ファイルの共有やセキュリティの強化が可能です。以下は、Google Cloud Storage をリモートバックエンドとして使用する例です。

terraform {
  backend "gcs" {
    bucket  = "my-terraform-state-bucket"
    prefix  = "terraform/state"
    project = "my-googlecloud-project"
  }
}

詳細は、以下の公式ドキュメントをご覧ください。

State ロック

Terraform では、State ロック機能を利用して State ファイルへの同時アクセスを制御します。これにより、状態の一貫性を保護します。ロック機能はバックエンドがサポートしている場合に自動的に適用され、以下の利点があります。

  • 競合の防止
    State ロックにより、複数のユーザーやプロセスが同時に State ファイルを変更することを防ぎます。これにより、状態ファイルの破損や不整合を防止し、安全にインフラを管理できます。

  • 自動ロック管理
    状態を変更する全ての操作(例:terraform applyterraform plan)において、Terraform は自動的に State ロックを取得します。ロック取得中は他の操作がブロックされ、同時実行による問題を回避できます。

  • ロック失敗時の安全性
    ロック取得に失敗した場合、Terraform は操作を続行せずエラーを返します。これにより、意図しない変更や上書きを防ぎます。ただし、-lockフラグを使用してロックを無効化することも可能ですが、推奨されません。ロックを無効にすると、競合による問題が発生するリスクが高まります。

詳細は、以下の公式ドキュメントをご覧ください。

各ブロックの概要

この章では、movedimportremoved ブロックの概要と使い方を説明します。これらのブロックを使用すると、コード内で State 操作が可能になります。

moved ブロックについて

moved ブロックは、リソースのアドレスを変更する際に使用します。リソースの再作成を避け、旧リソースを新アドレスに移行できます。これは、terraform state mv に相当し、Terraform v1.1 以降で使用可能です。以下は、公式ドキュメントです。

moved ブロックの使い方

moved ブロックは、Terraform の設定ファイル内で以下のように定義します。fromto の 2 つの引数を使用して、リソースの旧アドレスから新アドレスへの移動を指定します。

moved {
  from = <旧リソースアドレス>
  to   = <新リソースアドレス>
}
  • from: 移動前のリソースアドレスを指定
  • to: 移動後のリソースアドレスを指定

import ブロックについて

import ブロックは、Terraform 管理外のリソースを State ファイルへ取り込み、以後 Terraform で管理可能にします。 これは、terraform importに相当し、Terraform v1.5 以降で使用可能です。以下は、公式ドキュメントです。

import ブロックの使い方

import ブロックは、Terraform の設定ファイル内で以下のように定義します。to引数でインポート先のリソースアドレスを指定し、id 引数でインポートするリソースの固有 ID を指定します。

import {
  to = <インポート先のリソースアドレス>
  id = <インポートするリソースID>
}
  • to: インポート先のリソースアドレスを指定
  • id: インポートするリソース固有の ID を指定

id のフォーマットは各リソースの Terraform の公式ドキュメントを参照してください。
以下は Google Compute Engine インスタンスの import の Terraform 公式ドキュメントです。

removed ブロックについて

removed ブロックは、リソースを Terraform の管理から外す際に使用します。リソース本体を削除せず、State からのみ削除できます。これはterraform state rmに相当し、Terraform v1.7 以降で使用可能です。以下は、公式ドキュメントです。

removed ブロックの使い方

removed ブロックは、Terraform の設定ファイル内で以下のように定義します。
from 引数で管理から外すリソースアドレスを指定し、lifecycle ブロック内の destroy 引数でリソースの削除有無を設定します。

removed {
from = <管理から外すリソースアドレス>

lifecycle {
  destroy = false or true
  }
}
  • from:Terraform の State から削除するリソースアドレスを指定
  • lifecycle:destroy 引数を使用して、リソース自体を削除するかどうかを設定
    • destroy = false: リソースを Terraform の管理下から外しますが、インフラには影響を与えない
    • destroy = true: リソースを Terraform の管理下から外し、同時にインフラからも削除

lifecycle ブロックは必須であり、destroy 引数を指定する必要があります。

各ブロック使用例と動作確認

この章では、movedimportremoved ブロック使用例と動作確認を記載します。
動作確認には、terraform plan および terraform apply 結果を例示します。

moved ブロックの使用例

以下は、Terraform で作成した Google Compute Engine インスタンスのアドレスを新しいアドレスに変更する例です。

変更前のリソース定義
resource "google_compute_instance" "old_instance" {
  name         = "moved-test-instance"
  machine_type = "e2-micro"
  zone         = "asia-northeast1-a"
  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2004-lts"
    }
  }
  network_interface {
    network = "default"
  }

}
> terraform state list
google_compute_instance.old_instance
>
変更後のリソース定義と moved ブロックの追加

resource "google_compute_instance" "new_instance" {
  name         = "moved-test-instance"
  machine_type = "e2-micro"
  zone         = "asia-northeast1-a"
  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2004-lts"
    }
  }
  network_interface {
    network = "default"
  }
}

moved {
  from = "google_compute_instance.old_instance"
  to   = "google_compute_instance.new_instance"
}

moved ブロックの動作確認

moved ブロックを使用した時の動作確認を行います。
以下は、terraform plan および terraform apply の実行結果です。

Plan の実行
❯ terraform plan
google_compute_instance.new_instance: Refreshing state... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]

Terraform will perform the following actions:

  # google_compute_instance.old_instance has moved to google_compute_instance.new_instance
    resource "google_compute_instance" "new_instance" {
        id       = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
        name     = "moved-test-instance"
        tags     = []
        # (22 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

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

>
Apply の実行
> terraform apply

google_compute_instance.new_instance: Refreshing state... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]

Terraform will perform the following actions:

  # google_compute_instance.old_instance has moved to google_compute_instance.new_instance
    resource "google_compute_instance" "new_instance" {
        id       = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
        name     = "moved-test-instance"
        tags     = []
        # (22 unchanged attributes hidden)

        # (4 unchanged blocks hidden)
    }

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

Apply complete! Resources: 0 added, 0 changed, 0 destroyed.


>
> terraform state list
google_compute_instance.new_instance
>

この設定により、Terraform は既存の google_compute_instance.old_instance アドレスを google_compute_instance.new_instance として扱うようになります。これにより、リソースの再作成やダウンタイムを避けつつ、アドレスの変更が可能となります。

import ブロックの使用例

import ブロックの使用例を紹介します。
以下は、コンソールで作成された Google Compute Engine インスタンスを Terraform の管理下に取り込む例です。

コンソールで作成されたリソース

> terraform state list
# 何も表示されず Terraform の管理下にないことを確認
>
変更後のリソース定義と import ブロックの追加
resource "google_compute_instance" "import_instance" {
  name         = "import-test-instance"
  machine_type = "e2-micro"
  zone         = "asia-northeast1-a"
  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2004-lts"
    }
  }
  network_interface {
    network = "default"
  }

}

import {
  to = "google_compute_instance.import_instance"
  id = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
}

import ブロックの動作確認

import ブロックを使用した時の動作確認を行います。
以下は、terraform plan および terraform apply の実行結果です。

Plan の実行
> terraform plan
google_compute_instance.import_instance: Preparing import... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]
google_compute_instance.import_instance: Refreshing state... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # google_compute_instance.import_instance will be updated in-place
  ~ resource "google_compute_instance" "import_instance" {
      id          = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
      name        = "import-test-instance"
      machine_type = "e2-micro"
      zone        = "ZONE"

      network_interface {
        network    = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/default"
        subnetwork = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/regions/REGION/subnetworks/default"
        network_ip = "10.146.0.17"

        - access_config {
            - nat_ip       = "PUBLIC_IP_ADDRESS" -> null
            - network_tier = "PREMIUM" -> null
          }
      }

      # 他の変更点は省略されています
  }

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

>
Apply の実行
> terraform apply

google_compute_instance.import_instance: Preparing import... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]
google_compute_instance.import_instance: Refreshing state... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # google_compute_instance.import_instance will be updated in-place
  ~ resource "google_compute_instance" "import_instance" {
      id           = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
      name         = "import-test-instance"
      machine_type = "e2-micro"
      zone         = "ZONE"

      boot_disk {
          device_name = "import-test-instance"
          source      = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/zones/ZONE/disks/import-test-instance"

          initialize_params {
              image = "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-2004-focal-v20241016"
              size  = 10
              type  = "pd-balanced"
          }
      }

      network_interface {
          network    = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/global/networks/default"
          subnetwork = "https://www.googleapis.com/compute/v1/projects/PROJECT_ID/regions/REGION/subnetworks/default"
          network_ip = "10.146.0.17"

          - access_config {
              - nat_ip       = "PUBLIC_IP_ADDRESS" -> null
              - network_tier = "PREMIUM" -> null
            }
      }

      # 他の変更点は省略されています
  }

Plan: 1 to import, 0 to add, 1 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

google_compute_instance.import_instance: Importing... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]
google_compute_instance.import_instance: Import complete [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]
google_compute_instance.import_instance: Modifying... [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]
google_compute_instance.import_instance: Modifications complete after 2s [id=projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME]

Apply complete! Resources: 1 imported, 0 added, 1 changed, 0 destroyed.

>
> terraform state list
google_compute_instance.import_instance
>

この設定により、Terraform の管理下になかった Google Compute Engine インスタンスをインポートし、Terraform の管理下に取り込みました。以降、この手動で作成されたリソースを Terraform で管理することが可能となります。

removed ブロックの使用例

removedブロックの使用例を紹介します。
以下は、Terraform の管理下にある Google Compute Engine インスタンスのリソースを削除せずに Terraform の管理下からのみ削除する例です。

Terraform 管理下にあるリソース
resource "google_compute_instance" "removed_instance" {
  name         = "removed-test-instance"
  machine_type = "e2-micro"
  zone         = "asia-northeast1-a"
  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-2004-lts"
    }
  }
  network_interface {
    network = "default"
  }

}
> terraform state list
google_compute_instance.removed_instance
>
Terraform 管理下から削除するため removed ブロックの追加
removed {
  from = "google_compute_instance.removed_instance"

  lifecycle {
    destroy = false
  }
}

removed ブロックの動作確認

removed ブロックを使用した時の動作確認を行います。
以下は、terraform plan および terraform apply の実行結果です。

Plan の実行
> terraform plan

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

  # google_compute_instance.removed_instance will no longer be managed by Terraform, but will not be destroyed
  . resource "google_compute_instance" "removed_instance" {
      id   = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
      name = "removed-test-instance"
      # (19 unchanged attributes hidden)

      # (4 unchanged blocks hidden)
  }

Plan: 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - google_compute_instance.removed_instance
│
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.
╵

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

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.

>
Apply の実行
> terraform apply

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:

Terraform will perform the following actions:

  # google_compute_instance.removed_instance will no longer be managed by Terraform, but will not be destroyed
  . resource "google_compute_instance" "removed_instance" {
      id   = "projects/PROJECT_ID/zones/ZONE/instances/INSTANCE_NAME"
      name = "removed-test-instance"
      # (19 unchanged attributes hidden)

      # (4 unchanged blocks hidden)
  }

Plan: 0 to add, 0 to change, 0 to destroy.
╷
│ Warning: Some objects will no longer be managed by Terraform
│
│ If you apply this plan, Terraform will discard its tracking information for the following objects, but it will not delete them:
│  - google_compute_instance.removed_instance
│
│ After applying this plan, Terraform will no longer manage these objects. You will need to import them into Terraform to manage them again.
╵

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 state list
# 何も表示されず Terraform の管理下にないことを確認
>

Terraform の管理下からのみ削除されたことを確認

この設定により、Terraform はリソースを削除せずに、Terraform の State ファイルからリソースを削除することができました。

まとめ

本記事では、Terraform の movedimportremoved ブロックを解説し、それぞれの使用方法や具体的な例を紹介しました。これらを使うと、コード内で柔軟に State 操作が可能となります。特に、コード内での State 管理を行うことで、インフラストラクチャの変更履歴を明確にし、チームでの共同作業を進めることが可能となります。ぜひ、これらの機能を活用して、Terraform によるインフラ管理をより柔軟的に行ってください。

Discussion