Deploy Web Server on Google Compute Engine (GCE) with Terraform
With the help of Terraform, this tutorial demonstrates how to deploy a web server on Google Compute Engine, step-by-step.
Join the DZone community and get the full member experience.
Join For FreeIn 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 a Compute Instance
startup
script. - Rendering a template in Terraform.
Prerequisites
This post assumes the following:
- We already have a GCP Project with a network. By default, every GCP Project comes with a
default
network. - Google Cloud SDK (
gcloud
) andTerraform
is setup on your workstation. If you don’t have this, 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 aTerraform Google Provider.
vi provider.tf
This file has the following content:
# Specify the GCP Provider provider "google" { project = var.project_id region = var.region }
Write this Terraform code to create a Google Compute VM Instance.
vi vm.tf
To use the latest
debian
disk, we can use the data source:data "google_compute_image" "debian" { family = "ubuntu-1804-lts" project = "gce-uefi-images" }
# Creates a GCP VM Instance. 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.debian.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 put thehttp-server
tag on the VM astags = ["http-server"]
.Now, let's define a template file which has a script to install NGINX server and create a simple webpage
index.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 the 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-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" = "test" "team" = "devops" "application" = "webserver" }
We now have the required Terraform configuration. So, let’s initialize the Terraform project.
terraform init
Output
Initializing the backend...
Initializing provider plugins...
- Checking for available provider plugins...
- Downloading plugin for provider "google" (hashicorp/google) 3.4.0...
- Downloading plugin for provider "template" (hashicorp/template) 2.1.2...
The following providers do not have any version constraints in configuration,
so the latest version was installed.
To prevent automatic upgrades to new major versions that may contain breaking
changes, it is recommended to add version = "..." constraints to the
corresponding provider blocks in configuration, with the constraint strings
suggested below.
* provider.google: version = "~> 3.4"
* provider.template: version = "~> 2.1"
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.
```
10. After successful initialization, run plan and save plan in a file.
```bash
terraform plan --out 1.plan
Output
Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.
data.template_file.nginx: Refreshing state...
data.google_compute_image.debian: Refreshing state...
------------------------------------------------------------------------
An execution plan has been generated and is shown below.
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)
+ 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" = "test"
+ "team" = "devops"
}
+ machine_type = "f1-micro"
+ metadata_fingerprint = (known after apply)
+ metadata_startup_script = "#!/bin/bash\nset -e\necho \"***** Installing Nginx *****\"\napt update\napt install -y nginx\nufw allow 'Nginx HTTP'\nsystemctl enable nginx\nsystemctl restart nginx\n \necho \"***** Installation Complteted!! *****\"\n \necho \"Welcome to Google Compute VM Instance deployed using Terraform!!!\" > /var/www/html/index.html\n \necho \"***** Startup script completes!! *****\"\n"
+ min_cpu_platform = (known after apply)
+ name = "my-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/gce-uefi-images/global/images/ubuntu-1804-bionic-v20191113"
+ labels = (known after apply)
+ size = (known after apply)
+ type = (known after apply)
}
}
+ network_interface {
+ name = (known after apply)
+ network = "default"
+ network_ip = (known after apply)
+ subnetwork = (known after apply)
+ subnetwork_project = (known after apply)
+ access_config {
+ nat_ip = (known after apply)
+ network_tier = (known after apply)
}
}
+ scheduling {
+ automatic_restart = (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.
------------------------------------------------------------------------
This plan was saved to: 1.plan
To perform exactly these actions, run the following command to apply:
terraform apply "1.plan"
```
11. Plan shows to create a VM instance and use `install_nginx.tpl` as startup script. So, let's go ahead and apply the plan.
```bash
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 15s [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-webserver]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
The state of your infrastructure has been saved to the path
below. This state is required to modify and destroy your
infrastructure, so keep it safe. To inspect the complete state
use the `terraform show` command.
State path: terraform.tfstate
Outputs:
webserver_ip = 35.240.104.9
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 thewebserver_ip
. In this case, go tohttp://35.240.104.9
For cleanup, run
terraform destroy
.
terraform destroy
Output
data.template_file.nginx: Refreshing state...
data.google_compute_image.debian: Refreshing state...
google_compute_instance.vm: Refreshing state... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-webserver]
An execution plan has been generated and is shown below.
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
- deletion_protection = false -> null
- enable_display = false -> null
- guest_accelerator = [] -> null
- id = "projects/workshop-demo-34293/zones/europe-west1-b/instances/my-webserver" -> null
- instance_id = "3519528545052665512" -> null
- label_fingerprint = "k3pYoTAUZq4=" -> null
- labels = {
- "application" = "webserver"
- "environment" = "test"
- "team" = "devops"
} -> null
- machine_type = "f1-micro" -> null
- metadata = {} -> null
- metadata_fingerprint = "mE2Cwt2znPk=" -> null
- metadata_startup_script = "#!/bin/bash\nset -e\necho \"***** Installing Nginx *****\"\napt update\napt install -y nginx\nufw allow 'Nginx HTTP'\nsystemctl enable nginx\nsystemctl restart nginx\n\necho \"***** Installation Complteted!! *****\"\n\necho \"Welcome to Google Compute VM Instance deployed using Terraform!!!\" > /var/www/html/index.html\n\necho \"***** Startup script completes!! *****\"\n" -> null
- name = "my-webserver" -> null
- project = "workshop-demo-34293" -> null
- self_link = "https://www.googleapis.com/compute/v1/projects/workshop-demo-34293/zones/europe-west1-b/instances/my-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-webserver" -> null
- initialize_params {
- image = "https://www.googleapis.com/compute/v1/projects/gce-uefi-images/global/images/ubuntu-1804-bionic-v20191113" -> 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.13" -> 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 = "35.240.104.9" -> null
- network_tier = "PREMIUM" -> null
}
}
- scheduling {
- automatic_restart = true -> null
- on_host_maintenance = "MIGRATE" -> null
- preemptible = false -> 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.
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-webserver]
google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-webserver, 10s elapsed]
google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-webserver, 20s elapsed]
google_compute_instance.vm: Still destroying... [id=projects/workshop-demo-34293/zones/europe-west1-b/instances/my-webserver, 2m30s elapsed]
google_compute_instance.vm: Destruction complete after 2m36s
Destroy complete! Resources: 1 destroyed.
I 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.
Originally published at pbhadani.com
Published at DZone with permission of Pradeep Bhadani. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments