Terraform for Dummies Part 4: Launch a VM with a Static Website on a Google Cloud Platform —…
Tags: gcp, googlecloudplatform, terraform, tutorial
Published: 2023-06-19 21:15:53

---
title: Terraform for Dummies Part 4: Launch a VM with a Static Website on a Google Cloud Platform —…
published: true
date: 2021-10-29 17:49:50 UTC
tags: gcp,googlecloudplatform,terraform,tutorial
canonical_url: 
---

### Terraform for Dummies Part 4: Launch a VM with a Static Website on a Google Cloud Platform — Eclipsys

### Terraform for Dummies Part 4: Launch a VM with a Static Website on a Google Cloud Platform

### Intro

After [AWS](http://www.brokedba.com/2020/10/terraform-for-dummies-part-2-launch.html), [Oracle Cloud](https://brokedba.blogspot.com/2020/07/terraform-for-dummies-launch-instance.html), and [Azure](http://www.blogspot.brokedba.com/2021/10/terraform-for-dummies-part-3-launch-vm.html), GCP (Google Cloud Platform) is the 4th cloud platform in our Terraform tutorial series. We will describe what it takes to authenticate and provision a compute engine using their Terraform provider. The instance will also have an Nginx website linked to its public IP. If you want to know about the differences GCP brings in terms of networking, you can check out my [blog](http://www.brokedba.com/2021/08/what-makes-gcp-networking-service.html).

**Note:** GCP Terraform provider authentication was hell to get a hold on and counter-intuitive compared to other Cloud platforms. I wasted a lot of time just trying to figure if I could avoid hardcoding project id.

Here’s a direct link to my GitHub repo linked to this lab: [terraform-examples/terraform-provider-gcp](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp)

**Content:**  
[**I. Terraform Setup**](https://brokedba.blogspot.com/2021/10/terraform-for-dummies-part-4-launch-vm.html#setup)  
[**IV. Partial Deployment**](https://brokedba.blogspot.com/2021/10/terraform-for-dummies-part-4-launch-vm.html#partial)  
[**V. Full Deployment**](https://brokedba.blogspot.com/2021/10/terraform-for-dummies-part-4-launch-vm.html#full)  
[**Tips and Conclusion**](https://brokedba.blogspot.com/2021/10/terraform-for-dummies-part-4-launch-vm.html#tips)

### Overview and Concepts

#### Topology

The following illustration shows the layers involved between your workstation and GCP cloud while running the Terraform actions along with the instance attributes we will be provisioning.

![](https://cdn-images-1.medium.com/max/850/0*Rn2O5ji4-aHRMw9Y.png)

Besides describing my [GitHub repo](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp) before starting this tutorial, I’ll just briefly discuss some principles.

**Terraform Files**

Can be a single file or split into multiple **tf** or **tf.json** files, any other file extension is ignored.

Files are merged in alphabetical order but resource definition order doesn’t matter (subfolders are not read).

Common configurations have 3 type of **tf** files and a statefile.

1. **main.tf:** Terraform declaration code (configuration) . The file name can be anything you choose
2. **variables.tf:** Resource variables needed for the deploy
3. **outputs.tf:** displays the resources detail at the end of the deploy
4. **terraform.tfstate:** keeps track of the state of the stack(resources) after each Terraform apply run

**Terraform resource declaration syntax** looks like this:

```
Component "Provider_Resource_type" "MyResource_Name" { Attribute1 = value .. 
                                                       Attribute2 = value ..}
```

**Where do I find a good GCP deployment sample?**

The easiest way is to create/locate an instance from the console and then use the **import function** from Terraform to generate each of the related components in HCL format (VPC, instance,subnet,etc..) based on their id.

**Example for a VPC >>**

1. Create a shell resource declaration for the VPC in a file called ** vpc.tf**
2. Get the id of the VPC resource from your **GCP portal**
3. Run the **Terraform import** then run **Terraform show** to extract the VPC full declaration from GCP to the same file ( **vpc**.tf)
4. Now you can remove the id attribute with all non required attributes to create a [vpc resource](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/compute_network) (Do that for each resource)

```
1- # vi vpc.tf 
  provider "google" { 
              features {}
    }
  resource "google_compute_network" "terra_vpc" {
   }
2- # terraform import google_compute_network.terra_vpc {{project}}/{{name}}
3- # terraform show -no-color > vpc.tf 
```

**Note**  
If you want to import all the existing resources in your account in bulk mode [**terraformer**](https://github.com/GoogleCloudPlatform/terraformer) can help import both code and state from your **GCP** account automatically.

**Terraform Lab Content:** I purposely split this lab in 2 for more clarity

- [**VPC Deployment**](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp/create-vpc): To grasp the basics of single resource deployment.
- [**Instance Deployment**](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp/launch-instancel): Includes the instance provisioning configured as a web server(includes above VPC).

### I. Terraform Setup

Although I’m on windows I tried the lab using [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10) (Ubuntu) terminal (but same applies to Mac).

- **Windows:** Download and run the installer from their [website](https://www.terraform.io/downloads.html) ([32-bit](https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_windows_386.zip) ,[64-bit](https://releases.hashicorp.com/terraform/0.12.28/terraform_0.12.28_windows_amd64.zip))
- **Linux:** Download, unzip and move the binary to the local bin directory

```
$ wget https://releases.hashicorp.com/terraform/1.0.3/terraform_1.0.3_linux_amd64.zip 
$ unzip terraform_1.0.3_linux_amd64.zip 
$ mv terraform /usr/local/bin/
```

- Once installed run the version command to validate your installation

```
$ terraform --version
  Terraform v1.0.3
```

**GCP authentication (least user friendly)**

To authenticate to GCP with Terraform you will need **GCloud** , service account credentials key file, and the projectId

**Prerequisites**

Using dedicated service accounts to authenticate with GCP is recommended practice (not user accounts or API keys)

- **_GCLOUD authentication_** configured with your **GCP** credentials. Refer to my [Blog post](https://brokedba.blogspot.com/2021/09/google-sdk-cli-for-gcp-installation-and.html) for more details

```
$ gcloud auth login --activate
…
$ gcloud config list --format='table(account,project)'
 ACCOUNT PROJECT
-------------- ------------- 
bdba@gmail.com brokedba2000   
```

**Service account:** Either you create a service account with “ **owner role** “ in the console or run the below cli commands

```
1 -- Create service account
$ gcloud iam service-accounts create terraform-sa --display-name="Terra_Service"
$ gcloud iam service-accounts list --filter="email~terraform" --format='value(email)'

2 -- Bind it to a project and add owner role 
$ gcloud projects add-iam-policy-binding PROJECT_ID --member="serviceAccount:email" --role="roles/owner"

3 -– Generate the Key file for the service account
$ gcloud iam service-accounts keys create ~/gcp-key.json --iam-account=email
```

I’ll also assume the presence of an **ssh key pair** to attach to your VM instance. If not here is a command to generate a PEM based key pair.

```
$ ssh-keygen -P "" -t rsa -b 2048 -m pem -f ~/id_rsa_az
     Generating public/private rsa key pair.
 Your identification has been saved in /home/brokedba/id_rsa_az.
 Your public key has been saved in /home/brokedba/id_rsa_az.pub.
```

### II. Clone the Repository

- Pick an area that is close to your gcp-terraform directory on your file system and issue the following command.

```
$ git clone https://github.com/brokedba/terraform-examples.git
```

**Note:** As explained earlier you will find 2 directories inside the repository which will make things easier:

1. [**terraform-provider-gcp/create-vpc/**](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp/create-vpc) To grasp how we deploy a VPC.
2. [**terraform-provider-gcp/launch-instance/**](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp/launch-instance) For the final instance deploy.

### III. Provider Setup

#### 1. INSTALL AND SETUP THE GCP PROVIDER

- Cd Into _“_ **terraform-provider-gcp/create-vpc”** where our configuration resides (i.e vpc)

```
$ cd /brokedba/gcp/terraform-examples/terraform-provider-gcp/create-vpc 
```

- **GCP** provider plugin will be automatically installed by running **”terraform init”**.

```
$ terraform init
  Initializing the backend...

  Initializing provider plugins...
  - Finding latest version of hashicorp/google...
  - Installing hashicorp/google v3.88.0...
  * Installed hashicorp/google v3.88.0 (signed by HashiCorp)
Terraform has been successfully initialized!

$ terraform --version
  Terraform v1.0.3 on linux_amd64
  + provider registry.terraform.io/hashicorp/google v3.88.0   
```

- Let’s see what’s in the **”create-vpc”** directory. Here, only **\*.tf** files matter

```
$ tree
  .
  |-- outputs.tf ---> displays resources detail after the deploy
  |-- variables.tf ---> Resource variables needed for the deploy   
  |-- vpc.tf ---> Our vpc terraform declaration 
```

### IV. Partial Deployment

#### DEPLOY A SIMPLE VPC

- Once the authentication is setup and provider installed , we can run **terraform plan** command to create an execution plan (quick dry run to check the desired end-state).

```
$ terraform plan
var.prefix The prefix used for all resources in this example
Enter a value: Demo
Terraform used 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_network.terra_vpc will be created
    + resource "google_compute_firewall" "web-server" 
    {..
     + name = "allow-http-rule"
     + allow {
     + ports = [+ "80", + "22",+ "443",+ "3389",]
     + protocol = "tcp"
    ...

    # google_compute_firewall.web-server will be created
    + resource "google_compute_firewall" "web-server" {
    {..} 
    # google_compute_subnetwork.terra_sub will be created
    + resource "google_compute_subnetwork" "terra_sub"  
    {..
     ip_cidr_range = ["192.168.10.0/24”]
     ...}

  Plan: 3 to add, 0 to change, 0 to destroy.
```

- The output being too verbose I deliberately kept only relevant attributes for the VPC resource plan

- Next, we can run **”terraform deploy”** to provision the resources to create our VPC (listed in the plan)

```
$ terraform apply -auto-approve
google_compute_network.terra_vpc: Creating...
google_compute_firewall.web-server: Creating...
google_compute_subnetwork.terra_sub: Creating...
...
Apply complete! Resources: 3 added, 0 changed, 0 destroyed.

Outputs:

project = "brokedba2000"
```

![](https://cdn-images-1.medium.com/max/829/0*f0b1KhvlaWGem-ca)

**Observations:**

- The deployment started by loading the resources variables in [variables.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-gcp/create-vpc/variables.tf) which allowed the execution of [vpc.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-gcp/create-vpc/vpc.tf)
- Finally Terraform fetched the attributes of the created resources listed in [outputs.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-gcp/create-vpc/outputs.tf)

**Note** : We’ll now destroy the **VPC** as the next instance deploy contains the same **VPC**  specs.

```
$ terraform destroy -auto-approve

Destroy complete! Resources: 3 destroyed.
```

### V. Full deployment (Instance)

#### 1. OVERVIEW

- After our small intro to VPC creation, let’s launch a VM and configure Nginx in it in one command.
- First, we need to switch to the second directory [**terraform-provider-gcp/launch-instance/**](https://github.com/brokedba/terraform-examples/tree/master/terraform-provider-gcp/launch-instance)  
 Here's the content:

```
$ tree ./terraform-provider-gcp/launch-instance
.
|-- cloud-init --> SubFolder
| `--> centos_userdata.txt --> script to config a webserver the Web homepage
| `--> sles_userdata.txt --> for SUSE
| `--> ubto_userdata.txt --> for Ubunto 
| `--> el_userdata.txt --> for Enteprise linux distros 

|-- compute.tf ---> Compute engine Instance terraform configuration
|-- outputs.tf ---> displays the resources detail at the end of the deploy
|-- variables.tf ---> Resource variables needed for the deploy   
|-- vpc.tf ---> same vpc we deployed earlier
```

**Note:** As you can see we have 2 additional files and one Subfolder. is where the compute instance and all its attributes are declared. All the other “. **tf”** files come from my **VPC** example with some additions for [variables.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-gcp/launch-instance/variables.tf) and [output.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-gcp/launch-instance/outputs.tf)

- **Cloud-init** : is a cloud instance initialization method that executes scripts upon instance Startup. see below **metadata** entry of the **vm instance** definition (startup-script). There are 5 OS’ scripts (Centos,Ubuntu,Windows,RHEL,SUSE) windows was not tested.

```
...variable "user_data" { default = "./cloud-init/centos_userdata.txt"} 
    
 $ vi compute.tf 
resource "google_compute_instance" "terravm" { 
metadata = {
startup-script = ("${file(var.user_data)}")
...     
```

- In my lab, I used **cloud-init** to install Nginx and write an HTML page that will replace the HomePage at Startup.
- Make sure your **public ssh key** is in your home directory or just modify the path below (see [variables.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-gcp/launch-instance/variables.tf))

```
$ vi compute.tf
resource "google_compute_instance" "terravm" {
metadata = {

admin_ssh_key {

   ssh-keys = var.admin":${file("~/id_rsa_gcp.pub")}" ## Change me
```

#### 2. LAUNCH THE INSTANCE

- Once in “ **launch-instance”** directory, you can run the plan command to validate the 9 resources required to launch our vm instance. The output has been truncated to reduce verbosity

```
$ terraform plan
  ------------------------------------------------------------------------
    Terraform will perform the following actions:

  ... # VPC declaration (see previous VPC deploy) 
  ...  
 # google_compute_instance.terra_instance will be created
 + resource "google_compute_instance" "terra_instance" {
    + ...
    + hostname = "terrahost"
    + machine_type = "e2-micro"
    + name = "Terravm"
    + tags = [+ "web-server",]
    + boot_disk {
    + initialize_params { + image = "centos-cloud/centos-7"
    + network_interface {
      ... 
    + network_ip = "192.168.10.51"
     } 
    + metadata = {
  + "ssh-keys" = <<-EOT ssh-rsa AAAABxxx…*
         EOT 
   + "startup-script" = <<-EOT”        
      EOT}
  
  # google_compute_address.internal_reserved_subnet_ip will be created
  + resource "google_compute_address" "internal_reserved_subnet_ip" {
      ...}
   ...
  }
  Plan: 5 to add, 0 to change, 0 to destroy. terraform apply -auto-approve ... google_compute_network.terra_vpc: Creating... google_compute_firewall.web-server: Creating... google_compute_subnetwork.terra_sub: Creating... google_compute_address.internal_reserved_subnet_ip: Creating... google_compute_instance.terra_instance: Creating... Apply complete! Resources: 5 added, 0 changed, 0 destroyed. Outputs: vpc_name = "terra-vpc" Subnet_Name = "terra-sub" Subnet_CIDR = "192.168.10.0/24" fire_wall_rules = toset([{... "ports" = tolist([ "description" = "RDP-HTTP-HTTPS ingress trafic" "destination_port_ranges" = toset([ "80", "3389", "443", "3389",]) ] hostname = "terrahost.brokedba.com" project = "brokedba2000" private_ip = "192.168.10.51" public_ip = "35.227.81.2" SSH_Connection = "ssh connection to instance TerraCompute ==> sudo ssh -i ~/id_rsa_gcp centos@35.227.81.2"
```

- Now let’s launch our CENTOS7 vm using **terraform apply** (I left a map of different OS ids in the [variables.tf](https://github.com/brokedba/terraform-examples/blob/master/terraform-provider-aws/launch-instance/variables.tf) you can choose from)

```
$ terraform apply -auto-approve
...
google_compute_network.terra_vpc: Creating...
google_compute_firewall.web-server: Creating...
google_compute_subnetwork.terra_sub: Creating...
google_compute_address.internal_reserved_subnet_ip: Creating...
google_compute_instance.terra_instance: Creating...
Apply complete! Resources: 5 added, 0 changed, 0 destroyed.

Outputs:
vpc_name = "terra-vpc"
Subnet_Name = "terra-sub"
Subnet_CIDR = "192.168.10.0/24"
fire_wall_rules = toset([
 {…
  "ports" = tolist([
  "description" = "RDP-HTTP-HTTPS ingress trafic"
  "destination_port_ranges" = toset([
  "80",
  "3389",
  "443",
  "3389",])
]
hostname = "terrahost.brokedba.com"
project = "brokedba2000"
private_ip = "192.168.10.51"
public_ip = "35.227.81.2"
 SSH_Connection = "ssh connection to instance TerraCompute ==> sudo ssh -i ~/id_rsa_gcp centos@35.227.81.2"
```

![](https://cdn-images-1.medium.com/max/1020/0*ApEBrDVxZT04GnzU)

![](https://cdn-images-1.medium.com/max/1023/0*viwL4LUakxZZ2yvh)

- Once the instance is provisioned, juts copy the public IP address(i.e **52.191.26.102** ) in chrome and Voila!
- You can also tear down this configuration by simply running **terraform destroy** from the same directory

### Tips

- You can fetch any of the specified attributes in **outputs.tf** using terraform output command i.e:

```
$ terraform output SSH_Connection
ssh connection to instance TerraCompute ==> sudo ssh -i ~/id_rsa_gcp centos@ ’public_IP’ 
```

- **Terraform Console:**  
 Although Terraform is a declarative language, there are still myriads of functions you can use to process strings/number/lists/mappings etc. There is an excellent all in one script with examples of most terraform functions >>[here](https://cloudaffaire.com/terraform-functions/)
- I added cloud-init files for different distros you can play with by adapting **var.user\_data** &  **var.OS**

### CONCLUSION

- We have demonstrated in this tutorial how to quickly deploy a web server instance using terraform in GCP and leverage Cloud-init (Startupscript) to configure the VM during the bootstrap .
- We had to hardcode the **projectId** although it’s embedded in the config **credentials** (key file) which make it tedious and rigid
- Remember that all user attributes in this exercise can be modified in the **variables.tf** file.
- Route table and internet gateway didn’t need to be created
- **Improvement:** Validate that the startup script works for windows too.  
 Another improvement can be reached in terms of the display of the security rules using[formatlist](https://www.terraform.io/docs/language/functions/formatlist.html) .   
 stay tuned

_Originally published at_ [_https://eclipsys.ca_](https://eclipsys.ca/terraform-for-dummies-part-4-launch-a-vm-with-a-static-website-on-a-google-cloud-platform/)