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.