img2.png

Quick, Pretty and Easy Maintenance Page using Cloudflare Workers & Terraform

Published on August 02, 2020, 00:00 UTC 4 minutes 23470 views

Maintenance pages are a neat way to inform your users that there are operational changes that require downtime. Cloudflare Workers allows you to execute Javascript and serve HTML close to your users and when Cloudflare manages your DNS it makes it easy to create a Worker and let it manage all traffic during a maintenance period. This makes a great solution for smaller teams/applications that do not want to invest time into creating and displaying maintenance pages. Terraform is a tool that enables provisioning infrastructure via code and it integrates great with Cloudflare APIs.

A preview of the final implementation can be found here, and there’s a Terraform module for creating the maintenence page.

Cloudflare Worker

The Worker’s javascript is simple and it is found and explained in depth in this blog post. The snippet forwards the request to the Web Server if the connecting IP is whitelisted, otherwise it returns a html response that you’ve defined which in our case is the maintenance page.

async function fetchAndReplace(request) {

  let modifiedHeaders = new Headers()

  modifiedHeaders.set('Content-Type', 'text/html')
  modifiedHeaders.append('Pragma', 'no-cache')


  //Allow users from trusted into site
  if (white_list.indexOf(request.headers.get("cf-connecting-ip")) > -1)
  {
    //Fire all other requests directly to our WebServers
    return fetch(request)
  }
  else //Return maint page if you're not calling from a trusted IP
  {
    // Return modified response.
    return new Response(maintenancePage, {
      status: 503,
      headers: modifiedHeaders
    })
  }
}

let maintenancePage = `
<!doctype html>
<title>Site Maintenance</title>
<div class="content">
    <h1>We&rsquo;ll be back soon!</h1>
    <p>We&rsquo;re very sorry for the inconvenience but we&rsquo;re performing maintenance. Please check back soon...</p>
    <p>&mdash; Awesome Team</p>
</div>
`

Terraform

We’ll now use Terraform to create the script and to create the worker route. Creating a worker script with Terraform can be done with the cloud_worker_script resource:

resource "cloudflare_worker_script" "this" {
  name    = "maintenance"
  content = file("/maintenance.js")
}

This will create a Cloudflare worker script which we can reference with the cloudflare_worker_route. The route resource will deploy the worker in a zone with a specified pattern:

data "cloudflare_zones" "this" {
  filter {
    name = "hodovic.cc/*"
  }
}

resource "cloudflare_worker_route" "this" {
  zone_id     = lookup(data.cloudflare_zones.this.zones[0], "id")
  pattern     = "hodovi.cc/maintenance/*"
  script_name = cloudflare_worker_script.this.name
}

All users that visit the /maintenance/ path will be shown the maintenance page except the whitelisted IPs.

Terraform Module

The solution presented above is neat, although we’d like a resuable module. Therefore, I’ve created a Terraform module that does the creation and deployment of a Cloudflare Worker. However, for the module to be reusable we need dynamic html content as:

  • Logo
  • Font
  • Company/Team name
  • Favicon
  • Email address for support

Cloudflare supports key/value storage which we can define in the Terraform module. We’ll adjust the previous cloudflare_worker_script to add the key/value storages:

resource "cloudflare_worker_script" "this" {
  name    = "maintenance"
  content = file(format("%s/maintenance.js", path.module))

  plain_text_binding {
    name = "COMPANY_NAME"
    text = var.company_name
  }

  plain_text_binding {
    name = "WHITELIST_IPS"
    text = var.whitelist_ips
  }

  plain_text_binding {
    name = "LOGO_URL"
    text = var.logo_url
  }

  plain_text_binding {
    name = "FAVICON_URL"
    text = var.favicon_url
  }

  plain_text_binding {
    name = "FONT"
    text = var.font
  }

  plain_text_binding {
    name = "EMAIL"
    text = var.email
  }
}

We can create a HTML snippet and pass the environment variables:

let maintPage = (company_name, logo_url) => `
<body>
    <div class="content">
        <img class="logo" src="${logo_url}" alt="${company_name}">
        <div class="info">
            <h1>Our site is currently down for maintanence</h1>
            <p>We apologize for any inconveniences caused and we will be online as soon as possible. Please check again in a little while. Thank you!</p>
            <p>&mdash; ${company_name}</p>
        </div>
        <img class="image-main" src="https://i.imgur.com/0uJkCM8.png" alt="Maintenance image">
        <hr />
        <a href="mailto:${email}?subject=Maintenance">You can reach us at: ${email}</a>
    </div>
</body>
`;

We’ll also create dynamic CSS for fonts:

body {
    text-align: center;
    font-family: "${font}", sans-serif;
    color: #0C1231;
}

The html/css snippets are more complex and dynamic and the full source code can be found on Github.

The module supports several variables:

  • Logo
  • Font (Google fonts)
  • Email
  • Team name
  • Whitelisting IPs

You can create it using Terraform’s module:

module "hodovi_cc_maintenance" {
  source          = "git::[email protected]:adinhodovic/terraform-cloudflare-maintenance.git?ref=v0.1.3"
  enabled         = false
  cloudflare_zone = "hodovi.cc"
  pattern         = "hodovi.cc/maintenance/*"
  company_name    = "HoneyLogic"
  email           = "[email protected]"
  font            = "Poppins"
  logo_url        = "https://s3.eu-west-1.amazonaws.com/honeylogic.io/media/images/Honeylogic-blue.original.png"
  favicon_url     = "https://s3.eu-west-1.amazonaws.com/honeylogic.io/media/images/Honeylogic_-_icon.original.height-80.png"
}

A preview of the maintenance page can be found here, fully adjustable to your team needs!

Related Posts

Kickstarting Infrastructure for Django Applications with Terraform

When creating Django applications or using cookiecutters as Django Cookiecutter you will have by default a number of dependencies that will be needed to be created as a S3 bucket, a Postgres Database and a Mailgun domain.

Creating Awesome Alertmanager Templates for Slack

Prometheus, Grafana, and Alertmanager is the default stack when deploying a monitoring system. The Prometheus and Grafana bits are well documented, and there exist tons of open source approaches on how to make the best use of them. Alertmanager, on the other hand, is not highlighted as much, and even though the use case can be seen as fairly simple, it can be complex. The templating language has lots of features and capabilities. Alertmanager configuration, templates, and rules make a huge difference, especially when the team has an approach of ‘not staring at dashboards all day’. Detailed Slack alerts can be created with tons of information, such as dashboard links, runbook links, and alert descriptions, which go well together with the rest of a ChatOps stack. This post goes through how to make efficient Slack alerts.

Creating templates for Gitlab CI Jobs

Writing Gitlab CI templates becomes repetitive when you have similar applications running the same jobs. If a change to a job is needed it will be most likely needed to do the same change in every repository. On top of this there is a vast amount of excess YAML code which is a headache to maintain. Gitlab CI has many built-in templating features that helps bypass these issues and in addition helps automating the process of setting up CI for various applications.

Shynet