From Terraform in Action by Scott Winkler
This article discusses how Terraform can make serverless infrastructure easy and painless to use.
The “Two Penny Website”
This scenario is something I like to call “the two-penny website”, because this is how much I estimate it costs to run on a monthly basis. If you can scrounge some coins from between the seat cushions, you’ll be good for at least a year or two of web hosting. Not to mention (I’m probably overestimating a bit); for most low traffic applications, the true cost is likely to be a fraction of one penny. Because serverless is based on the premise of pay-as-you-go, it’s difficult to estimate ahead of time exactly how much it will cost. Perhaps, if you have a popular website, you might be charged five cents rather than two cents. Nevertheless, I’m sure you agree that serverless has the ability to be comically affordable, and by the end of this article, you won’t have any excuse not to have some sort of web presence. You can even justify making silly, or single purpose websites, because it’s practically free anyways.
The website we’re going to deploy is a ballroom dancing forum, called “Ballroom Dancers Anonymous”. Unauthenticated users are able to leave comments which are stored in a database and are viewable by other users on the site. The design is fairly simple, but the beauty is that this is generalized to work with a wide variety of different web applications. A sneak peak of the final product is shown in figure 1.
We’ll be using Azure to deploy the serverless website. A basic deployment strategy is shown in figure 2.
Architecture and Planning
Although this website costs only pennies to run, by no means is it a toy. Because it’s deployed on Azure Functions, it’s able to rapidly scale up to handle tremendous spikes in traffic and do this with low latency. It also uses HTTPS, a NoSQL database, and serves both static content (HTML/CSS/JS) as well as a fully-fledged RESTful API. An architecture diagram is shown in figure 3.
Because the code we’ll write’s relatively short and cohesive, I’ve decided to put it all in a single main.tf file, instead of using nested modules.
Tip As a rule of thumb, I suggest having no more than two hundred lines of code per file. Any more, and it becomes more difficult for a reader to build a mental map of how the code works.
The big question is, how should the code be organized to be both easy to read and easy to understand? Organizing code based on the number of dependencies is a tried and true approach. Typically, this means organizing code such that resources having fewer number of dependencies are located toward the top, but resources having greater number of dependencies are located toward the bottom. This leaves a lot of room for ambiguity, like when resources have the same number of dependencies, but don’t feel like they belong together.
Instead of strictly organizing your code by the number of dependencies each resource has, I recommend grouping related resources together and sorting them by group (i.e. sorting first by group, then by resource). This can be visualized in figure 4.
In the same way that it’s quicker to search for a word in a dictionary than in a crossword puzzle, it’s faster to find what you’re looking for when your code’s organized in a sensible manner (such as the pattern shown above). Although Terraform won’t necessarily operate on the code synchronously — due to the complex way that Terraform walks the dependency graph — the goal is that someone reading your code from top-to-bottom should be able to completely understand how it works without having to think too hard.
For this project, I’ve divided it into four main groups of resources, each one serving a particular purpose. These groups are:
- Resource Group — the resource group and other base level resources are created first, as they are required by many downstream resources but have no resource dependencies of their own
- Storage container — the storage container is used to store the build artifact (or source code) for Azure Functions, as well as the records of the NoSQL database
- Storage blob — the storage blob is a binary file containing the build artifact that Azure Functions App needs to be able to run. This is a two-step process: we first need to download the artifact before we can upload it.
- Azure Functions App — the Azure Functions app needs to be created and configured. Anything related to achieving this goal’s considered part of this step.
This can be pictured in figure 5.
Note Because we’ll be working through these groups one at a time, I’ll use the word “step” interchangeably with the word “group”
The last part of planning we need to do is consider the overall inputs and outputs of the root module. From a black box perspective, there are three inputs and one output. Two of the inputs are used for configuring the provider/base resources (client_secret and region) and the third is for affording a consistent naming scheme (namespace). The sole output value we’ll have is website_url, which is a live link to the final deployed website. This can all be visualized in figure 6.
If you’re anything like me, you might be thinking to yourself “it’s great you’re showing me how to do all this, but there’s no way I could possibly have come up with this on my own.” When writing this book, I didn’t want it to be another cookbook of copy-paste snippets. Terraform can be used in many different ways and there’s no possible way I could cover every potential use case. Instead, I wanted to give you a set of tools and thought processes to use to solve your own problems. Here’s a list of steps that I take when tackling a new problem with Terraform:
- Define the problem and goals
- Research potential solutions, while keeping an open mind
- Select key technologies and tools to use
- Build a prototype to determine if further investment’s warranted
- Develop final product
For this particular scenario, I knew my goal was that I wanted to deploy a serverless website on Azure, but I haven’t used Azure much before (I’m more of an AWS guy). I had to do a lot of research to figure out what services were available and how they could best be used. Then I selected Azure Functions and Azure Storage because those both seemed to fit my requirements. Afterwards, I built a quick and dirty prototype using the UI editor, before finally sitting down to translate this all into clean and working Terraform configuration code. It looks neat and tidy only if you ignore all the dead-ends I went through before settling on the final design.
Tip problem solving is an art, and the only way to get better is with practice.
Check out more great free content from our titles on our Free Content Center.