HashiCorp Learn
Infrastructure
  • TerraformTerraformLearn terraformDocs
  • PackerPackerLearn packerDocs
  • VagrantVagrantLearn vagrantDocs
Security
  • VaultVaultLearn vaultDocs
  • BoundaryBoundaryLearn boundaryDocs
Networking
  • ConsulConsulLearn consulDocs
Applications
  • NomadNomadLearn nomadDocs
  • WaypointWaypointLearn waypointDocs
  • HashiCorp Cloud Platform (HCP) LogoHashiCorp Cloud Platform (HCP)HashiCorp Cloud Platform (HCP)Docs
Type '/' to Search
Loading account...
  • Bookmarks
  • Manage Account
  • Overview
  • Defining a Provisioner
  • Running Provisioners
  • Failed Provisioners and Tainted Resources
  • Destroy Provisioners
DocsForum
Back to terraform
GCPView Collection
    Introduction to Infrastructure as Code with TerraformInstall TerraformBuild InfrastructureChange InfrastructureDestroy InfrastructureCreate Resource DependenciesProvision InfrastructureDefine Input VariablesQuery Data with Output Variables

Provision Infrastructure

  • 6 min
  • Products Usedterraform

The compute instance we launched at this point is based on the Google image given, but has no additional software installed or configuration applied.

GCP allows customers to manage their own custom operating system images . This can be a great way to ensure the instances you provision with Terraform are pre-configured based on your needs. Packer is the perfect tool for this and includes a builder for GCP.

In general, we recommend that you use a tool like Packer so that your infrastructure requires little or no provisioning after it's deployed. Managing your infrastructure this way is sometimes called immutable infrastructure, and can help ensure your infrastructure is more robust and fault tolerant.

That said, many infrastructures still require some sort of initialization or software provisioning step. Terraform uses provisioners to upload files, run shell scripts, or install and trigger other software like configuration management tools.

»Defining a Provisioner

To define a provisioner, modify the resource block defining the first vm_instance in your configuration to look like the following.

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

+  provisioner "local-exec" {
+    command = "echo ${google_compute_instance.vm_instance.name}:  ${google_compute_instance.vm_instance.network_interface[0].access_config[0].nat_ip} >> ip_address.txt"
+  }

  # ...
}

You can add multiple provisioner blocks to define multiple provisioning steps. Terraform supports many provisioners, but for this example we are using the local-exec provisioner.

The local-exec provisioner executes a command locally on the machine running Terraform, not the VM instance itself. We're using this provisioner versus the others so we don't have to worry about specifying any connection info right now.

This also shows a more complex example of string interpolation than we've seen before. Each VM instance can have multiple network interfaces, so we refer to the first one with network_interface[0] - counting starting from 0, as most programming languages do. Each network interface can have multiple access_config blocks as well, so once again we specify the first one.

»Running Provisioners

The results of running terraform apply at this point may be confusing at first:

$ terraform apply
random_string.bucket: Refreshing state... [id=fjnh3vk1]
google_compute_network.vpc_network: Refreshing state... [id=projects/testing-project/global/networks/terraform-network]
google_compute_address.vm_static_ip: Refreshing state... [id=projects/testing-project/regions/us-central1/addresses/terraform-static-ip]
google_storage_bucket.example_bucket: Refreshing state... [id=learn-gcp-fjnh3vk1]
google_compute_instance.vm_instance: Refreshing state... [id=projects/testing-project/zones/us-central1-c/instances/terraform-instance]
google_compute_instance.another_instance: Refreshing state... [id=projects/testing-project/zones/us-central1-c/instances/terraform-instance-2]

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

Terraform will find nothing to do. If you check, you'll also find that there's no ip_address.txt file on your local machine.

Terraform treats provisioners differently from other arguments. Provisioners only run when a resource is created, so adding a provisioner to a resource that has already been provisioned does not force that resource to be destroyed and recreated. Use terraform taint to tell Terraform to recreate the instance.

$ terraform taint google_compute_instance.vm_instance
Resource instance google_compute_instance.vm_instance has been marked as tainted.

Terraform will mark the instances as tainted. Any tainted resources will be destroyed and recreated during the next apply. Run terraform apply now.

$ terraform apply
random_string.bucket: Refreshing state... [id=fjnh3vk1]
google_compute_network.vpc_network: Refreshing state... [id=projects/testing-project/global/networks/terraform-network]
google_compute_address.vm_static_ip: Refreshing state... [id=projects/testing-project/regions/us-central1/addresses/terraform-static-ip]
google_storage_bucket.example_bucket: Refreshing state... [id=learn-gcp-fjnh3vk1]
google_compute_instance.another_instance: Refreshing state... [id=projects/testing-project/zones/us-central1-c/instances/terraform-instance-2]
google_compute_instance.vm_instance: Refreshing state... [id=projects/testing-project/zones/us-central1-c/instances/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 is tainted, so must be replaced
-/+ resource "google_compute_instance" "vm_instance" {
      ~ cpu_platform         = "Intel Haswell" -> (known after apply)
      - enable_display       = false -> null
      ~ guest_accelerator    = [] -> (known after apply)
      ~ id                   = "projects/testing-project/zones/us-central1-c/instances/terraform-instance" -> (known after apply)
      ~ instance_id          = "3090747464390022621" -> (known after apply)
      ~ label_fingerprint    = "42WmSpB8rSM=" -> (known after apply)
      - labels               = {} -> null
      - metadata             = {} -> null
      ~ metadata_fingerprint = "uvlMXYfy7lg=" -> (known after apply)
      + min_cpu_platform     = (known after apply)
        name                 = "terraform-instance"
      ~ project              = "testing-project" -> (known after apply)
      ~ self_link            = "https://www.googleapis.com/compute/v1/projects/testing-project/zones/us-central1-c/instances/terraform-instance" -> (known after apply)
        tags                 = [
            "dev",
            "web",
        ]
      ~ tags_fingerprint     = "XaeQnaHMn9Y=" -> (known after apply)
      ~ zone                 = "us-central1-c" -> (known after apply)
        # (3 unchanged attributes hidden)

      ~ boot_disk {
          ~ device_name                = "persistent-disk-0" -> (known after apply)
          + disk_encryption_key_sha256 = (known after apply)
          + kms_key_self_link          = (known after apply)
          ~ source                     = "https://www.googleapis.com/compute/v1/projects/testing-project/zones/us-central1-c/disks/terraform-instance" -> (known after apply)
            # (2 unchanged attributes hidden)

          ~ initialize_params {
              ~ image  = "https://www.googleapis.com/compute/v1/projects/cos-cloud/global/images/cos-stable-85-13310-1041-28" -> "cos-cloud/cos-stable"
              ~ labels = {} -> (known after apply)
              ~ size   = 10 -> (known after apply)
              ~ type   = "pd-standard" -> (known after apply)
            }
        }

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

          ~ access_config {
              ~ network_tier = "PREMIUM" -> (known after apply)
                # (1 unchanged attribute hidden)
            }
        }

      ~ 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)
            }
        }

      - shielded_instance_config {
          - enable_integrity_monitoring = true -> null
          - enable_secure_boot          = false -> null
          - enable_vtpm                 = true -> null
        }
    }

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:

Confirm with a yes.

  Enter a value: yes

google_compute_instance.vm_instance: Destroying... [id=projects/testing-project/zones/us-central1-c/instances/terraform-instance]
google_compute_instance.vm_instance: Still destroying... [id=projects/testing-project/zones/...entral1-c/instances/terraform-instance, 10s elapsed]
google_compute_instance.vm_instance: Destruction complete after 16s
google_compute_instance.vm_instance: Creating...
google_compute_instance.vm_instance: Still creating... [10s elapsed]
google_compute_instance.vm_instance: Provisioning with 'local-exec'...
google_compute_instance.vm_instance (local-exec): Executing: ["/bin/sh" "-c" "echo terraform-instance:  34.67.140.206 >> ip_address.txt"]
google_compute_instance.vm_instance: Creation complete after 19s [id=projects/testing-project/zones/us-central1-c/instances/terraform-instance]

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

Verify that this worked by looking at the contents of the ip_address.txt file.

$ cat ip_address.txt
terraform-instance: 34.67.140.206

It contains the IP address, just as we asked.

»Failed Provisioners and Tainted Resources

If a resource is successfully created but fails a provisioning step, Terraform will error and mark the resource as tainted. A resource that is tainted still exists, but shouldn't be considered safe to use, since provisioning failed.

When you generate your next execution plan, Terraform will remove any tainted resources and create new resources, attempting to provision them again after creation.

»Destroy Provisioners

Provisioners can also be defined that run only during a destroy operation. These are useful for performing system cleanup, extracting data, etc.

For many resources, using built-in cleanup mechanisms is recommended if possible (such as init scripts), but provisioners can be used if necessary.

The getting started tutorial won't show any destroy provisioner examples. If you need to use destroy provisioners, please see the provisioner documentation.


PreviousCreate Resource DependenciesNextDefine Input Variables
HashiCorp
  • System Status
  • Terms of Use
  • Security
  • Privacy
stdin: is not a tty