Posted on November 01 2023 by Paul Bourhis

Introduction

Welcome back to our journey of creating a DevOps-engineered website from scratch. In our last article, we laid out the strategy and the tools. Now, it's time to roll up our sleeves and delve into the technical setup. We'll be purchasing domain names, setting up cloud services, and configuring our CI/CD pipeline. Let's dive in!

NOTE: This article is the second part of a series. If you haven't read the first part, you can find it here.

NOTE: The source code for this project is available on GitHub: here

Part 1: Establishing Prerequisites

Before we initiate the deployment process, it's essential to set up the foundational elements that our website's infrastructure will rely on. Each component plays a critical role in the seamless creation and operation of the website.

  • Azure Subscription: A subscription is your gateway to accessing all Azure cloud services. It's a prerequisite for creating and managing Azure resources. If you don't have one already, you can sign up for a free subscription. Get an Azure Subscription.
  • Domain Name: The domain name serves as your website's digital address, essential for branding and making the site accessible to visitors. OVH is one of the platforms where you can secure a domain name that fits your brand. Register a domain on OVH.
  • Terraform: This tool allows you to define, provision, and configure Azure infrastructure using easy-to-understand declarative code. It's an indispensable part of Infrastructure as Code (IaC), making your infrastructure management more efficient and scalable. Install Terraform.
  • SendGrid Account: Reliable email communication is a pillar of user engagement. SendGrid provides robust email services that can be integrated into your website for sending transactional and marketing emails. Sign up for SendGrid.

NOTE: The SendGrid account is optional. If you don't want to use SendGrid, you can remove the SendGrid-related code from the Terraform configuration. It will be used to send emails to newsletter subscribers.

Part 2: Infrastructure Setup with Terraform

Terraform Installation

To manage our Azure resources effectively, we begin by installing Terraform. This powerful tool allows us to define our infrastructure as code, making it easier to deploy and version our web application infrastructure. Detailed instructions for installing Terraform can be found on the official Terraform website.

Terraform Background

The foundation of Terraform usage is its ability to manage state. We set up a Terraform backend that stores the state of our resources, ensuring that we can track and collaborate on changes made to our infrastructure. Here is an example of how we might configure our Terraform backend using an Azure Storage Account:


                terraform {
                  backend "azurerm" {
                    resource_group_name  = "your_resource_group"
                    storage_account_name = "your_storage_account"
                    container_name       = "your_container"
                    key                  = "terraform.tfstate"
                  }
                }
                    

For this configuration to work, we first need to create the resource group, the storage account and the storage container. We can do this manually wth the fowwowing script:

NOTE: The backend configuration is stored in a file called `backend-config.txt`. This file is not included in the repository for security reasons.


                        set website_name=bhsitconsulting
                        set region=westeurope

                        ## Create an resource group
                        az group create -n "%website_name%-tfdata" -l %region%

                        ## Create a storage account
                        az storage account create --name "%website_name%tfdata" --resource-group "%website_name%-tfdata"  --location %region% --sku Standard_LRS

                        ## Create a storage account container
                        az storage container create -n tfdata --account-name "%website_name%tfdata" --resource-group "%website_name%-tfdata"

                        ## Set up backend config file
                        echo resource_group_name="%website_name%-tfdata" > backend-config.txt
                        echo storage_account_name="%website_name%tfdata" >> backend-config.txt
                        echo container_name="tfdata" >> backend-config.txt
                    

Custom Domain and DNS Configuration

To connect a custom domain to our Azure Static Web App, we need to configure DNS settings. This is handled by creating an Azure DNS zone and defining the necessary records. Here's how we set up the DNS resources in Terraform:

DNS Zone: This resource is where we define our DNS zone, which will contain all our DNS records.


                    resource "azurerm_dns_zone" "swa" {
                      name                = var.custom_domain_name
                      resource_group_name = azurerm_resource_group.swa.name
                      tags                = var.common_tags
                    }
                        

TXT Record: A TXT record is used for various purposes, including domain ownership verification and email sender verification. We add a TXT record to our DNS zone with the necessary validation token.


                    resource "azurerm_dns_txt_record" "txt" {
                      name                = "@"
                      zone_name           = azurerm_dns_zone.swa.name
                      resource_group_name = azurerm_resource_group.swa.name
                      ttl                 = 300
                      record {
                        value = azurerm_static_site_custom_domain.txt.validation_token == "" ? "validated" : azurerm_static_site_custom_domain.txt.validation_token
                      }
                    }
                        

NOTE: The validation_token is used to verify domain ownership. It is generated by Azure Static Web App and stored in the azurerm_static_site_custom_domain.txt resource. The validation_token is only available after the domain has been validated. This is why we use a conditional statement to check if the token is available. If it is, we use it as the value for the TXT record. Otherwise, we use the string "validated".

A Record: The A record maps our domain name to the IP address of our static site. The "@" symbol represents the root domain.


                    resource "azurerm_dns_a_record" "alias" {
                      name                = "@"
                      zone_name           = azurerm_dns_zone.swa.name
                      resource_group_name = azurerm_resource_group.swa.name
                      ttl                 = 300
                      target_resource_id  = azurerm_static_site.swa.id
                    }
                        

Outputs Configuration

After deploying our infrastructure with Terraform, we receive output that includes sensitive information such as the API token for our static site and the name servers for our DNS zone. These outputs are crucial for configuring our domain's settings in OVH and for managing our static site:


                    output "api_token" {
                      value     = azurerm_static_site.swa.api_key
                      sensitive = true
                    }
                    
                    output "name_servers" {
                      value     = azurerm_dns_zone.swa.name_servers
                      sensitive = false
                    }
                
                        

Each output has a specific utility:

  • API Token: This token is used to authenticate GitHub Actions or other CI/CD pipelines with Azure, allowing for automated deployment of our web application. It is marked as sensitive because it grants access to control your Azure resources.
  • Name Servers: The list of name servers is crucial for configuring your domain name system (DNS) settings. You'll need to enter these servers at your domain registrar, like OVH, to point your domain to the Azure infrastructure we've established.
  • Storage Connection String: This connection string provides necessary credentials to access the Azure Table Storage, enabling our application to store and retrieve data securely. It is vital for backend operations such as managing newsletter subscriptions.

It's imperative to securely store the api_token and storage_connection_string as they contain sensitive information that can affect the integrity and security of your web application.

The api_token will be used in our Azure DevOps deployment file (e.g., azure-static-web-apps-deployment.yml) to enable continuous deployment through Azure Pipelines.

Azure Static Web App Configuration

The Azure Static Web App is a pivotal resource in our project, hosting the static content that makes up the frontend of our website. It's a scalable and cost-effective solution that integrates with Azure functions for backend services. Here's how we define it in Terraform:


                resource "azurerm_static_site" "swa" {
                  name                = var.website_name
                  resource_group_name = azurerm_resource_group.swa.name
                  location            = var.region
                  sku_tier            = var.static_web_app_sku
                  
                  app_settings = {
                    "TableServiceConnectionString" = module.storage.primary_connection_string
                    "SendGridApiKey"               = var.sendgrid_api_key
                  }
                }
                    

In this configuration:

  • TableServiceConnectionString: The connection string to Azure Table Storage, enabling the app to store and access data like email addresses for the newsletter.
  • SendGridApiKey: The API key for SendGrid, authorizing the app to send emails via SendGrid's email delivery service.

This Terraform configuration ensures our web app has all it needs to operate smoothly, with connections to data storage and email services set up right from the start.

Set up Azure DevOps

Azure Repos

Azure Repos provides Git repositories for source control of your code. It's a scalable, secure place that stores your code and branches, and supports collaborative pull requests and advanced file management. To start using Azure Repos, you first need to create a new repo where you will store the code for your web application.

Azure Boards for Planning

Azure Boards is a service for managing your development projects with agile tools, work item tracking, and Kanban boards. You can plan your work, track progress, and discuss issues directly from within the board. Check out Azure Boards documentation for detailed instructions on setting up and using boards for planning your web app development.

CI Pipeline for Continuous Delivery

The Continuous Integration (CI) pipeline in Azure DevOps helps to automate the build and deployment of your application. The following YAML file defines a CI pipeline for an Azure Static Web App, specifying the build and deployment process triggered by changes in the main branch:


                name: Azure Static Web Apps CI/CD
                
                pr:
                    branches:
                        include:
                            - main
                trigger:
                    branches:
                        include:
                            - main
                
                jobs:
                    - job: build_and_deploy_job
                      displayName: Build and Deploy Job
                      condition: or(eq(variables['Build.Reason'], 'Manual'), eq(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.Reason'], 'IndividualCI'))
                      pool:
                          vmImage: ubuntu-latest
                      variables:
                          - group: bhs-it-consulting-variable-group
                      steps:
                          - checkout: self
                            submodules: true
                          - task: AzureStaticWebApp@0
                            inputs:
                                azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN)
                                app_location: "/Website" # App source code path
                                api_location: "/Website/api" # Api source code path - optional
                                skip_app_build: true # Skip build - optional
                                # output_location: "" # Built app content directory - optional
                    

This pipeline uses Azure Static Web Apps task to deploy your web application, using the API token defined in the Azure DevOps variable group for authentication. The pipeline is configured to trigger on pull requests and commits to the main branch, ensuring that updates are continuously deployed to your static web app.

Conclusion

This article has taken us through the foundational steps of setting up a robust DevOps infrastructure using Terraform, detailing the process of creating Azure resources, configuring custom domains, and setting up a CI/CD pipeline. By leveraging IaC, we've laid down a scalable and maintainable framework for our web application.

The forthcoming article will shift focus from infrastructure to development, discussing the intricacies of building the website itself, from HTML construction to implementing essential web services like SendGrid. We'll also reflect on the learning process, the importance of SEO, and the revelations about the distinction between development and design.