Terraform Google Cloud Platform Setup
Published: 2022-03-24
Intro
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.
Software
The following software was used in this post.
- Terraform - 1.1.7
- Ubuntu - 2004
- gcloud - 378.0.0
Pre-Flight Check
Google Account
You will need a Google account to work with GCP. If you don't already have one. Go and create one now.
gcloud
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.8Terraform
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_amd64Setup GCP
Create Project
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.
Create a Service Account
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.
Create Authentication Keys
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
Store Keys
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.jsonActivate Service Account
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>]Enable API Services
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.OK phew, that's it for the gcloud setup. Let's move onto the Terraform section.
Terraform
Link Credentials
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 ~/.zshrcNow, source the file to load the variable into your shell environment.
source ~/.zshrcTerraform Project
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.Plan
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.Deploy
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 !!!
Destroy
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.
Outro
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!
Links
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