Workshops
Book a 90-minute product workshop led by HashiCorp engineers and product experts during HashiConf Digital Reserve your spot

Get Started - Google Cloud

Change Infrastructure

In the previous page, you created your first infrastructure with Terraform: a VPC network. In this page, we're going to modify your configuration, and see how Terraform handles change.

Infrastructure is continuously evolving, and Terraform was built to help manage and enact that change. As you change Terraform configurations, Terraform builds an execution plan that only modifies what is necessary to reach your desired state.

By using Terraform to change infrastructure, you can version control not only your configurations but also your state so you can see how the infrastructure evolves over time.

»Adding Resources

You can add new resources by adding them to your Terraform configuration and running terraform apply to provision them.

First, add a google compute instance resource to main.tf.

resource "google_compute_instance" "vm_instance" {
  name         = "terraform-instance"
  machine_type = "f1-micro"

  boot_disk {
    initialize_params {
      image = "debian-cloud/debian-9"
    }
  }

  network_interface {
    network = google_compute_network.vpc_network.name
    access_config {
    }
  }
}

This resource includes a few more arguments. The name and machine type are simple strings, but boot_disk and network_interface are more complex blocks. You can see all of the available options in the documentation.

For this example, your compute instance will use a Debian operating system, and will be connected to the VPC Network you created earlier. Notice how this configuration refers to the network's name property with google_compute_network.vpc_network.name -- google_compute_network.vpc_network is the ID, matching the values in the block that defines the network, and name is a property of that resource.

The presence of the access_config block, even without any arguments, ensures that the instance will be accessible over the internet.

Now run terraform apply to create the compute instance.

$ terraform apply
google_compute_network.vpc_network: Refreshing state... [id=terraform-network]

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

Terraform will perform the following actions:

  # google_compute_instance.vm_instance will be created
  + resource "google_compute_instance" "vm_instance" {
      + can_ip_forward       = false
      + cpu_platform         = (known after apply)
      + deletion_protection  = false
      + guest_accelerator    = (known after apply)
      + id                   = (known after apply)
      + instance_id          = (known after apply)
      + label_fingerprint    = (known after apply)
      + machine_type         = "f1-micro"
      + metadata_fingerprint = (known after apply)
      + name                 = "terraform-instance"
      + project              = (known after apply)
      + self_link            = (known after apply)
      + tags_fingerprint     = (known after apply)
      + zone                 = (known after apply)

      + boot_disk {
          + auto_delete                = true
          + device_name                = (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + source                     = (known after apply)

          + initialize_params {
              + image = "debian-cloud/debian-9"
              + size  = (known after apply)
              + type  = (known after apply)
            }
        }

      + network_interface {
          + address            = (known after apply)
          + name               = (known after apply)
          + network            = "https://www.googleapis.com/compute/v1/projects/hc-training-test/global/networks/terraform-network"
          + network_ip         = (known after apply)
          + subnetwork         = (known after apply)
          + subnetwork_project = (known after apply)

          + access_config {
              + assigned_nat_ip = (known after apply)
              + nat_ip          = (known after apply)
              + network_tier    = (known after apply)
            }
        }

      + scheduling {
          + automatic_restart   = (known after apply)
          + on_host_maintenance = (known after apply)
          + preemptible         = (known after apply)

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }
    }

Plan: 1 to add, 0 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.vm_instance: Creating...
google_compute_instance.vm_instance: Still creating... [10s elapsed]
google_compute_instance.vm_instance: Still creating... [20s elapsed]
google_compute_instance.vm_instance: Still creating... [30s elapsed]
google_compute_instance.vm_instance: Still creating... [40s elapsed]
google_compute_instance.vm_instance: Creation complete after 41s [id=terraform-instance]

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

Once again, answer yes to the confirmation prompt.

This is a fairly straightforward change - we added a "google_compute_instance" resource named "vm_instance" to our configuration, and Terraform created the resource in GCP.

»Changing Resources

In addition to creating resources, Terraform can also make changes to those resources.

Add a "tags" argument to your "vm_instance" so that it looks like this:

resource "google_compute_instance" "vm_instance" {
  name         = "terraform-instance"
  machine_type = "f1-micro"
  tags         = ["web", "dev"]
  # ...
}

Run terraform apply again to update the instance.

$ terraform apply
google_compute_network.vpc_network: Refreshing state... [id=terraform-network]
google_compute_instance.vm_instance: Refreshing state... [id=terraform-instance]

...

Terraform will perform the following actions:

  # google_compute_instance.vm_instance will be updated in-place
  ~ resource "google_compute_instance" "vm_instance" {

...

~ tags                 = [
    + "dev",
    + "web",
  ]

...

Plan: 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:

The prefix ~ means that Terraform will update the resource in-place. You can go and and apply this change now by responding yes, and Terraform will add the tags to your instance.

»Destructive Changes

A destructive change is a change that requires the provider to replace the existing resource rather than updating it. This usually happens because the cloud provider doesn't support updating the resource in the way described by your configuration.

Changing the disk image of our instance is one example of a destructive change. Edit the boot_disk block inside the vm_instance resource in your configuration file and change it to the following.

  boot_disk {
    initialize_params {
      image = "cos-cloud/cos-stable"
    }
  }

This will change the boot disk from being a Debian 9 image to use Google's Container-Optimized OS.

Now run terraform apply again to see how Terraform will apply this change to the existing resources.

$ terraform apply
google_compute_network.vpc_network: Refreshing state... [id=terraform-network]
google_compute_instance.vm_instance: Refreshing state... [id=terraform-instance]

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

Terraform will perform the following actions:

  # google_compute_instance.vm_instance must be replaced
-/+ resource "google_compute_instance" "vm_instance" {
        can_ip_forward       = false
      ~ cpu_platform         = "Intel Haswell" -> (known after apply)
        deletion_protection  = false
      ~ guest_accelerator    = [] -> (known after apply)
      ~ id                   = "terraform-instance" -> (known after apply)
      ~ instance_id          = "2506428060139560363" -> (known after apply)
      ~ label_fingerprint    = "42WmSpB8rSM=" -> (known after apply)
      - labels               = {} -> null
        machine_type         = "f1-micro"
      - metadata             = {} -> null
      ~ metadata_fingerprint = "AWPE2PjCWIY=" -> (known after apply)
        name                 = "terraform-instance"
      ~ project              = "hc-training-test" -> (known after apply)
      ~ self_link            = "https://www.googleapis.com/compute/v1/projects/hc-training-test/zones/us-central1-c/instances/terraform-instance" -> (known after apply)
      - tags                 = [] -> null
      ~ tags_fingerprint     = "42WmSpB8rSM=" -> (known after apply)
      ~ zone                 = "us-central1-c" -> (known after apply)

      ~ boot_disk {
            auto_delete                = true
          ~ device_name                = "persistent-disk-0" -> (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          ~ source                     = "https://www.googleapis.com/compute/v1/projects/hc-training-test/zones/us-central1-c/disks/terraform-instance" -> (known after apply)

          ~ initialize_params {
              ~ image = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-9-stretch-v20190618" -> "cos-cloud/cos-stable" # forces replacement
              ~ size  = 10 -> (known after apply)
              ~ type  = "pd-standard" -> (known after apply)
            }
        }

      ~ network_interface {
          + address            = (known after apply)
          ~ name               = "nic0" -> (known after apply)
            network            = "https://www.googleapis.com/compute/v1/projects/hc-training-test/global/networks/terraform-network"
          ~ network_ip         = "10.128.0.2" -> (known after apply)
          ~ subnetwork         = "https://www.googleapis.com/compute/v1/projects/hc-training-test/regions/us-central1/subnetworks/terraform-network" -> (known after apply)
          ~ subnetwork_project = "hc-training-test" -> (known after apply)

          ~ access_config {
              + assigned_nat_ip = (known after apply)
              ~ nat_ip          = "34.66.211.126" -> (known after apply)
              ~ network_tier    = "PREMIUM" -> (known after apply)
            }
        }

      ~ scheduling {
          ~ automatic_restart   = true -> (known after apply)
          ~ on_host_maintenance = "MIGRATE" -> (known after apply)
          ~ preemptible         = false -> (known after apply)

          + node_affinities {
              + key      = (known after apply)
              + operator = (known after apply)
              + values   = (known after apply)
            }
        }
    }

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

The prefix -/+ means that Terraform will destroy and recreate the resource, rather than updating it in-place. While some attributes can be updated in-place (which are shown with the ~ prefix), changing the boot disk image for an instance requires recreating it. Terraform and the GCP provider handle these details for you, and the execution plan makes it clear what Terraform will do.

Additionally, the execution plan shows that the disk image change is what required our instance to be replaced. Using this information, you can adjust your changes to possibly avoid destroy/create updates if they are not acceptable in some situations.

Once again, Terraform prompts for approval of the execution plan before proceeding. Answer yes to execute the planned steps:

  Enter a value: yes

google_compute_instance.vm_instance: Destroying... [id=terraform-instance]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 10s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 20s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 30s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 40s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=terraform-instance, 50s elapsed]
google_compute_instance.vm_instance: Destruction complete after 57s
google_compute_instance.vm_instance: Creating...
google_compute_instance.vm_instance: Still creating... [10s elapsed]
google_compute_instance.vm_instance: Still creating... [20s elapsed]
google_compute_instance.vm_instance: Creation complete after 29s [id=terraform-instance]

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

As indicated by the execution plan, Terraform first destroyed the existing instance and then created a new one in its place. You can use terraform show again to see the new values associated with this instance.