published: 24th of March 2022
In this post I will show you how to setup Terraform to connect to your Google Cloud Platform (GCP) tenancy to manage your GCP infrastructure as code.
The following software was used in this post.
You will need a Google account to work with GCP. If you don't already have one. Go and create one now.
gcloud is a CLI tool that is used to manage GCP resources. If you don't already have it installed, see the docs for the latest instructions.
Confirm gcloud is installed and accessible.
gcloud -v
# Output
Google Cloud SDK 378.0.0
alpha 2022.03.18
beta 2022.03.18
bq 2.0.74
bundled-python3-unix 3.8.11
core 2022.03.18
gsutil 5.8
This post assumes that you already have Terraform installed. If you don't already have it installed, see the docs for the latest instructions.
Confirm Terraform is installed and accessible.
terraform -v
# Output
Terraform v1.1.7
on linux_amd64
First things first, we will need to create a GCP project. In the web console, naviagte to:
Enter the project name test-project and press create .
I don't have a web browser on my dev machine where I am running Terraform so I will be using a service account to authenticate to GCP. To create a service account navigate to:
Enter the name terraform and press CREATE AND CONTINUE .
In the Role drop down select Editor and click CONTINUE .
Finally, click DONE .
Under the Actions field, press the three dots ( ) and press Manage Keys .
Press ADD KEY and then Create new key .
Select JSON as the format and then press CREATE
Download the keys to a secure location for use on your machine. I am using ~/.gcp and naming the file terraform.json . Once there, be sure the adjust the permissions so they are RW only by you.
chmod 0600 ~/.gcp/terraform.json
Finally, activate the service account by running the following command.
gcloud auth activate-service-account <service-account-email> --key-file=/path/to/.gcp/terraform.json
# Output
Activated service account credentials for: [<service-account-email>]
To allow access to a GCP service via the API we need to enable the service for API access.
For this post we will need to enable the compute.googleapis.com service.
gcloud services enable compute.googleapis.com --project test-project-<id>
# Output
Operation "operations/acf.p2-<some-id>" finished successfully.
Be aware this can take a minute or two to complete.
The project id can be found in the keyfile under the project_id field.
OK phew, that's it for the gcloud setup. Let's move onto the Terraform section.
To allow Terraform to use the servie account credentials we need to set an environment variable in our shell config. I am using ZSH so my environment variables live in the ~/.zshrc file. Adjust accordingly for your environment.
export GOOGLE_APPLICATION_CREDENTIALS="/path/to/.gcp/terraform.json"
Again, make sure this file is RW only by yourself.
chmod 0600 ~/.zshrc
Now, source the file to load the variable into your shell environment.
source ~/.zshrc
Let's create a terraform project. I am creating mine in the ~/code/terraform/gcp-test/ directory.
mkdir -p ~/code/terraform/gcp-test/ && cd ~/code/terraform/gcp-test/
Now, create a file called main.tf with the following contents.
provider "google" {
project = "test-project-<id>"
region = "australia-southeast1"
zone = "australia-southeast1-a"
}
resource "google_compute_instance" "vm_instance" {
name = "terraform-instance"
machine_type = "f1-micro"
boot_disk {
initialize_params {
image = "debian-cloud/debian-9"
}
}
network_interface {
# A default network is created for all GCP projects
network = google_compute_network.vpc_network.self_link
access_config {
}
}
}
resource "google_compute_network" "vpc_network" {
name = "terraform-network"
auto_create_subnetworks = "true"
}
This code specifies the GCP project , region details and a compute instance (VM) . I am using the australia-southeast1 region. Adjust according to your needs.
Region information can be found here
Next, initialize the Terraform project with the terraform init command. This will setup the project and install any required plugins.
terraform init
# Output
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/google...
- Installing hashicorp/google v4.15.0...
- Installed hashicorp/google v4.15.0 (signed by HashiCorp)
Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.
Terraform has been successfully initialized!
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.
If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.
Almost there, lets test our deployment with the terraform plan command. This will perform a dry-run and verify all the resources that need to be created.
terraform plan
# Output
Terraform used the selected providers to generate the following execution plan. 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)
+ current_status = (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)
+ min_cpu_platform = (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)
+ kms_key_self_link = (known after apply)
+ mode = "READ_WRITE"
+ source = (known after apply)
+ initialize_params {
+ image = "debian-cloud/debian-9"
+ labels = (known after apply)
+ size = (known after apply)
+ type = (known after apply)
}
}
+ confidential_instance_config {
+ enable_confidential_compute = (known after apply)
}
+ network_interface {
+ ipv6_access_type = (known after apply)
+ name = (known after apply)
+ network = "default"
+ network_ip = (known after apply)
+ stack_type = (known after apply)
+ subnetwork = (known after apply)
+ subnetwork_project = (known after apply)
+ access_config {
+ nat_ip = (known after apply)
+ network_tier = (known after apply)
}
}
+ reservation_affinity {
+ type = (known after apply)
+ specific_reservation {
+ key = (known after apply)
+ values = (known after apply)
}
}
+ scheduling {
+ automatic_restart = (known after apply)
+ min_node_cpus = (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.
───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
Note: You didnt use the -out option to save this plan, so Terraform cant guarantee to take exactly these actions if
you run "terraform apply" now.
It's looking good, now lets deploy our Terraform plan with the terraform apply -auto-approve command.
terraform apply -auto-approve
# Output
Terraform used the selected providers to generate the following execution plan. 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)
+ current_status = (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)
+ min_cpu_platform = (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)
+ kms_key_self_link = (known after apply)
+ mode = "READ_WRITE"
+ source = (known after apply)
+ initialize_params {
+ image = "debian-cloud/debian-9"
+ labels = (known after apply)
+ size = (known after apply)
+ type = (known after apply)
}
}
+ confidential_instance_config {
+ enable_confidential_compute = (known after apply)
}
+ network_interface {
+ ipv6_access_type = (known after apply)
+ name = (known after apply)
+ network = "default"
+ network_ip = (known after apply)
+ stack_type = (known after apply)
+ subnetwork = (known after apply)
+ subnetwork_project = (known after apply)
+ access_config {
+ nat_ip = (known after apply)
+ network_tier = (known after apply)
}
}
+ reservation_affinity {
+ type = (known after apply)
+ specific_reservation {
+ key = (known after apply)
+ values = (known after apply)
}
}
+ scheduling {
+ automatic_restart = (known after apply)
+ min_node_cpus = (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.
google_compute_instance.vm_instance: Creating...
google_compute_instance.vm_instance: Still creating... [10s elapsed]
google_compute_instance.vm_instance: Creation complete after 18s [id=projects/test-project-345109/zones/australia-southeast1-a/instances/terraform-instance]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
It's looking like it's deployed, let's verify.
List the deployed instances with the gcloud compute instances list command.
gcloud compute instances list --project test-project-<id>
# Output
NAME ZONE MACHINE_TYPE PREEMPTIBLE INTERNAL_IP EXTERNAL_IP STATUS
terraform-instance australia-southeast1-a f1-micro 10.152.0.2 X.X.X.X RUNNING
!!! WE GOT ONE !!!
Let's clean up so we don't have to pay any unnessecary bills.
Kill the infrastructure with the terraform destroy command and type yes at the prompt.
terraform destroy
# Output
google_compute_instance.vm_instance: Refreshing state... [id=projects/test-project-345109/zones/australia-southeast1-a/instances/terraform-instance]
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
- destroy
Terraform will perform the following actions:
# google_compute_instance.vm_instance will be destroyed
- resource "google_compute_instance" "vm_instance" {
- can_ip_forward = false -> null
- cpu_platform = "Intel Broadwell" -> null
- current_status = "RUNNING" -> null
- deletion_protection = false -> null
- enable_display = false -> null
- guest_accelerator = [] -> null
- id = "projects/test-project-345109/zones/australia-southeast1-a/instances/terraform-instance" -> null
- instance_id = "6552850478757173275" -> null
- label_fingerprint = "42WmSpB8rSM=" -> null
- labels = {} -> null
- machine_type = "f1-micro" -> null
- metadata = {} -> null
- metadata_fingerprint = "43DNKhxlZco=" -> null
- name = "terraform-instance" -> null
- project = "test-project-345109" -> null
- resource_policies = [] -> null
- self_link = "https://www.googleapis.com/compute/v1/projects/test-project-345109/zones/australia-southeast1-a/instances/terraform-instance" -> null
- tags = [] -> null
- tags_fingerprint = "42WmSpB8rSM=" -> null
- zone = "australia-southeast1-a" -> null
- boot_disk {
- auto_delete = true -> null
- device_name = "persistent-disk-0" -> null
- mode = "READ_WRITE" -> null
- source = "https://www.googleapis.com/compute/v1/projects/test-project-345109/zones/australia-southeast1-a/disks/terraform-instance" -> null
- initialize_params {
- image = "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-9-stretch-v20220317" -> null
- labels = {} -> null
- size = 10 -> null
- type = "pd-standard" -> null
}
}
- network_interface {
- name = "nic0" -> null
- network = "https://www.googleapis.com/compute/v1/projects/test-project-345109/global/networks/default" -> null
- network_ip = "10.152.0.2" -> null
- queue_count = 0 -> null
- stack_type = "IPV4_ONLY" -> null
- subnetwork = "https://www.googleapis.com/compute/v1/projects/test-project-345109/regions/australia-southeast1/subnetworks/default" -> null
- subnetwork_project = "test-project-345109" -> null
- access_config {
- nat_ip = "34.116.64.143" -> null
- network_tier = "PREMIUM" -> null
}
}
- scheduling {
- automatic_restart = true -> null
- min_node_cpus = 0 -> null
- on_host_maintenance = "MIGRATE" -> null
- preemptible = false -> null
}
}
Plan: 0 to add, 0 to change, 1 to destroy.
Do you really want to destroy all resources?
Terraform will destroy all your managed infrastructure, as shown above.
There is no undo. Only yes will be accepted to confirm.
Enter a value: yes
google_compute_instance.vm_instance: Destroying... [id=projects/test-project-345109/zones/australia-southeast1-a/instances/terraform-instance]
google_compute_instance.vm_instance: Still destroying... [id=projects/test-project-345109/zones/aust...theast1-a/instances/terraform-instance, 10s elapsed]
google_compute_instance.vm_instance: Still destroying... [id=projects/test-project-345109/zones/aust...theast1-a/instances/terraform-instance, 20s elapsed]
google_compute_instance.vm_instance: Destruction complete after 22s
Destroy complete! Resources: 1 destroyed.
And one last check to confirm the instance is gone.
gcloud compute instances list --project test-project-<id>
# Output
Listed 0 items.
I believe this means we are devops now.
If you made it this far, thanks for following along.
In this post, we setup our GCP environment with a service account that is used by Terraform to create resources via the GCP API.
Smoke me a kipper, i'll be back for breakfast!
https://cloud.google.com/community/tutorials/getting-started-on-gcp-with-terraform
https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started
https://cloud.google.com/sdk/docs/authorizing
https://cloud.google.com/sdk/gcloud/reference/services/enable