Provisioning Azure Functions Using Terraform
The Azure Portal is an intuitive web app to manage cloud resources. It is so easy to use that sometimes I feel like I have years of Azure experience. As every good web app, it keeps evolving and simplifying our work, but this constant evolution makes the life of writers like me very hard. If I try to give detailed instructions about the steps to create resources there, it will be obsolete in a matter of weeks. Yet, if I don’t explain the cloud side of my articles, they end up being incomplete, frustrating my readers. So, how can I possibly solve this problem? Long story short: Infrastructure as Code.
Short story long: A way to be precise about cloud infrastructure while keeping articles useful for longer is to use code. This is the thing in technology that takes longer to become obsolete. Good programming languages evolve over time but also preserve backwards compatibility, making sure that old software still compiles in modern technology. When it is time break compatibility they try to do it gracefully, giving developers time to adapt.
Using code to build infrastructure is a practice known as “Infrastructure as Code”. Cloud providers have been serving APIs to create and maintain resources for years now. But some clever people out there have built tools that take care of the hard part and make available domain-specific languages to better describe cloud resources. The most popular tools out there are Ansible and Terraform. Ansible is cool, but I’m going to work with Terraform because it is written in Go, a language that I’m particularly passionate about.
We have provisioned some resources when Deploying an Azure Function in Go, using Azure CLI. We created a resource group, a storage account, and a function app. Behind the scene, it also created an application service plan and an application insights. We can do the same using Terraform. The subfolder /terraform
in this repo contains the scripts that demonstrate that. Let’s start explaining the main.tf
file:
terraform {
required_providers {
azurerm = {
source = "hashicorp/azurerm"
version = ">= 2.26"
}
}
}
provider "azurerm" {
features {}
}
resource "azurerm_resource_group" "rg" {
name = var.resource_group_name
location = var.location
}
resource "azurerm_storage_account" "sa" {
name = var.storage_account_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
account_tier = "Standard"
account_replication_type = "LRS"
account_kind = "StorageV2"
allow_blob_public_access = false
enable_https_traffic_only = true
}
resource "azurerm_app_service_plan" "asp" {
name = var.app_service_plan_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
kind = "functionapp"
reserved = true
sku {
size = "Y1"
tier = "Dynamic"
}
}
resource "azurerm_application_insights" "appinsights" {
name = var.app_insights_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
application_type = "web"
}
resource "azurerm_function_app" "function" {
name = var.function_name
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
app_service_plan_id = azurerm_app_service_plan.asp.id
storage_account_name = azurerm_storage_account.sa.name
storage_account_access_key = azurerm_storage_account.sa.primary_access_key
https_only = true
os_type = "linux"
version = "~3"
app_settings = {
"FUNCTIONS_WORKER_RUNTIME" = "custom"
"APPINSIGHTS_INSTRUMENTATIONKEY" = azurerm_application_insights.appinsights.instrumentation_key
}
}
This is written in the Terraform Configuration Language. It is using the Azure Resource Manager (azurerm), an extension that speaks with Azure’s APIs. It creates 5 resources mentioned above, leaving everything ready to deploy the function. Notice that we have some hardcoded values that are essential for the Go function and some variables prefixed with “var.”. These variables are defined in the file variables.tf
, as you can see below:
// https://azure.microsoft.com/en-ca/global-infrastructure/geographies/
variable "location" {
description = "Name of the location where the resources will be provisioned"
type = string
}
variable "resource_group_name" {
description = "Name of the resource group"
type = string
}
variable "storage_account_name" {
description = "Name of the storage account"
type = string
}
variable "app_service_plan_name" {
description = "Name of the application service plan"
type = string
}
variable "app_insights_name" {
description = "Name of the application insights"
}
variable "function_name" {
description = "Name of the function"
type = string
}
These variables are what need to change between my environment and yours. So, you can reuse this script as long as you define unique values for these variables. We can do that by assigning values to the variables with a .tfvars
file. My values are defined in the file env.tfvars
.
location = "eastus"
resource_group_name = "buyersmarket"
storage_account_name = "buyersmarketstore"
app_service_plan_name = "buyersmarketasp"
app_insights_name = "buyersmarket"
function_name = "buyersmarket"
Make sure the files main.tf
, variables.tf
, and env.tfvars
are together in a subfolder of your project. To run this code, Terraform needs to be installed and available in the command line. Download it from the Terraform website and follow the instructions for your operating system. In the command line, go to the folder where the scripts are located and initialize it:
$ cd azure/function/terraform
$ terraform init
The initialization install the dependencies required by our script. Next, Terraform uses Azure CLI to authenticate to Azure. So, make sure you are authenticated:
$ az login
Once authenticated, we are ready to compare what is defined in our scripts with what we have on Azure. We do it with the plan
argument:
$ terraform plan -var-file=env.tfvars
This command lists a detailed description of everything that will be created on Azure without actually creating it. Review it and if everything looks good, apply it:
$ terraform apply -var-file=env.tfvars
Once applied, the function is ready to be deployed, as we did when Deploying an Azure Function in Go:
$ cd azure/function
$ func azure functionapp publish buyersmarket
After a few seconds, call the URL:
$ curl 'https://buyersmarket.azurewebsites.net/api/offer?savings=134507&listingPrice=700000&downPayment=10&closingCosts=17000'
If you are just playing or don’t need the resources anymore, just destroy them:
$ terraform destroy -var-file=env.tfvars
I have to admit that preparing these scripts is a lot more work than running a couple of Azure CLI commands, but doing it with Terraform has some advantages:
-
The infrastructure as code is described in details. We can document and even explain why we made those architectural decisions without relying on diagrams that becomes obsolete very quickly.
-
The same script can be used to create several environments, such as development, test, and production, by simply referring to the corresponding
.tfvars
file. -
Changes to the infrastructure can be versioned. This is important to preserve the technical decisions made over time and even revert bad decisions. Versioning also shares the scripts with a continuous integration service that can propagate changes in several environments.
-
We are working with a simple example here, but the infrastructure can become very complex over time. So, using command line instructions might be unmanageable with a feel more resources. Terraform allows reusing and redefining values, which is hard to do in command line instructions.
The source code of the Azure Resource Manager is available on Github. Have fun with the examples there.
Recent Posts
Can We Trust Marathon Pacers?
Introducing LibRunner
Clojure Books in the Toronto Public Library

Once Upon a Time in Russia

FHIR: A Standard For Healthcare Data Interoperability

First Release of CSVSource

Astonishing Carl Sagan's Predictions Published in 1995

Making a Configurable Go App

Dealing With Pressure Outside of the Workplace

Reacting to File Changes Using the Observer Design Pattern in Go

Taking Advantage of the Adapter Design Pattern

Applying The Adapter Design Pattern To Decouple Libraries From Go Apps

Using Goroutines to Search Prices in Parallel

Applying the Strategy Pattern to Get Prices from Different Sources in Go
