Serverless on Azure for beginners, with Cloudflare and Terraform

Photo by Kevin Jarrett on Unsplash

Goal:

Create a serverless app on Azure for beginners.

Using a custom domain, TLS encryption, and Terraform for infrastructure as code.

Why?

The idea of running our own web servers, sizing VMs and patching OSes seems so 2005. For simple web apps, and seeing if our new service will stick, we want hosting that is as low-cost as possible, but we also want the ability to scale elastically for when we get picked up on Hacker News.

We also want to try some of the new Azure Terraform resources and take Azure Container Instances for a spin. I’ll show you how to run an API with a long-running Docker container (~ $2/day), as well as using an Azure Function for ultimate low-cost (pay per-request).

How?

In this example, we’ll use Azure for our storage and compute, layering CloudFlare on top for TLS encryption to the client. We’ll manage this whole multi-provider stack with one terraform configuration, practicing what we preach with Infrastructure as Code.

The code can be found here if you want to reproduce: github.com/dcolebatch/azure_serverless_example

So let’s unpack this!


Prerequisites

Before we start, you need the following:

  1. A domain name. We used freenom.com in this example, and created serverlessexample.ga
  2. git for source control
  3. Ensure you have the Azure CLI utilities
    On OSX with Homebrew: brew install azure-cli
  4. Login to Azure on the command line: az login
  5. Ensure your Default Azure subscription is what you want to use for this. See great instructions in terraform docs here.
  6. Clone the sample code repo: 
    git clone git@github.com:dcolebatch/azure_serverless_example.git
  7. Initialize your terraform, which downloads any providers we depend on: terraform init

Great, now you’re ready to tweak our terraform to your liking and see what affects your changes will have using the terraform plan command.

Starting From The Bottom

Our simple web application is comprised of some static HTML and JavaScript. The JavaScript makes an asynchronous call to a small web service which serves up a random URL to a cat image and this is included in the page.

Simple architectures are the easiest to manage :)

This pattern works just as well with a more complex frontend application. For single page applications (SPAs), we can write it with React or Angular and setup our deploy pipelines to upload the assets to the blob store. A cheap and cheerful way to accomplish this is: 
GitLab CI — to — > Azure

Our web service is the highly sought after random-cats service, pre-built on dockerhub for your convenience. We can deploy this by using a few lines of Terraform:

ACI was simple to use, but it’s not the most cost-effective for something that’s expected to get only periodic requests. So, I will end up refactoring this to an Azure Function with a GitHub deployment hook.

With the web UI and API components in place, we now need some DNS and CDN magic to make it user-friendly.

Application Publication Using Cloudflare

This is now the simplest part of the stack, but also requires a few manual steps before we can use terraform apply. With your new domain name registered, head over to cloudflare.com and create an account (it’s free!).

Enter your domain name to create a “zone” and get your allocated DNS Servers. You’ll need to configure these where you registered your domain name. For us on freenom.com, it looked like this:

Be sure to enter your cloudflare DNS servers where you registered your DNS name

Once this is configured and propagated, DNS for your domain is now managed by Cloudflare and we can use terraform’s Cloudflare provider to manage our DNS entries.

For this to work, we need to get our API key from your Cloudflare profile. Head over there and scroll down to the “View API Key” button.

You’ll be prompted for your password again to confirm that it’s you.

Be sure to treat this API Key like a password, because that’s what it is. For this reason, never commit it to a git repo and for this project, only set it via the environment variables mentioned in setup/env_vars.sh.

NB: We also tested this setup with Azure CDN Premium (Verizon) and you can see the difference between the two terraform configs in github. However, the Azure API will return an error message with a link to a third-party configuration portal. See Gotchas below.


Make It Go!

If you have tweaked setup/main.tf to match your domain name, you can now execute!

terraform apply

Terraform will now interrogate Azure and Cloudflare to update your “state file”, and present you with all the resources it’s going to change. First time around, these should all be green “New” resources to add.

Take a look at these resources, and if it looks good, type yes to apply them.

After some API calls to Azure Resource Manager and Cloudflare, terraform should report back that it all deployed and your outputs should be similar to:

You’re done!

Navigate to the frontend_endpoint from your terraform output, and you should see this site.

A category killer for this year’s webby ? :)

Gotchas

There are a couple of gotchas to using this setup that I’m confident will be improved in future releases of Azure Resource Manager and corresponding Terraform Azure provider resources.

1. Watch your MIME Types on the Blobs

We can’t currently set file MIME types on blobs via terraform, which means Azure serves them to us as “application/octet” and the browser thinks index.html is to be downloaded, not rendered. Damn.

As a workaround, re-upload the files using Azure Storage Explorer which will correctly set the file type.

2. No API for Azure CDN Premium (Verizon)

Unfortunately, the terraform resource for this only creates the shell of the resource in the Azure portal and cannot manage its configuration, or it’s state. Terraform will complain about this resource with each invocation, and ask you to follow a link in the portal to a separate configuration portal.

This configuration portal is managed by Verizon, and unfortunately configuring custom domain names here requires service requests (tickets), emails and wait times. This is an anti-pattern (long feedback loop) that I’m confident will be addressed by future releases.

3. No HTTPS for HTTP origins in Azure CDN Premium (Verizon)

Snag two for the CDN service: You can’t actually configure an HTTPS enabled endpoint for an origin service that doesn’t itself offer HTTPS. Sadly for our random-cats service, encryption is not included. While the UI in the portal may seem like you can configure an HTTPS endpoint to an HTTP Origin, Microsoft and Verizon support confirmed that this is not possible.

4. No clear path to deploy a function app code via terraform

We can create the function app with terraform, but configuring and deploying code from a RepoUrl looks like something we have to revert to ARM templates for, for now. The quickest path for deploying something simple like this, is right in the Azure Portal like so:

Reusing our service from the Docker image in Azure Functions

In summary, we have explored a few different approaches to deploying sites and looked at a couple of lesser-known technologies that are available to us on Azure.

I hope you found this a useful starting point for your Azure project, and we’ll be sure to post updates as the Azure support in terraform continues to improve (235 commits in the last month alone!).

Edit: The same day I published this, Cloudflare added 6 new terraform resources, allowing even more of their service to be managed by terraform. The space is moving fast, so if you’ve looked at a terraform provider six months ago and passed, I’d encourage you to take another look.

-David Colebatch
Chief Migration Hacker, Tidal Migrations


Other resources you may find useful on this journey…

You can compare the few scenarios we tested with a git-diff of branches. See the DIFFerence between:

Azure Blob Storage

Basic steps for Blob storage with a CNAME (no HTTPS):
https://docs.microsoft.com/en-us/azure/storage/blobs/storage-custom-domain-name

Working with the root-container in blob storage:
https://docs.microsoft.com/en-us/rest/api/storageservices/working-with-the-root-container

Azure CDN Related

Using the Azure CDN to access blobs with custom domains over HTTPS:
https://docs.microsoft.com/en-us/azure/storage/blobs/storage-https-custom-domain-cdn

Azure CDN Rules engine required if you want to redirect HTTP to HTTPS:
https://docs.microsoft.com/en-us/azure/cdn/cdn-rules-engine

For default serving of index.html when using Azure CDN Premium:
https://docs.microsoft.com/en-us/azure/cdn/cdn-rules-engine-reference-match-conditions#url-query-literal

Azure CDN Rules engine specs:
https://docs.microsoft.com/en-us/azure/cdn/cdn-rules-engine