DigitalOcean - Terraform Modules by example (2)
Introduction
In this post, we described how to create a droplet, project, tag and DNS record on DigitalOcean using Terraform. We’ve shown a regular Terraform script (cfr. Create a single droplet) and we have also show how to do this using modules (cfr. Create a single droplet using Terraform modules). However in that post, we limited ourselves to creating a single droplet, assigned it to a single project and created a single DNS record for that droplet. In the upcoming post, we’ll see how to make it work if we want to create multiple droplets.
Create multiple droplets using regular Terraform files
Imagine we have the following variable ‘servers’:
servers = {
server1 = {
size = "s-2vcpu-2gb"
image = "ubuntu-21-10-x64"
region = "ams3",
tags = ["web", "development"]
},
server2 = {
size = "s-2vcpu-2gb"
image = "ubuntu-20-04-x64"
region = "lon1",
tags = ["web", "staging"]
}
}
We want to create a droplet for each of these entries. We could simply achieve this with a for_each
loop as follows:
resource "digitalocean_droplet" "server" {
for_each = var.servers
name = each.key
image = each.value.image
size = each.value.size
region = each.value.region
ssh_keys = [
data.digitalocean_ssh_key.terraform.id
]
tags = each.value.tags
}
Using provisioners
As explained in this post, when we want to install some software onto each of these servers. We could do so by using provisioners.
# Create directories for deployment scripts
provisioner "remote-exec" {
inline = [
"mkdir -p /tmp/scripts/",
]
connection {
type = "ssh"
user = "root"
private_key = file("ssh_keys/${var.ssh_key}")
host = digitalocean_droplet.server[each.key].ipv4_address
}
}
# Copy Scripts
provisioner "file" {
source = "${local.script_directory}/"
destination = "/tmp/scripts/"
connection {
type = "ssh"
user = "root"
private_key = file("ssh_keys/${var.ssh_key}")
host = digitalocean_droplet.server[each.key].ipv4_address
}
}
provisioner "remote-exec" {
inline = [
"bash /tmp/scripts/software_install.sh"
]
connection {
type = "ssh"
user = "root"
private_key = file("ssh_keys/${var.ssh_key}")
host = digitalocean_droplet.server[each.key].ipv4_address
}
}
The important piece here is the statement host = digitalocean_droplet.server[each.key].ipv4_address
. We are addressing the current server through the [each_key]
. When iterating this will become digitalocean_droplet.server[“server1”] and digitalocean_droplet.server[“server2”]. As such, we assign the IP address to the host attribute of the connection.
However, when we execute this code (e.g. terraform plan), you will notice we receive the following error:
Error: Cycle: digitalocean_droplet.server[“server2”], digitalocean_droplet.server[“server1”]
The problem here is that we are creating a cyclic dependency because we are referring a resource by its name within its own block. This issue (and solution as a matter of fact) is explained here. So the solution to our little problem here is to use:
host = self.ipv4_address
instead of
host = digitalocean_droplet.server[each.key].ipv4_address
Create multiple droplets using modules
Let’s now see how we can achieve the same using Terraform modules. We have covered this already in part 1 of course but in what follows we will focus on creating multiple droplets (rather than a single droplet as we did in part 1).
In essence, we just have to call the module multiple times. We can do this using a for_each
loop (see example below) or using a count
variable. In below example, we are simply looping over the servers variable while assigning the values to the attributes using the each.value
construct.
Then for the DNS records, we are looping over the created droplets using a count variable. Each element is addressed with the count.index
.
module "ubuntu-server" {
source = "./modules/server"
for_each = var.servers
name = each.value.name
image = each.value.image
environment = each.value.environment
tag = each.key
domain_name = var.domain_name
region = each.value.region
ssh_key = var.ssh_key
}
module "terraform-project" {
source = "./modules/project"
project_name = var.project_name
resources = values(module.ubuntu-server)[*].droplet_urn
}
module "server-record" {
source = "./modules/record"
count = length(module.ubuntu-server)
domain_name = var.domain_name
name = values(module.ubuntu-server)[count.index].droplet_name
value = values(module.ubuntu-server)[count.index].droplet_ip_address
}