Setting up my blog has always been easy. Previous to today, you just had to:
- Provision an Ubuntu 18 VM somewhere (I’ve been using Digital Ocean)
- Install Apache and remove its default website
- Use Hugo to generate the site’s static files and copy them to the VM host
- Done!
But, never being one to sit still, I’ve decided to up my game and move to automatic provisioning with Terraform and Ansible.
Why? Because it’s fucking cool, that’s why!
Here’s my Terraform provisioning script. It will spin up two hosts in Digital Ocean, apply a firewall policy, and then setup a load balancer in front of them to distribute traffic across them.
# Set the variable value in *.tfvars file
# or using -var="do_token=..." CLI option
variable "do_token" {}
# Configure the DigitalOcean Provider
provider "digitalocean" {
token = "${var.do_token}"
}
# Create web servers
resource "digitalocean_droplet" "server1" {
image = "ubuntu-18-04-x64"
name = "blog-server1"
tags = ["blog"]
region = "sfo2"
size = "s-1vcpu-1gb"
ssh_keys = ["762573"]
}
resource "digitalocean_droplet" "server2" {
image = "ubuntu-18-04-x64"
name = "blog-server2"
tags = ["blog"]
region = "sfo2"
size = "s-1vcpu-1gb"
ssh_keys = ["762573"]
}
# Create firewall to control network traffic
resource "digitalocean_firewall" "fw" {
name = "fw-all-the-things-except-some-of-them"
droplet_ids = ["${digitalocean_droplet.server1.id}",
"${digitalocean_droplet.server2.id}"]
inbound_rule {
protocol = "tcp"
port_range = "22"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "tcp"
port_range = "80"
source_addresses = ["0.0.0.0/0", "::/0"]
}
inbound_rule {
protocol = "icmp"
source_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "80"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "tcp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "udp"
port_range = "53"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
outbound_rule {
protocol = "icmp"
destination_addresses = ["0.0.0.0/0", "::/0"]
}
}
# Create load balancer to spread traffic across web servers
resource "digitalocean_loadbalancer" "lb" {
name = "blog-lb"
region = "sfo2"
forwarding_rule {
entry_port = 80
entry_protocol = "http"
target_port = 80
target_protocol = "http"
}
healthcheck {
port = 80
protocol = "http"
path = "/"
}
droplet_ids = ["${digitalocean_droplet.server1.id}",
"${digitalocean_droplet.server2.id}"]
}
# Attach resources to project
resource "digitalocean_project" "blog_project" {
name = "My Blog"
description = "www.jamessimas.com"
purpose = "Website or blog"
environment = "Production"
resources = ["${digitalocean_droplet.server1.urn}",
"${digitalocean_droplet.server2.urn}",
"${digitalocean_loadbalancer.lb.urn}"]
}
Prepping the hosts for Hugo can be done with the following Ansible playbook:
# This playbook configures an Ubuntu VM:
# - Installs Python dependencies so that Ansible works
# - Installs Apache and other dependencies
---
- hosts: apache
remote_user: root
gather_facts: False
pre_tasks:
- name: Install python2
raw: apt-get update && apt -y install python-simplejson
tags: python
- hosts: apache
remote_user: root
gather_facts: True
tags: apache
tasks:
- name: Install packages via apt
apt:
name: "{{ packages }}"
vars:
packages:
- apache2
- name: Remove Apache default website files
file:
path: /var/www/html/
state: absent
- name: Create an empty directory
file:
path: /var/www/html/
state: directory
- name: Setup regular security updates
copy:
src: update-ubuntu
dest: /etc/cron.weekly/update-ubuntu
owner: root
group: root
mode: '0774'
I can now tear down and re-provision my entire blog within a few minutes using this orchestration.
If you’re curious how I did it, check out the source code here.