Tagbangers Blog

Terraformで管理するリソースを手動で変更してしまった時の対処(terraform import)

本日は、Terraformを使っていて、tfstateと実際のリソースに差分が生じてしまった際の修正方法を紹介します。

稼働中で作り直しができないリソースなので、tfstateに手動で変更した状態を取り込むことにしました。

起きたことと、解決までの流れは以下のようになります。

  • terraform plan を実行すると予期せぬ差分(.tf に記述していない)が発生
  • Manegement Consoleと .tf ファイルを比較し、terraformで管理するリソースに手動で変更が加わっていることを特定
  • Management Console側のリソースを、 terraform import で tfstate に反映させる
  • tfstateに合わせて、.tfファイルの記述を修正
  • terraform plan を実行し、Management Console と差分がなくなったことを確認

tfstateとは

terraformで管理する対象のリソースの情報をjson形式で記載したファイルです。

terraformが自動生成するもので、ここに記載のあるリソースのみterraformの管理下にあります。

最重要ファイルなので、管理場所や1つのファイルで扱うリソースの粒度が難しいポイントです。

terraform cloudを使用するとtfstateの管理場所を意識しなくてよくなったりしますが、この辺はまたの機会に〜

問題発生

で、ちょうどtfstateの管理方法を修正していた最中の出来事。

.tf は書き換えていなかったのですが、add,changed,destroy...

ただ、planでdry-runが実行されてわかりやすく差分が表示されるからterraformはいいですね〜

$ terraform plan 
 # aws_instance.sample must be replaced
-/+ resource "aws_instance" "sample" {
      + ebs_block_device { # forces replacement
          + delete_on_termination = true
          + device_name           = "xvdf"
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = 20
          + volume_type           = "gp2"
        }
        
        + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)
        }
      ~ metadata_options {
          ~ http_endpoint               = "enabled" -> (known after apply)
          ~ http_put_response_hop_limit = 1 -> (known after apply)
          ~ http_tokens                 = "optional" -> (known after apply)
        }
      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)
        }
      ~ root_block_device {
            delete_on_termination = true
          ~ device_name           = "/dev/sda1" -> (known after apply)
          ~ encrypted             = false -> (known after apply)
          ~ iops                  = 300 -> (known after apply)
          + kms_key_id            = (known after apply)
          ~ volume_id             = "vol-0f00a78377c440cbc" -> (known after apply)
          ~ volume_size           = 100 -> 30
            volume_type           = "gp2"
        }
Plan: 1 to add, 1 to change, 1 to destroy.

.tf には以下のように、aws_instance の記述されていました。

ebsの追加でのアタッチと、rootブロックデバイスの容量の変更が行われていたようです。

resource "aws_instance" "sample" {
  ami                    = local.ami
  instance_type          = local.instance_type
  key_name               = local.key_name
  vpc_security_group_ids = local.vpc_security_group_ids
  subnet_id              = local.subnet_id
  private_ip             = local.private_ip
  tags = {
    Name = local.tag_name
    backup = local.tag_backup
  }
  root_block_device {
    volume_type = local.root_volume_type
    volume_size = local.root_volume_size
  }
  ebs_block_device {
    device_name = local.ebs_device_name
    volume_type = local.ebs_volume_type
    volume_size = local.ebs_volume_size
  }  
}


変更が加わったリソースをTerraform管理下におく

現在のリソースの状態をimportするために .tf に以下のように空のリソースを記述します。参考(import)

resource "aws_instance" "sample_new" {
}

このリソース名を使って、手動で変更したリソースの現在の状態を tfstate にインポートします。参考(aws_instance)

$ terraform import aws_instance.sample_new i-023456789abcdefgh


tfstate をs3で管理しているので、ローカルにコピーして vscode で確認してみます。

$ terraform state pull >> /tmp/terraform.state_import_sample
$ code /tmp/terraform.state_import_sample


インポートできていました。

    {
      "mode": "managed",
      "type": "aws_instance",
      "name": "sample_new",
      "provider": "provider.aws",
      "instances": [
        {
          "schema_version": 1,
          "attributes": {
            "ami": "ami-014192b9d69d36b87",
            "arn": "arn:aws:ec2:ap-northeast-1:421754326399:instance/i-023456789abcdefgh",
            "associate_public_ip_address": true,
            "availability_zone": "ap-northeast-1a",
            "cpu_core_count": 1,
            "cpu_threads_per_core": 2,
            "credit_specification": [],
            "disable_api_termination": false,
            "ebs_block_device": [],
            "ebs_optimized": false,
            "ephemeral_block_device": [],
            "get_password_data": false,
            "hibernation": false,
            "host_id": null,
            "iam_instance_profile": "",
            "instance_state": "running",
            "instance_type": "c3.large",
            "ipv6_address_count": 0,
            "ipv6_addresses": [],
            "private_ip": "172.17.60.15",
            "root_block_device": [
              {
                "delete_on_termination": true,
                "device_name": "/dev/sda1",
                "encrypted": false,
                "iops": 300,
                "kms_key_id": "",
                "volume_id": "vol-0f00a78377abcdefgh",
                "volume_size": 100,
                "volume_type": "gp2"
              }
            ],


差分を埋めていく

あとは .tf を tfstate に合わせる形で編集して、planで差分を確認していきます。

resource "aws_instance" "sample_new" {
  ami                    = local.ami
  instance_type          = local.instance_type
  key_name               = local.key_name
  vpc_security_group_ids = local.vpc_security_group_ids
  subnet_id              = local.subnet_id
  private_ip             = local.private_ip
  tags = {
    Name = local.tag_name
    backup = local.tag_backup
  }
  root_block_device {
    volume_type = local.root_volume_type
    volume_size = local.root_volume_size
  }
}
$  terraform plan
Plan: 0 to add, 0 to change, 1 to destroy.

現在のリソースを .tf に記述できたので(sample_new)、以前の記述(sample)をtfstateから削除します。

$ terraform state rm aws_instance.sample

これで手動で変更してしまったリソースの状態を取り込んで、terraformでの管理下に戻すことができました!

$  terraform plan
No changes. Infrastructure is up-to-date.

慣れない作業のため慎重に、新たなリソース(sample_new)を定義して作業を行いましたが、

  • sampleをtfstateから削除(terraform state rm aws_instance.sample)
  • インポートし直す(terraform import aws_instance.sample i-023456789abcdefgh)
  • .tf を修正

という手順だとよりスマートかと思います。