Deploy Web Server on Google Compute Engine (GCE) with Terraform
Updated on August 10, 2022
In this blog, I will show how to deploy a Web Server (Nginx) using Terraform on Google Compute Engine(GCE).
There are many ways to deploy Nginx
server on GCP (like on GKE, App Engine, GCE etc.) but for this post I will use GCE to illustrate its usage.
The source code used in this blog is available here.
Goal
Deploy a Web Server on Google Compute Engine (GCE) using Terraform.
What we will explore?
- Deploying a Google Compute VM Instance using Terraform.
- Use of Compute Instance
startup
script. - Rendering a template in terraform.
Prerequisites
This post assumes the following:
1. We already have a GCP Project with a network. By default, every GCP Project comes with a default
network.
2. Google Cloud SDK (gcloud
) and Terraform
is setup on your workstation. If you don’t have, then refer to my previous blogs - Getting started with Terraform and Getting started with Google Cloud SDK.
Create a Compute VM Instance
Create a unix directory for the Terraform project.
mkdir ~/terraform-webserver cd ~/terraform-webserver
Define Terraform Google Provider.
vi provider.tf
This file has following content
# Specify the GCP Provider provider "google" { project = var.project_id region = var.region }
Write below terraform code to create a Google Compute VM Instance.
vi vm.tf
To use the latest
debian
disk, we can use the data sourcedata "google_compute_image" "ubuntu" { family = "ubuntu-2204-lts" project = "ubuntu-os-cloud" }
# Creates a GCP VM Instance. Metadata Startup script install the Nginx webserver. resource "google_compute_instance" "vm" { name = var.name machine_type = var.machine_type zone = var.zone tags = ["http-server"] labels = var.labels boot_disk { initialize_params { image = data.google_compute_image.ubuntu.self_link } } network_interface { network = "default" access_config { // Ephemeral IP } } metadata_startup_script = data.template_file.nginx.rendered }
Note: To allow
HTTP
connection to VM instance, we puthttp-server
tag on the VM astags = ["http-server"]
.Now, lets define a template file which has script to install
Nginx
server and create a simple webpageindex.html
mkdir template vi template/install_nginx.tpl
#!/bin/bash set -e echo "***** Installing Nginx *****" apt update apt install -y nginx ufw allow '${ufw_allow_nginx}' systemctl enable nginx systemctl restart nginx echo "***** Installation Complteted!! *****" echo "Welcome to Google Compute VM Instance deployed using Terraform!!!" > /var/www/html echo "***** Startup script completes!! *****"
Note: We pass the value of
'${ufw_allow_nginx}'
from terraform code during template rendering.Let’s, render the above template.
vi vm.tf
Append the following code.
data "template_file" "nginx" { template = "${file("${path.module}/template/install_nginx.tpl")}" vars = { ufw_allow_nginx = "Nginx HTTP" } }
Once the instance comes up, we want to know its public IP so that we can browse the webpage. To do this, we can use terraform outputs.
vi outputs.tf
output "webserver_ip" { value = google_compute_instance.vm.network_interface.0.access_config.0.nat_ip }
Now, define all the variables in a file.
vi variables.tf
variable "project_id" { description = "Google Cloud Platform (GCP) Project ID." type = string } variable "region" { description = "GCP region name." type = string default = "europe-west1" } variable "zone" { description = "GCP zone name." type = string default = "europe-west1-b" } variable "name" { description = "Web server name." type = string default = "my-nginx-webserver" } variable "machine_type" { description = "GCP VM instance machine type." type = string default = "f1-micro" } variable "labels" { description = "List of labels to attach to the VM instance." type = map }
Define require variables value in
tfvars
file.vi terraform.tfvars
project_id = "gcp-project-id" labels = { "environment" = "dev" "team" = "devops" "application" = "webserver" }
We now have all the required terraform configuration. So, let’s initialize the terraform project.
terraform init
Output
Initializing the backend... Initializing provider plugins... - Finding latest version of hashicorp/google... - Finding latest version of hashicorp/template... - Installing hashicorp/google v4.31.0... - Installed hashicorp/google v4.31.0 (signed by HashiCorp) - Installing hashicorp/template v2.2.0... - Installed hashicorp/template v2.2.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.
After successful initialization, run plan and save plan in a file.
terraform plan --out 1.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 will be created + resource "google_compute_instance" "vm" { + 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) + labels = { + "application" = "webserver" + "environment" = "dev" + "team" = "devops" } + machine_type = "f1-micro" + metadata_fingerprint = (known after apply) + metadata_startup_script = <<-EOT #!/bin/bash set -e echo "***** Installing Nginx *****" apt update apt install -y nginx ufw allow 'Nginx HTTP' systemctl enable nginx systemctl restart nginx echo "***** Installation Complteted!! *****" echo "Welcome to Google Compute VM Instance deployed using Terraform!!!" > /var/www/html/index.html echo "***** Startup script completes!! *****" EOT + min_cpu_platform = (known after apply) + name = "my-nginx-webserver" + project = (known after apply) + self_link = (known after apply) + tags = [ + "http-server", ] + tags_fingerprint = (known after apply) + zone = "europe-west1-b" + 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 = "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-2204-jammy-v20220712a" + 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) + instance_termination_action = (known after apply) + min_node_cpus = (known after apply) + on_host_maintenance = (known after apply) + preemptible = (known after apply) + provisioning_model = (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. Changes to Outputs: + webserver_ip = (known after apply) ─────────────────────────────────────────────────────────────────────────────────────────────────────── Saved the plan to: 1.plan To perform exactly these actions, run the following command to apply: terraform apply "1.plan"
Plan shows to create a VM instance and use
install_nginx.tpl
as startup script. So, let’s go ahead and apply the plan.terraform apply 1.plan
Output
google_compute_instance.vm: Creating... google_compute_instance.vm: Still creating... [10s elapsed] google_compute_instance.vm: Creation complete after 12s [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: webserver_ip = "34.79.25.220"
Now if you navigate to Google Console and navigate to
Compute Engine --> VM Instance
, you will see an instance coming up. Once the instance is up successfully, browse thewebserver_ip
. In this case, go tohttp://34.79.25.220
For cleanup, run terraform destroy.
terraform destroy
Output
google_compute_instance.vm: Refreshing state... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver] 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 will be destroyed - resource "google_compute_instance" "vm" { - can_ip_forward = false -> null - cpu_platform = "Intel Haswell" -> null - current_status = "RUNNING" -> null - deletion_protection = false -> null - enable_display = false -> null - guest_accelerator = [] -> null - id = "projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver" -> null - instance_id = "6795915107669774507" -> null - label_fingerprint = "ko4NbUxfxog=" -> null - labels = { - "application" = "webserver" - "environment" = "dev" - "team" = "devops" } -> null - machine_type = "f1-micro" -> null - metadata = {} -> null - metadata_fingerprint = "mE2Cwt2znPk=" -> null - metadata_startup_script = <<-EOT #!/bin/bash set -e echo "***** Installing Nginx *****" apt update apt install -y nginx ufw allow 'Nginx HTTP' systemctl enable nginx systemctl restart nginx echo "***** Installation Complteted!! *****" echo "Welcome to Google Compute VM Instance deployed using Terraform!!!" > /var/www/html/index.html echo "***** Startup script completes!! *****" EOT -> null - name = "my-nginx-webserver" -> null - project = "workshop-demo-34293" -> null - resource_policies = [] -> null - self_link = "https://www.googleapis.com/compute/v1/projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver" -> null - tags = [ - "http-server", ] -> null - tags_fingerprint = "FYLDgkTKlA4=" -> null - zone = "europe-west1-b" -> 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/workshop-demo-34293/zones/europe-west1-b/disks/my-nginx-webserver" -> null - initialize_params { - image = "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-2204-jammy-v20220712a" -> null - labels = {} -> null - size = 10 -> null - type = "pd-standard" -> null } } - network_interface { - name = "nic0" -> null - network = "https://www.googleapis.com/compute/v1/projects/workshop-demo-34293/global/networks/default" -> null - network_ip = "10.132.0.33" -> null - queue_count = 0 -> null - stack_type = "IPV4_ONLY" -> null - subnetwork = "https://www.googleapis.com/compute/v1/projects/workshop-demo-34293/regions/europe-west1/subnetworks/default" -> null - subnetwork_project = "workshop-demo-34293" -> null - access_config { - nat_ip = "34.79.25.220" -> null - network_tier = "PREMIUM" -> null } } - scheduling { - automatic_restart = true -> null - min_node_cpus = 0 -> null - on_host_maintenance = "MIGRATE" -> null - preemptible = false -> null - provisioning_model = "STANDARD" -> null } - shielded_instance_config { - enable_integrity_monitoring = true -> null - enable_secure_boot = false -> null - enable_vtpm = true -> null } } Plan: 0 to add, 0 to change, 1 to destroy. Changes to Outputs: - webserver_ip = "34.79.25.220" -> null 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: Destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver] google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver, 10s elapsed] google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver, 20s elapsed] google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver, 30s elapsed] google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver, 40s elapsed] google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-nginx-webserver, 50s elapsed] google_compute_instance.vm: Destruction complete after 51s Destroy complete! Resources: 1 destroyed.
Hope this blog gives you familiarity with google_compute_instance
and Terraform template rendering.
If you have feedback or questions, please reach out to me on LinkedIn or Twitter