9 min to read
Beginners Guide to building in GCP with Terraform
Automate the build of a web server and Ansible on Google Compute Engine with Terraform
Goal
This article will set out what you need in order to be able to standup an automated build of a VM (Google Cloud) a web server (Apache) and Ansible.
What will we cover
Deployment of a Google Compute VM instance using Terraform Use of a Compute Instance startup script or user-data Rendering a Terraform template Dependencies
You have created a GCP account (or trial account). You will receive $300 of credits and also 750 hours per month will be free if you select the e2-micro instance. More information can be found in the tutorial below including the configuration of your gcp-creds.json file
The Google Cloud SDK is installed link here Google Cloud SDK installation guide
You can validate this with:
gcloud --version
terraform --version
Our Terraform code will build all the firewall rules, network, API dependencies and resources along with ssh key access using your own public key id_rsa.pub
Lets Begin
Creating a Google Compute Engine VM
mkdir terraform-project
cd terrafor-project
Define the Google provider in your terraform code along with client.json credentials.
Create all the code in your terraform project directory
vi main.tf
# Main terraform code block
# Author: Longy
# Date: March 2022
# Description: Automate build of CE, Firewall rules & tags. Install web server, ansible & docker
# Platform: GCP
# Version: 1.0
#########################################################################
provider "google" {
credentials = file("my-gcp-creds.json") // export GCLOUD_KEYFILE_JSON
project = "dev-work-342110" // export GCLOUD_PROJECT
region = "us-west1" // export GCLOUD_REGION
zone = "us-west1-c" // export GCLOUD_ZONE
}
NOTE this can also be set as variables see addendum for additional improvements.
We are now going to create the resources in our code:
Firewall rules
resource "google_compute_firewall" "web" {
name = "web-access"
network = "default"
source_ranges = ["0.0.0.0/0"]
allow {
protocol = "tcp"
ports = ["80", "443", "22"]
}
target_tags = ["http-server", "https-server", "allow-ssh"]
priority = 1000
}
Enable APIs
resource "google_project_service" "api" {
for_each = toset([
"cloudresourcemanager.googleapis.com",
"compute.googleapis.com"
])
disable_on_destroy = false
service = each.value
}
Configure Compute Engine instance, network and bootstrapping
resource "google_compute_instance" "gcp-dev-build" {
name = "gcp-server-dev"
machine_type = "e2-micro"
boot_disk {
initialize_params {
image = "ubuntu-1804-bionic-v20220131"
}
}
network_interface {
network = "default" // This enables Private IP Address
access_config {} // This enables Public IP Address
}
Apply the firewall rule to allow external IPs to access this instance
tags = ["http-server", "https-server", "gcp-dev-build"]
To use the latest Debian disk image we can use the data source and any type of instance can be selected:
data "google_compute_image" "debian" {
family = "ubuntu-1804-lts"
project = "gce-uefi-images"
}
You can play around with different build installations
Invoke our build script
metadata_startup_script = file("/Users/alongmuir/coding/terraform-project/gcp-user-data.sh")
depends_on = [google_project_service.api, google_compute_firewall.web]
}
The contents of the gcp-user-data.sh are as follows.
#!/bin/bash
# Test Basic Web Page. Install updates, apache and enable/start
sudo apt-get update
sudo apt-get install -y apache2
sudo systemctl start apache2
sudo systemctl enable apache2
sudo echo "<h1>Instance deployed by Terraform in GCP instance build time</h1> `date`" | sudo tee /var/www/html/index.html
# Install pip and ansible
sudo curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
sudo python3 get-pip.py --user
sudo python3 -m pip install --user ansible
sudo python3 get-pip.py
sudo python3 -m pip install ansible
# Upgrade to latest pip
sudo python3 -m pip install --upgrade pip
sudo apt install ansible -y
Pass public RSA key via terraform to VM instance
# Pass public RSA key via terraform to VM instance
resource "google_compute_project_metadata" "my_ssh_key" {
metadata = {
ssh-keys = <<EOF
< Paste the contents of your id_rsa.pub file here>
EOF
}
}
This will hardcode they key into your code there are ways you can obfuscate this. Some additional research for you 😉 but worth noting not to push your code to any public GIT repos.
Lets display our outputs
data "google_project" "project" {
}
output "instance_ip" {
value = join(" ", google_compute_instance.gcp-dev-build.*.network_interface.0.access_config.0.nat_ip)
description = "The public IP address of the newly created instance"
}
output "project_name" {
value = data.google_project.project.name
}
Lets Initialise our Terraform Project
To do this run terraform init see output below.
Initializing the backend...
Initializing provider plugins...
- Finding latest version of hashicorp/google...
- Installing hashicorp/google v4.13.0...
- Installed hashicorp/google v4.13.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.
This will download your provider resources defined earlier. You can view this under .terraform folder in your project directory.
Now lets validate the syntax of our code with terraform validate
You should see
Success! The configuration is valid.
Running terraform plan will inform you of what will be created or changed when running your code. Terraform remembers this by its state file. I’ll do an additional article on how to configure this best practice.
OK … lets build our resource in GCP. To do this execute terraform apply and answer yes
If you run terraform apply -auto-approve this will omit the need to validate with a prompt.
Plan: 5 to add, 0 to change, 0 to destroy.
Changes to Outputs:
+ instance_ip = (known after apply)
+ project_name = "Dev-Work"
google_project_service.api["compute.googleapis.com"]: Creating...
google_project_service.api["cloudresourcemanager.googleapis.com"]: Creating...
google_compute_project_metadata.my_ssh_key: Creating...
google_compute_firewall.web: Creating...
google_project_service.api["compute.googleapis.com"]: Creation complete after 4s [id=dev-work-342110/compute.googleapis.com]
google_project_service.api["cloudresourcemanager.googleapis.com"]: Creation complete after 4s [id=dev-work-342110/cloudresourcemanager.googleapis.com]
google_compute_project_metadata.my_ssh_key: Still creating... [10s elapsed]
google_compute_firewall.web: Still creating... [10s elapsed]
google_compute_firewall.web: Creation complete after 12s [id=projects/dev-work-342110/global/firewalls/web-access]
google_compute_instance.gcp-dev-build: Creating...
google_compute_project_metadata.my_ssh_key: Creation complete after 12s [id=dev-work-342110]
google_compute_instance.gcp-dev-build: Still creating... [10s elapsed]
google_compute_instance.gcp-dev-build: Creation complete after 13s [id=projects/dev-work-342110/zones/us-west1-c/instances/gcp-server-dev]
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.
Outputs:
instance_ip = "34.83.92.251"
project_name = "Dev-Work"
Now if you navigate to the Google Console and navigate to Compute Engine –> VM Instance, you will see an instance coming up. Once the instance is up successfully, browse the webserver_ip. In this case, go to http://34.83.92.251

NOTE You can see the terraform output showing the public IP address
Here we can see the web server is provisioned by code and build time

To remove all resources that have been created just run terraform destroy you can also apply the same switch if you do not wish to be prompted
Additional Improvements
Given the size and complexity of the terraform code it isn’t worth the effort to break this down into modules and separate .tf files. However this is good practice so for extra practice attempt the following:
Remember Terraform is a declarative language so this will help in modularisation of your code.
- Create separate .tf module files
- Create custom variables
- Running
terraform fmtwill ensure your code is nicely linted - Test the Ansible deployment by configuring a local
Inventory.txtfile and running the following command.
ansible gcp_servers -m script -a "update_web_server.sh"
The contents of your ```update_web_server.sh``` file should be something like
```bash
sudo echo "<h1>Instance Built Using Terraform in GCP</h1> `date`" | sudo tee /var/www/html/index.html
Thats all until next time 💥
Good luck and Happy Coding 💻
Comments