CI/CD Azure Web app deployed via Terraform


Build Status Website CI/CD Pipeline
Build Status Terraform Pipeline

Terraform deployment of the full infrastructure needed for a web app - deploying the web app, supporting resources such as the CDN & Application Insights and verifying DNS records for custom domains bound to the web app & CDN

Architecture

Created using azurediagrams.com

architecture

Summary

Improvements made to an existing web app deployed using CI/CD including:

  • Full Terraform deployment through a DevOps Pipeline, including DNS verification of custom domains (for the web app & CDN) directly from the pipeline
  • Addition of Application Insights to monitor the web app
  • Addition of a KV to store secrets

Terraform

Link to Github repo

Modules:

Creates an RG based off the project_name & environment variables passed through
After creation the RG name is outputted

Creates a LAW and links an Application Insights resource to it

APPINSIGHTS_INSTRUMENTATIONKEY & APPLICATIONINSIGHTS_CONNECTION_STRING are outputted to be used to bind a web app to the App Insights resource
The ID of the App Insight is also outputted

Creates a key vault to store secrets needed for the devops pipeline
The Key Vault ID is outputted

Creates a Container Registry to hold the Docker containers created as part of the Web App CI/CD pipeline
Also assigns the managed identity of the ASP created later the AcrPull role so it can pull the latest Docker image

Creates an App Service Plan with the B1 SKU
A Linux Web App is also created under the ASP and also passes through the outputted Application Insight variables, as well as the Container Registry information

The following variables are outputted:

  • app_service_plan.id

  • web_app_name

  • app_service_plan_managed_id
    Passed through to be used by the containerRegistry module to assign the Managed Identity the AcrPull role

  • web_app_domain_verification_id
    Used by the cloudflare module to verify the custom domain used

  • web_app_default_domain
    Also used by the cloudflare module to verify the custom domain

  • storageAccount

Creates a new RG to hold a storage account under - the storage account will later be used by the CDN module to configure it as an origin

Creates a CDN profile and a CDN endpoint, the storage account created by the storageAccount module is linked as a origin so images stored in the storage account can both be cached by the CDN to reduce costs, allow images to have a custom domain instead of the default blob endpoint and be encrypted over HTTPS

The endpoint FQDN is outputted to be later used by the cloudflare module to verify the second custom domain

Using the Cloudflare Terraform provider 3 DNS records are created

  • cdn_cname
    Creates a CNAME record using the cdn_hostname and cdn_endpoint_uri, this is used to verify the custom domain linked to the CDN, in my case this will be images.andriusdalgeda.uk

  • web_app_txt
    Creates a TXT record using the web_app_domain_verification_id & asuid. plus the website_hostname
    This is used to verify my andriusdalgeda.uk custom domain linked to the web app

  • web_app_a
    Creates an A record using the website_hostname and the IP address returned by the DNS Terraform provider
    This is needed as Azure doesn’t return the IP address linked to the web_app directly so the provider is called to resolve the record to an IP address
    The IP is then passed through to create the A record

After creation of these 3 records a 2min sleep is started to ensure that the DNS changes propagate so that the next modules won’t fail

After adding the DNS records the module links the custom domain to the web app (andriusdalgeda.uk)
An Azure managed certificate is also generated to encrypt traffic over HTTPS to the site

After DNS verification a custom domain is passed through to the CDN (images.andriusdalgeda.uk)
This starts domain verification which can take up to 8 hours so the deployment time can vary

Providers:

  • hashicorp/azurerm

  • cloudflare/cloudflare
    Creates DNS records needed to verify the custom domains of both the web app & CDN

  • hashicorp/time
    Used for a time-sleep statement in the cloudflare module

  • hashicorp/dns
    Used to resolve the web apps address to an IP, the IP is then passed through and used for domain verification using an A record

Deployment from scratch:

Specify variables where empty:

First run backend.ps1 to create a resource group and storage account to store the Terraform remote backend

Populate the backend.tf using the variables specified in the .ps1 script
Uncomment the use_azuread_auth if you are running this from a pipeline (using a service principal)

Populate template.tfvars

terraform init
terraform plan var-file=“template.tfvars”
terraform apply var-file=“template.tfvars”

Full Deployment:

Using the Terraform code above and azure-pipelines.yml
The pipeline was run and completed after 21mins - the bulk of it being during CDN domain verification
pipeline

After deploying the infrastructure I had to edit my existing pipeline that deploys the web app to point to the new web app and container registry
webpipeline

I then migrated all of my existing images to the new storage account using the azcopy tool
customdomains

After it ran I verified that both the website and CDN were working, and configured with HTTPS
customdomains

Further Improvements

Currently if the Terraform code is deployed the existing pipeline that deploys the web app the code has to be manually updated to point to the new resource
A way to improve this is to use the Azure Devops Terraform provider to write variables directly to the pipelines library (storing the secrets/values)

The KV could also be integrated into the pipeline to store and return the secrets/values