Using Terraform to Bootstrap a Google Cloud Platform Cluster

Marc Rooding
Ramblings of a Dutch dev

--

Don’t you just hate it when you have to execute a set of commands you don’t use that often to get a job done once? Bootstrapping a Google Cloud Platform cluster is surely one of those things. Wouldn’t it be neat if we could provision this automatically? Well, with infrastructure as code, you can!

Terraform: Infrastructure as Code

Terraform is a tool built by HashiCorp that provides a way to build infrastructure as code. Within your code, you can combine a variety of providers. Once you’re done with the code, creating the infrastructure is as simple as running a few commands. Besides the initial bootstrap, Terraform will keep track of the state of your infrastructure. This allows you to change the code and see what changes will be applied before actually changing your infrastructure configuration.

The advantages of using infrastructure as code are that it’s:

  1. Repeatable
  2. Testable
  3. Predictable
  4. Self-documenting

Common Properties

Since we’re writing our infrastructure as code we can also take advantage of variables. We’ll dive right in by declaring a file with common properties that we will re-use in several files.

Create a new file called variables.tf and add the following:

variable "gcloud-region"    { default = "europe-west1" }
variable "gcloud-zone" { default = "europe-west1-b" }
variable "gcloud-project" { default = "sample-project" }
variable "platform-name" { default = "sample-platform" }

Configure the Google Cloud Provider

To authenticate with Google Cloud services a JSON file is required. This credentials file can be downloaded via the Google Developers Console as follows:

  1. Log in to the Google Developers Console and select the project you would like to obtain the credentials file for
  2. Go to the “API Manager” through the hamburger menu on the left-side
  3. Go to the “Credentials overview by clicking on “Credentials” on the left-side
  4. Click the blue “Create credentials” button and select “Service account key” and choose “JSON” as the format.
  5. Click on “Create” to generate and download the key

Let’s configure the Google Cloud Provider in a file called providers.tf:

provider "google" {
credentials = "${file("account.json")}"
project = "${var.gcloud-project}"
region = "${var.gcloud-region}"
}

The important thing to note here is that we’re referencing the Google Cloud service account key that we generated as account.json. For this to work you need to copy the JSON file to the same directory as the Terraform script.

Platform Network Foundation

Our fictional platform will consist of twodifferent clusters: development and production. We want to have one network consisting of two subnetworks; one subnetwork per cluster. To configure the global network create a new file called global.tf and add the following:

resource "google_compute_network" "platform" {
name = "${var.platform-name}"
}

We want to limit access to our network to SSH and HTTP(S). To accomplish this we can create a google_compute_firewall resource:

resource "google_compute_firewall" "ssh" {
name = "${var.platform-name}-ssh"
network = "${google_compute_network.platform.name}"

allow {
protocol = "icmp"
}

allow {
protocol = "tcp"
ports = ["22", "80", "443"]
}

source_ranges = ["0.0.0.0/0"]
}

We apply this firewall rule to the network we just created using ${google_compute_network.platform.name}. We will allow the ICMP protocol and also TCP on port 22, 80 and 443 and this for the quad-zero address 0.0.0.0/0 which basically means all IP-addresses.

To provide easy access to our platform we can easily add a domain name using a google_dns_managed_zone resource:

resource "google_dns_managed_zone" "sample-platform" {
name = "sample-platform-com"
dns_name = "sample-platform.com."
description = "sample-platform.com DNS zone"
}

In the above example we’re setting up a DNS zone for the domain name “sample-platform.com”. Of course, this will only work if you actually set the domain’s nameservers to the nameservers Google provides after you create the DNS zone by applying the Terraform code.

Defining a Cluster

Each cluster will be defined in a separate Terraform file. Although there will most likely be configurational differences, the files will look eerily similar. They will both contain configuration for the cluster itself, the subnetwork and dns record set. Due to this similarity, I’ll only go through the setup for the “dev” cluster.

Let’s start off by creating the subnetwork for this cluster:

resource "google_compute_subnetwork" "dev" {
name = "dev-${var.platform-name}-${var.gcloud-region}"
ip_cidr_range = "10.1.2.0/24"
network = "${google_compute_network.platform.self_link}"
region = "${var.gcloud-region}"
}

Worth noting here is that we specify the URI of the network in which we want to create the subnetwork with ${google_compute_network.platform.self_link}. We also define which range we want our subnetwork to have and in which region we want the subnetwork to be created.

Creating the cluster itself is as simple as specifying:

  1. the network, subnetwork and zone to use
  2. the initial node count
  3. credentials for the Kubernetes web UI
  4. The machine type and image to use for each node
resource "google_container_cluster" "dev" {
name = "dev"
network = "${google_compute_network.platform.name}"
subnetwork = "${google_compute_subnetwork.dev.name}"
zone = "${var.gcloud-zone}"

initial_node_count = 1

master_auth {
username = "admin"
password = "password4"
}

node_config {
machine_type = "n1-standard-1"

oauth_scopes = [
"https://www.googleapis.com/auth/projecthosting",
"https://www.googleapis.com/auth/devstorage.full_control",
"https://www.googleapis.com/auth/monitoring",
"https://www.googleapis.com/auth/logging.write",
"https://www.googleapis.com/auth/compute",
"https://www.googleapis.com/auth/cloud-platform"
]
}
}

Note: due to API limitations, we cannot modify any of these properties after applying the Terraform cluster code. If you do, so Terraform will completely recreate the cluster for you!

Last but not least we want our cluster to be added as a DNS record set so that we can access the web UI using our domain name:

resource "google_dns_record_set" "dev-k8s-endpoint-sample-platform" {
name = "k8s.dev.${google_dns_managed_zone.sample-platform.dns_name}"
type = "A"
ttl = 300

managed_zone = "${google_dns_managed_zone.sample-platform.name}"

rrdatas = ["${google_container_cluster.dev.endpoint}"]
}

Using a google_dns_record_set we add an A DNS-record for “k8s.dev.sample-project.com” with a TTL of 300.

Applying your Terraform code

Having completed the code, we can now instruct Terraform to actually create the infrastructure on Google Cloud.

Let’s first create an execution plan:

terraform plan -out infrastructure.plan

When running the plan command, Terraform will perform a refresh and determine which actions are necessary to achieve the desired state as specified in our code. We save the plan by specifying the -out parameter which ensures that when we run apply only the actions in this plan are executed.

Let’s now run apply to actually create our infrastructure. Note that creating the infrastructure could take a couple of minutes.

terraform apply

Conclusion

We’ve seen how a limited amount of code makes it incredibly easy to create a simple cluster with networking, firewall rules, and DNS on Google Cloud. Even though the Google Cloud provider currently does not support modifying most of the properties of a google_container_cluster I still believe that using Terraform still adds a lot of benefits. It’s definitely not as labor intensive and error prone as doing it manually or through a shell script.

--

--