I use Cloudflare as a CDN everywhere I can via multiple domains for the different obsessions of mine.
The Cloudflare web-based GUI is intuitive and very easy to manage though I really hate manual entry of things, web-based or not. I like automation.
I also love Terraform.
Cloudflare and Terraform together make life so much easier.
This module can maintain Cloudflare DNS records and is usable with the following DNS types:
-
- A – IPv4 address
-
- proxy == true
-
-
-
- proxy == false
-
AAAA – IPv6 address
-
-
- proxy == true
-
-
-
- proxy == false
-
CNAME – Canonical name
-
-
- proxy == true
-
-
-
- proxy == false
-
SPF – Sender Policy Framework
TXT – Text record
MX – Mail Exchange records
NS – Name Server records
SRV – Service records
CAA – Certification Authority Authorization records
Later versions of this module will also allow for LOC records.
Here’s a very basic example of the module to create A
and AAAA
records through Cloudflare’s network:
module "cloudflare_dns_records" { source = "git::https://gitlab.com/geekandi-terraform/cloudflare-dns-record-module.git" domain = "example.com" # name, value, priority (integer), type, proxied multi_records = [ "www" , "192.0.2.1", 0, "A", "true"], "example.com", "192.0.2.1", 0, "A", "true"], "www" , "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"], "example.com", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"], ] }
That looks easy. It is not very maintainable.
Let’s do this again but this time let’s use a vars
file to allow for easier maintenance!
Let’s start with my example.com.tfvars
file:
domain = "example.com" # name, value, priority (integer), type, proxied multi_records = [ ["www" , "192.0.2.1", 0, "A", "true"], ["example.com", "192.0.2.1", 0, "A", "true"], ["www" , "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"], ["example.com", "2001:0db8:85a3:0000:0000:8a2e:0370:7334", 0, "AAAA", "true"], ["example.com", "mail.example.com", 10, "MX", "false"], ["example.com", "mail2.example.com", 20, "MX", "false"], ] # item, proto, priority (integer), weight (integer), port (integer), target (no trailing dot) srv_records = [ ["_sip", "_tls", 1, 100, 443, "sipdir.online.lync.com"], ["_sipfederationtls", "_tcp", 1, 100, 5061, "sipfed.online.lync.com"], ] # name, flags, tag, value caa_records = [ ["www", "0", "issue", "comodoca.com"], ["www", "0", "issue", "digicert.com"], ["www", "0", "issue", "globalsign.com"], ["example.com", "0", "issue", "comodoca.com"], ["example.com", "0", "issue", "digicert.com"], ["example.com", "0", "issue", "globalsign.com"], ]
And our new Terraform file:
module "cloudflare_dns_records" { source = "git::https://gitlab.com/geekandi-terraform/cloudflare-dns-record-module.git" domain = var.domain zone_id = data.cloudflare_zones.active.zones[0].id multi_records = var.multi_records srv_records = var.srv_records caa_records = var.caa_records }
Why is this easier? Because now, with a bit of effort on state storage, you can support each domain in a consistent fashion within Cloudflare by using Terraform and multiple vars
files. I would suggest looking into using Terraform Workspaces with durable storage via S3.
Using a workspace
set up you can do fun things using a S3 backend for the state file yet continue to keep good separation between your different domains.
Here’s a quick example script that forces the issue and I even left something for you, the reader, to finish.
#!/bin/sh OPERATION=$1 WORKSPACE=$2 if [ -z "${WORKSPACE}" ] ; then echo "workspace MUST be passed as the 2nd argument" exit 77 fi if [ -f vars/${WORKSPACE}.vars ] ; then echo "we are going to use ${WORKSPACE}.vars file for input" else echo "vars file: vars/${WORKSPACE}.vars does not exist" exit 78 fi case ${OPERATION} in plan) echo "delivering a plan.." echo "" terraform workspace select ${WORKSPACE} if [ $? -eq 1 ] ; then terraform workspace new ${WORKSPACE} terraform workspace select ${WORKSPACE} fi terraform plan -var-file vars/${WORKSPACE}.vars terraform workspace select default break ;; apply) echo "applying a change.." echo "" terraform workspace select ${WORKSPACE} if [ $? -eq 1 ] ; then echo "no workspace available, did you plan first?" fi terraform apply -var-file vars/${WORKSPACE}.vars terraform workspace select default break ;; destroy) echo "operating on a destroy" break ;; *) echo "are you daft?" exit 78 ;; esac
EDIT 20190306: Converted reusable module to Terraform 0.12
EDIT 20191110: Cloudflare updated their provider and things broke along the way so this has been updated to support the new requirements. You will need to make a data call to get the resulting zone_id that is now required. I have updated the example code below that uses the module and the repository below has an updated README.
I have written a couple of modules but for this post I am only referring to Cloudflare DNS Record Module.
I created a data.tf file to do this for me within my terraform directory:
module data "cloudflare_zones" "active" { filter { name = var.domain } }