Ekin Öcalan
Posted on August 17, 2020
Terraform Series
- Part 1: Introduction
- Part 2: Creating the Server
- Part 3: Provisioning the Server
- Part 4: Managing Terraform State
- Part 5: Cleaner Code with Terraform Modules
- Part 6: Loops with Terraform
- Part 7: Conditionals with Terraform
- Part 8: Testing Terraform Code
Now that we could create a server, it's time to configure it a bit. It's no use to us if it sits right there. So let's install nginx and then have one of our domains point to the server to see if it's working or not.
Provisioning the server
It's only to install nginx for now, but it's still a server provision. We are going to use remote-exec provisioner to achieve this simple install.
Within digitalocean_droplet
resource, add the provisioner below:
resource "digitalocean_droplet" "web" {
image = "ubuntu-20-04-x64"
name = "terraform-sandbox"
region = "ams3"
size = "s-1vcpu-1gb"
provisioner "remote-exec" {
inline = [
"export PATH=$PATH:/usr/bin",
# install nginx
"sudo apt-get update",
"sudo apt-get -y install nginx"
]
}
}
There are better ways to provision a server, but we'll get to that later. For our simple nginx install, this would do the trick.
Still, there is one thing missing here. How is Terraform going to connect to our server to run the bash script above?
Granting Terraform the SSH access
You first need to go to your Account/Security page on DigitalOcean and add your computer's SSH key there. If you don't know how to do it, DigitalOcean helps you to create and use an SSH key on that page. You're going to need the SSH key fingerprint shown on that page.
Now we are going to create a new Terraform file and call it variables.tf
.
variable "public_key" {}
variable "private_key" {}
variable "ssh_fingerprint" {}
You see how we create variables in Terraform. Typically, you can give them default values as well. However, since they contain sensitive data, we deliberately left them empty. This way, Terraform will ask us to fill them while planning or applying.
We head back to our main.tf
now, and add the ssh_keys
parameter inside our droplet resource.
resource "digitalocean_droplet" "web" {
image = "ubuntu-20-04-x64"
name = "terraform-sandbox"
region = "ams3"
size = "s-1vcpu-1gb"
ssh_keys = [
var.ssh_fingerprint
]
provisioner "remote-exec" {
inline = [
"export PATH=$PATH:/usr/bin",
# install nginx
"sudo apt-get update",
"sudo apt-get -y install nginx"
]
}
}
This addition will grant the SSH key holder to access our droplet. We provided a list of ssh_keys
. It means that you can add more than one. Additionally, it also shows how we can access variables defined within the Terraform files. Hint: var.ssh_fingerprint
, var.public_key
, etc.
Lastly, we are going to define a connection
within the droplet resource.
resource "digitalocean_droplet" "web" {
image = "ubuntu-20-04-x64"
name = "terraform-sandbox"
region = "ams3"
size = "s-1vcpu-1gb"
ssh_keys = [
var.ssh_fingerprint
]
connection {
host = self.ipv4_address
user = "root"
type = "ssh"
private_key = file(var.private_key)
timeout = "2m"
}
provisioner "remote-exec" {
inline = [
"export PATH=$PATH:/usr/bin",
# install nginx
"sudo apt-get update",
"sudo apt-get -y install nginx"
]
}
}
This connection declaration defines an SSH connection. The host is the IP address of our droplet. Notice how we used the self
object there. It's going to connect with the root
user, using the private key of our computer.
Our main.tf
is ready to create a droplet, and then make a connection to it to install nginx. Now, instead of a plain terraform apply
, we need to do it with populating the variables as well:
$ terraform apply \
-var "public_key=$HOME/.ssh/id_rsa.pub" \
-var "private_key=$HOME/.ssh/id_rsa" \
-var "ssh_fingerprint=YOUR_FINGERPRINT"
You need to use your SSH key fingerprint instead of YOUR_FINGERPRINT. And please also note that the public_key
and private_key
values may be different based on how you created them.
When you run the command, it's going to show you the plan and ask for your approval. You won't see the provisioner in the plan. But when you apply itthe plan, you'll see how Terraform makes the connection to the droplet, and you'll see the logs of the provisioner script.
Pointing a domain to the server
We have a server, and we have nginx running on it. You can visit the IP address of your server to see it with your eyes. However, let's move one step ahead and use a domain instead of the IP address.
Create a domain.tf
file and populate it with:
resource "digitalocean_domain" "yourdomain" {
name = "yourdomain.com"
ip_address = digitalocean_droplet.web.ipv4_address
}
resource "digitalocean_record" "cname_www" {
domain = digitalocean_domain.yourdomain.name
type = "CNAME"
name = "www"
value = "@"
}
First, we create a digitalocean_domain
resource. You need to update the name with your domain address. Notice how we used the ipv4_address
attribute to point our domain to a specific IP address. You can see the full list of attributes reference on Terraform Registry. The same way we access the IP address here, you can access any attribute from digitalocean_droplet.web
.
Second, we create a digitalocean_record
resource to point all www
subdomain requests to the main one. Thus, both yourdomain.com and www.yourdomain.com will lead to our droplet. See how we set the domain
parameter inside to digitalocean_domain.yourdomain.name
. That's how Terraform will understand under which domain it should create this record.
Resource order is not important
One interesting fact with Terraform we can mention here is that Terraform doesn't care about the order of the resources. Even if you declare digitalocean_record
resource before the digitalocean_domain
one, it would still work. That's because Terraform is declarative, and it creates its dependency graph based on what you declared.
Back to the domain creation... Now if you apply your plan again:
$ terraform apply \
-var "public_key=$HOME/.ssh/id_rsa.pub" \
-var "private_key=$HOME/.ssh/id_rsa" \
-var "ssh_fingerprint=YOUR_FINGERPRINT"
You'll see a plan just like this:
Terraform will perform the following actions:
# digitalocean_domain.yourdomain will be created
+ resource "digitalocean_domain" "yourdomain" {
+ id = (known after apply)
+ ip_address = "111.111.111.111"
+ name = "yourdomain.com"
+ urn = (known after apply)
}
# digitalocean_record.cname_www will be created
+ resource "digitalocean_record" "cname_www" {
+ domain = "yourdomain.com"
+ fqdn = (known after apply)
+ id = (known after apply)
+ name = "www"
+ ttl = (known after apply)
+ type = "CNAME"
+ value = "@"
}
Plan: 2 to add, 0 to change, 0 to destroy.
Apply the plan, and that's all. Now if you visit your domain, you'll see your that nginx's default site configuration will catch your request and show you its default page:
PS: Cover photo by tian kuan
Part 2.........................................................................................................Part 4
Posted on August 17, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.