Help us understand the problem. What is going on with this article?

terraformのcountリソースを、再作成なしでfor_eachリソースに変更する

はじめに

terraformの0.12.6から、resourceをfor_eachで作成できるようになった、というものがある。
そこで、自分のリソースで、現在countで管理しているデータもfor_eachで管理してみたくなったわけだが、
手間取ったので、備忘録として書いておく

注意

この操作はtfstateを直接書き換えるものであり、場合によってはterraform管理が壊れてしまう可能性があるため、必ずtfstateのバックアップと、事前検証を行っておくこと

使用するリソース

今回は以下のようなリソースを用意して検証する。

locals {
    resourcemap = [
        {
            name = "a"
        },
        {
            name = "b"
        },
        {
            name = "c"
        }
    ]
}

resource "null_resource" "test" {
    count = length(local.resourcemap)
}

上記リソースを、

locals {
    resourcemap = [
        {
            name = "a"
        },
        {
            name = "b"
        },
        {
            name = "c"
        }
    ]
}

resource "null_resource" "test" {
    for_each = { for x in local.resourcemap: x.name => x }
}

のように、nameをキーにしてマッピングするという想定をする。

普通に移行すると

普通に書き換えた場合、以下のようなplanが出力される。

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

null_resource.test[1]: Refreshing state... [id=153962400052254689]
null_resource.test[2]: Refreshing state... [id=962039978484673663]
null_resource.test: Refreshing state... [id=1112149254847114420]

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  - destroy

Terraform will perform the following actions:

  # null_resource.test will be destroyed
  - resource "null_resource" "test" {
      - id = "1112149254847114420" -> null
    }

  # null_resource.test[1] will be destroyed
  - resource "null_resource" "test" {
      - id = "153962400052254689" -> null
    }

  # null_resource.test[2] will be destroyed
  - resource "null_resource" "test" {
      - id = "962039978484673663" -> null
    }

  # null_resource.test["a"] will be created
  + resource "null_resource" "test" {
      + id = (known after apply)
    }

  # null_resource.test["b"] will be created
  + resource "null_resource" "test" {
      + id = (known after apply)
    }

  # null_resource.test["c"] will be created
  + resource "null_resource" "test" {
      + id = (known after apply)
    }

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

------------------------------------------------------------------------

Note: You didn't specify an "-out" parameter to save this plan, so Terraform
can't guarantee that exactly these actions will be performed if
"terraform apply" is subsequently run.

これを行うと、既存のリソースの再作成が実行される。
今回はnull_resourceを使用しているので問題ないが、実際は仮想マシンインスタンス等が対象となるため、実際のサービスへの大きな影響や、適用中の失敗する可能性が懸念され、かつ実行に時間がかかることも予想される。

リソースの再作成を抑止するには

さて、ではどうすればリソースの再作成を抑止できるだろうかということで、試行錯誤して見つけた方法を以下に書く。
簡単に言うと、

  1. 既存のtfstateを取得
  2. tfstateを編集
  3. 編集したtfstateを適用

というものとなる。
前提として、無用なトラブルを避けるため、countからfor_eachへ切り替える以外の変更は全て排除しておくこと

既存のtfstateの取得

既存のtfstateを取得するには、terraform state pullを使用する。
オプションは無く、常に標準出力に表示されるため、リダイレクトしてファイルに保存する。
なお、形式はjson形式となる。

ただし、エンコーディングはBOM無しUTF-8でなければならないため、windowsでpowershell等使用している場合は要注意

tfstateを編集

取得したtfstateに対して、以下のような修正を加える。

  • /serialの数字を1増やす
  • /resources以下の、eachの値がmapになっているオブジェクトを探す
  • eachの値をmapに変更する
  • instances以下のindex_keyを、想定するマッピングのキーに変更する

例として、

    ...
    "serial": 1
    ...
    {
      ...
      "each": "list",
      "instances": [
        {
          ...
          "index_key": 0,
          ...
        },
        ...
      ]
    }

    ...
    "serial": 2
    ...
    {
      ...
      "each": "map",
      "instances": [
        {
          ...
          "index_key": "a",
          ...
        },
        ...
      ]
    }

のように編集する。

フォーマットは基本的にjsonなので、対象リソースが多くて大変な場合はスクリプト等で回した方が良いかもしれない。

tfstateを適用する

適用はterraform state push [編集されたtfstateファイル]で行う。
この時、json的に文法がおかしかったり、serialを上げていない等の明確な間違いがあった場合は弾かれる。
この時、実際にtfstateが変更されるため、必ずその前に元のtfstateのバックアップを行っておくこと

結果の確認

狙い通りになった場合、terraform planしても、create/destroyが0になるはず。今回用意したリソースでは、以下のようになる。

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

null_resource.test["b"]: Refreshing state... [id=153962400052254689]
null_resource.test["a"]: Refreshing state... [id=1112149254847114420]
null_resource.test["c"]: Refreshing state... [id=962039978484673663]

------------------------------------------------------------------------

No changes. Infrastructure is up-to-date.

This means that Terraform did not detect any differences between your
configuration and real physical resources that exist. As a result, no
actions need to be performed.

終りに

繰り返しになるが、tfstateを直接編集しているので、必ず事前にバックアップを取っておくことを推奨する。
今回はtfstate直接編集という手段を用いたが、その他に安全な方法があるならばぜひ教えてほしい。

参考リンク

Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away