The sample shown automated reproducable Azure Functions App provisioning and deployment using Terraform.
Terraform is infrastructure as code software tool that provides a consistent CLI workflow to manage hundreds of cloud services. Terraform codifies cloud APIs into declarative configuration files
.tf
.
Copy and rename .env.sample
to .env
and terraform.sample.tfvars
to terraform.tfvars
, provide your variables.
Read State Backend
before initiating terraform.
make init
Make sure that az login
was previously established.
The project uses Azure Backend for storing Terraform state.
Before starting, copy .env.sample
to .env
and provide a valid TERRAFORM_BACKEND_STORAGE_ACCOUNT
. The account should be created in your infrastructure first:
- Create resource group:
Terraform
- Add storage account (which name goes to
TERRAFORM_BACKEND_STORAGE_ACCOUNT
) - Create
tfstate
container with private access
Then run init command.
To use local state, comment backend "azurerm" {}
in 01_main.tf
model.
Terraform .tf
files' content composition mostly doesn't matter much until you feel comfortable with what is where and how structured for reusability or maintenance robustness. It can be all in one file on domain areas split between multiple .tf
s.
01_main.tf
- main Terraform file, sort of an entry point. I used to place the provider, resource group, and network stanzas here.02_storage.tf
- storage account is a pillar of an Azure Functions app. Here storage is combined with deployment via storage container blobs artifacts.03_functions.tf
- functions app and service plan definitions, data automation outputs (such as provisioned functions hostname and function key).
variables.tf
- dynamic input variables definition.terraform.sample.tfvars
- sample variables file, ignored. For simpler copy & paste intoterraform.tfvars
.terraform.tfvars
- variables values files. Should not be included in the repo, might contain private data and secrets. The variables also can be provided with command line arguments.
Makefile
- automation commands aliases.- Miscellaneous:
.terraform.lock.hcl
- dependencies lock file used withterraform init
..terraform/
folder - Terraform provider's binaries. Aka "node_modules/" of terraformation. Might have tangible size.terraform.tfstate
- remote state sync file.package/functions.zip
- custom handler deployment package produced withmake pack
command.
Aka "npm install" for terraform providers. Initiates Terraform project and downloads missed provisioning providers.
make init
Terraform variables are private dynamic values/inputs which are used in the templates allowing targeting the same tested definitions agains different environments or tenants.
- Copy & rename
terraform.sample.tfvars
toterraform.tfvars
- Provide your variables
subscription_id = "529f730b-4ac8-43b6-9851-221e735a71cf"
location = "westus2"
function_app = "az-fun-go-042"
sharepoint_siteurl = "https://contoso.sharepoint.com/sites/site"
sharepoint_clientid = "428b492b-575d-4d4b-991e-16195a3c496e"
sharepoint_clientsecret = "CgnihMbRphqR....7XLlZ/0QCgw="
Avoid variables commit to Git repository
Plans provisioning, apply stuff in a dry mode. Can be useful for trubleshooting and verification of what a deployment will do. E.g. in some cases a deployment of a resource can't be done without destroying and recreating, which however might be unwanted behavior. With planning, you'd see what property causes such effect.
make plan
Before applying Terraform templates, the Azure Functions package should be created with:
make pack
This will create ./package/functions.zip
, which is used as an app code source deployed to Blobs and bounded with the Azure Functions app via WEBSITE_RUN_FROM_PACKAGE
environment variable and corresponding automation.
make apply
The command is an alias of terraform apply -auto-approve
, won't prompt for confirmation.
Azure Functions and Custom Handlers in Go are now published and ready to use.
make destroy
Don't run this for a prod as all the resources provisioned with Terraform templates will be removed.
I tend to believe and prefer serverless options first approach. Also, I'd chose Linux on a server in all cases when it works.
## 03_functions.tf
# Service Plan
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/service_plan
resource "azurerm_service_plan" "service_plan" {
name = var.function_app
resource_group_name = azurerm_resource_group.rg.name
location = azurerm_resource_group.rg.location
os_type = "Linux"
sku_name = "Y1"
tags = var.tags
}
# Functions App
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/linux_function_app
resource "azurerm_linux_function_app" "functions" {
name = var.function_app
# ...
app_settings = {
FUNCTIONS_WORKER_RUNTIME = "custom" # Custom Handler runtime
# ...
}
}
## 03_functions.tf
resource "azurerm_linux_function_app" "functions" {
name = var.function_app
# ...
app_settings = {
# SharePoint auth binding configuration, see more in a root project
SPAUTH_SITEURL = var.sharepoint_siteurl
SPAUTH_CLIENTID = var.sharepoint_clientid
SPAUTH_CLIENTSECRET = var.sharepoint_clientsecret
# ...
}
}
## variables.tf
variable "sharepoint_siteurl" {
type = string
description = "SharePoint SiteURL"
}
variable "sharepoint_clientid" {
type = string
description = "SharePoint ClientID"
}
variable "sharepoint_clientsecret" {
type = string
description = "SharePoint Client Secret"
}
## terraform.tfvars
sharepoint_siteurl = "https://contoso.sharepoint.com/sites/site"
sharepoint_clientid = "428b492b-575d-4d4b-991e-16195a3c496e"
sharepoint_clientsecret = "CgnihMbRphqR....7XLlZ/0QCgw="
## 03_functions.tf
resource "azurerm_linux_function_app" "functions" {
name = var.function_app
# ...
app_settings = {
# Deployment from storage container blob settings
FUNCTION_APP_EDIT_MODE = "readonly"
HASH = base64encode(filesha256(var.package))
WEBSITE_RUN_FROM_PACKAGE = "https://${azurerm_storage_account.storage.name}.blob.core.windows.net/${azurerm_storage_container.deployments.name}/${azurerm_storage_blob.appcode.name}${data.azurerm_storage_account_sas.sas.sas}"
# ...
}
}
## 02_storage.tf
resource "azurerm_storage_container" "deployments" {
# ...
}
resource "azurerm_storage_blob" "appcode" {
name = "functions.zip"
type = "Block"
source = var.package # Package archive upload
# ...
}
# Deployment blob access connection string
data "azurerm_storage_account_sas" "sas" {
# ...
}
## variables.tf
variable "package" {
type = string
default = "./package/functions.zip" # Package default location
}