Eloy Coto
Eloy Coto

Reputation: 73

Terraform destroy --target behaviour

I've just struggle with terraform, and terraform destroy parameters. If I have three nodes and I deleted one of the nodes with terraform destroy --target I have the following:

$ terraform state list
packet_device.jenkins-node[0]
packet_device.jenkins-node[1]
packet_device.jenkins-node[2]
$ terraform destroy --target packet_device.jenkins-node[1]
....
....
....
$ terraform state list
packet_device.jenkins-node[0]
packet_device.jenkins-node[2]

If I decrease the node count to two, terraform destroy jenkins node 2 and create a new one (jenkins node 1). The count is still two, but terraform destroy one server and create a new one. Is there any way to disable this behaviour?

$ terraform 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.

packet_device.jenkins-node.2: Refreshing state... (ID: XXXX)
packet_device.jenkins-node.0: Refreshing state... (ID: XXXX)
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

  + packet_device.jenkins-node[1]
      access_private_ipv4:     "<computed>"
      access_public_ipv4:      "<computed>"
      access_public_ipv6:      "<computed>"
      always_pxe:              "false"
      billing_cycle:           "hourly"
      created:                 "<computed>"
      facility:                "sjc1"
      hardware_reservation_id: "<computed>"
      hostname:                "jenkins-eloy"
      locked:                  "<computed>"
      network.#:               "<computed>"
      operating_system:        "ubuntu_17_10"
      plan:                    "baremetal_0"
      project_id:              "ea13e749-0b1b-4c0d-9701-d0a3df7391f2"
      public_ipv4_subnet_size: "<computed>"
      root_password:           "<sensitive>"
      state:                   "<computed>"
      updated:                 "<computed>"

  - packet_device.jenkins-node[2]


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

If I set terraform plan with three nodes I got the following:

$ export TF_VAR_nodes=3
$ terraform 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.

packet_device.jenkins-node.0: Refreshing state... (ID: 02287fed-c281-4027-8603-bcad6db8b8e6)
packet_device.jenkins-node.2: Refreshing state... (ID: f35fa202-423d-4e02-9d18-1f1bd7f7a3ef)
The Terraform execution plan has been generated and is shown below.
Resources are shown in alphabetical order for quick scanning. Green resources
will be created (or destroyed and then created if an existing resource
exists), yellow resources are being changed in-place, and red resources
will be destroyed. Cyan entries are data sources to be read.

Note: You didn't specify an "-out" parameter to save this plan, so when
"apply" is called, Terraform can't guarantee this is what will execute.

  + packet_device.jenkins-node[1]
      access_private_ipv4:     "<computed>"
      access_public_ipv4:      "<computed>"
      access_public_ipv6:      "<computed>"
      always_pxe:              "false"
      billing_cycle:           "hourly"
      created:                 "<computed>"
      facility:                "sjc1"
      hardware_reservation_id: "<computed>"
      hostname:                "jenkins-eloy"
      locked:                  "<computed>"
      network.#:               "<computed>"
      operating_system:        "ubuntu_17_10"
      plan:                    "baremetal_0"
      project_id:              "ea13e749-0b1b-4c0d-9701-d0a3df7391f2"
      public_ipv4_subnet_size: "<computed>"
      root_password:           "<sensitive>"
      state:                   "<computed>"
      updated:                 "<computed>"


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

Any idea?

Upvotes: 6

Views: 4987

Answers (2)

GeorgeJ
GeorgeJ

Reputation: 186

the only safe way to update your infrastructure is to edit your files, plan, and apply. All other methods listed up there are only meant to be used in non-normal circumstances. And in time will cause your state to be shifted from the actual infrastructure you are running and paying for.


It looks like your terraform infrastructure contains a count somewhere.

Like this

module "packet_device" "jenkins_node" {
    count = 3
    ...other configs using ${count.index}
}

If it does, there is a limitation when using count that is you cant just increase and decrease the count to scale up and down.

Since you are using count, terraform stores the state of each module in an index format. [0: {module1},1: {module2},2: {module3}] and when you reduce the count to 2 or remove a single instance, it will cause a shift in the index and terraform will mark it as a change. Because it sees the index as a resource’s identity.

So let's say you removed index 0 you will end up with [0: {module2},1: {module3}]

module2 will have a new index which is 0, and module3 will have a new index which is 1. So terraform will have to destroy and recreate these modules because it does not know what happened.

Unfortunately, you cannot prevent this behavior using the count configuration.


What you need to do is use a map or for_each, that way the resources will be matched by a key, not by an index. So removing or adding other resources (in the same block) will not affect each other.

This is just an example...

module "packet_device" "jenkins_node" {
    for_each = toset(["key1", "key2", "key3"])
    ...other configs and access the value by ${each.value}
}

More information on for_each is available in the documentation.

Upvotes: 1

Jakub Kania
Jakub Kania

Reputation: 16477

There is not. However if you want to destroy packet_device.jenkins-node[1] you should then run

terraform state mv packet_device.jenkins-node.2 packet_device.jenkins-node.1

So node 2 becomes node 1

Upvotes: 5

Related Questions