Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Hosted-services / Azure

Leveraging Bicep to Deploy Azure Resources

0.00/5 (No votes)
29 Jan 2024CPOL12 min read 2.1K   6  
How to automatically deploy Azure resources with Bicep?
Commencing today, we initiate a series of articles delving into the deployment of Azure resources using Bicep. Throughout this exploration, we will elucidate how this emerging practice aligns with the broader paradigm of infrastructure as code.

Introduction

Over the past decade, the observed trend of cloud adoption has prompted numerous organizations to transition a multitude of their applications onto the cloud, encouraging a more profound embrace of this paradigm and this migration necessitated the adoption of more stringent processes for developing and deploying applications.

  • This trend primarily impacted applications, prompting engineers to increasingly adopt CI/CD best practices facilitated by the integrated tools offered by cloud providers, such as Azure DevOps, for instance.

  • A burgeoning trend is currently unfolding wherein automation extends beyond applications to encompass infrastructure. Resources are no longer manually allocated; instead, they are automatically provisioned through configuration files. This paradigm is commonly known as "infrastructure as code".

Various tools have surfaced to cater to the infrastructure-as-code paradigm, with Terraform and Ansible being among the most prominent. Recently, Microsoft has introduced a newcomer called Bicep, and in this series of articles, we will explore its application in real-world scenarios.

The subsequent textbooks prove useful for concluding this series, addressing infrastructure as code as a comprehensive subject matter.

This article was originally posted here: Leveraging Bicep to deploy Azure resources

What is Infrastructure as Code (IaC)?

Infrastructure as code (IaC) is a methodology that involves managing and provisioning computing infrastructure through machine-readable script files, rather than through physical hardware configuration or interactive configuration tools. In essence, it treats infrastructure —such as servers, networks, and databases— as code, allowing it to be versioned, tested, and deployed with the same practices used for application code.

With infrastructure as code, configurations are expressed in a declarative or imperative scripting language, specifying the desired state of the infrastructure. This script, often referred to as a "template" or "manifest", can be used to automate the provisioning and configuration of infrastructure components.

Streamlining the Automation

IaC allows for the automated provisioning and management of infrastructure, reducing manual intervention and the risk of human error.

Image 1

Ensuring Version Control

Infrastructure configurations can be versioned using version control systems, enabling tracking changes over time, rollback to previous states, and collaboration among team members.

Image 2

Ensuring Reproductibility

Infrastructure can be consistently reproduced across different environments (development, testing, production) by using the same codebase, minimizing discrepancies and improving reliability.

Image 3

Why Now?

The challenges mentioned earlier are not novel; they have persisted since the inception of computer science. However, the fundamental shift lies in the historical provisioning of servers on premises, where tools for automating deployments were either unavailable or challenging to implement. With the widespread adoption of cloud computing, cloud platforms have introduced methodologies and mechanisms to streamline these processes. Consequently, applying automation has become imperative, especially in the early stages of a project's lifecycle.

IaC has rapidly evolved from a pioneering concept to a foundational element of the software development process.

What are the Common Tools for Implementing Infrastructure as Code?

Presently, there are numerous providers for IaC, with Terraform and Ansible standing out as the most renowned. Notably, Microsoft has introduced Bicep, a tool specifically designed for the Azure platform, adding to the array of choices available for IaC implementation.

What is Terraform?

Terraform is actively developed by HashiCorp and enables users to define and provision infrastructure in a declarative configuration language. With Terraform, we describe the components and resources needed for our infrastructure in a high-level configuration file, and then Terraform automates the process of provisioning and managing those resources.

  • Terraform uses a declarative language to define the desired state of infrastructure. Users specify what resources they need and their configurations, and Terraform handles the process of figuring out how to make the current infrastructure match the desired state.

    Information

    We will observe that Bicep also utilizes a declarative syntax for provisioning infrastructure, where we articulate the intended outcome without specifying the individual steps to achieve that result.

  • Terraform keeps track of the state of the infrastructure in a state file. This file records the relationships between resources and their current configurations, allowing Terraform to make informed decisions during subsequent runs.

    Information

    The state file, essential for Terraform to monitor changes, needs to be stored in a designated location. This can be accomplished through Azure Storage or Terraform Cloud. Notably, Azure simplifies this process for Bicep by autonomously managing the storage of the state file.

What is Ansible?

Ansible is an open-source automation tool developed by Red Hat. It simplifies complex tasks and facilitates the management of IT infrastructure by allowing users to describe their infrastructure as code.

Ansible serves as a notable illustration of an imperative IaC tool, employing YAML-based playbooks to articulate a series of tasks.

Information

Imperative IaC is an approach where the user specifies the step-by-step sequence of actions that need to be taken to achieve the desired infrastructure state. In imperative IaC, the focus is on detailing the specific tasks and operations that should be executed, often resembling a procedural or script-like style.

While imperative IaC provides a high level of control over the deployment process and greater flexibility, it can sometimes be more complex to maintain and may require more manual intervention to handle changes or updates.

Our objective here is to introduce Bicep, showcase its practical application, and explore how it may present unique capabilities or advantages that could pose challenges to its competitors in the IaC landscape.

How to Deploy Resources on Azure?

Deploying resources on Azure can be done directly through the Azure portal, which provides a graphical user interface (GUI) mode, or in a more technical manner using the Azure CLI. While these methods are convenient, they lack the capability to automate processes or ensure reproducibility. This limitation led Azure to introduce infrastructure as code with ARM templates, allowing for a more systematic and automated approach to resource deployment.

What are ARM Templates?

ARM templates are JSON files that define the infrastructure and configuration for our project. These templates use declarative syntax, which lets us state what we intend to deploy without having to write the sequence of programming commands to create it. Here is an example of an ARM template to create a storage account.

JSON
{
  "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
  "contentVersion": "1.0.0.0",
  "resources": [
    {
      "type": "Microsoft.Storage/storageAccounts",
      "apiVersion": "2022-09-01",
      "name": "mystorageaccount",
      "location": "East US",
      "sku": {
        "name": "Standard_LRS",
        "tier": "Standard"
      },
      "kind": "StorageV2",
      "properties": {}
    }
  ]
}

This particular template specifies the creation of a storage account named "mystorageaccount" in the "East US" region, using the "Standard_LRS" SKU.

By the way, we observe the essence of declarative; in this context, we articulate what we wish to deploy without specifying the procedural steps. In practice, Microsoft interprets the file in an imperative manner, converting the definition into the corresponding REST API operation. This operation is then transmitted to the Microsoft.Storage resource provider.

JSON
PUT https://management.azure.com/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/
providers/Microsoft.Storage/storageAccounts/mystorageaccount?api-version=2022-09-01
REQUEST BODY
{
  "location": "eastus",
  "sku": {
    "name": "Standard_LRS"
  },
  "kind": "StorageV2",
  "properties": {}
}

Important

This JSON file can subsequently be stored, reused for future purposes, and version-controlled, for example, using Git. It can also be incorporated into a continuous delivery (CD) pipeline within Azure DevOps, enabling the complete automation of the deployment process.

What are the Limitations?

So far, everything aligns with the process. However, the adoption of this syntax by software engineers proved to be challenging; they found it cumbersome and difficult to use. This was especially notable considering that other providers, such as Terraform, offered a much cleaner and intuitive method for declaring resources. In response to this feedback, Microsoft took action by introducing a new, more adapted language. Enter Bicep.

Image 4

What is Bicep?

Bicep is a declarative syntax for defining ARM templates. It is designed to simplify and enhance the authoring experience for ARM templates.

  • Bicep also employs a declarative syntax, allowing users to specify the desired state of their Azure resources without detailing the procedural steps to achieve that state.
  • Bicep abstracts the complexity of ARM templates, making it more concise and readable. It provides a higher-level abstraction that is closer to the intent of the user.
  • Bicep introduces a type system, enhancing code safety by ensuring that the specified types match the expected Azure resource types.
  • Bicep provides built-in validation tools to check for errors and issues in the code before deployment, reducing the likelihood of runtime errors.

Information 1

Remember that Bicep is intended to be a more user-friendly alternative to writing ARM templates directly. It aims to streamline the process of provisioning and managing Azure resources by providing a more intuitive and efficient authoring experience. Indeed, it is not a groundbreaking technology in and of itself, nor is it something unprecedented.

Information 2

Unlike Terraform and Ansible, which are versatile tools usable across various cloud providers such as Azure, AWS, and GCP, Bicep is specifically designed for Azure. It's important to note that Bicep should not be employed if the intention is to provision resources for tools like BigQuery or Redshift.

Now, we will delve into the practical aspects of Bicep. However, before proceeding, we need to install some prerequisites to ensure its proper functionality. Although this may be a bit cumbersome, it is essential for us to move forward and explore concrete examples.

Setting Up Bicep Development and Deployment Environment

Disclaimer

The installation process outlined in this section is somewhat opinionated, emphasizing the use of Visual Studio 2022 for writing Bicep files and deploying them with Azure CLI. However, alternative options exist, such as crafting files in a basic editor and deploying them using PowerShell.

Creating Bicep Files

Mastering Bicep's syntax entirely may not be feasible, but Intellisense comes to the rescue. In this context, we'll utilize the Bicep extension for Visual Studio 2022, which can be downloaded from here. Once the extension is downloaded, simply install it in the IDE.

Deploying Bicep Files

Deploying Bicep files directly from Visual Studio is not currently supported. The deployment of a Bicep file can only be accomplished using Azure CLI or Azure PowerShell, and in this series, we will be opting for Azure CLI.

To install Azure CLI on Windows, follow the instructions provided here.

  • After installing Azure CLI, open a command prompt and execute the command 'az bicep version'.

    Image 5

  • Run the command 'az bicep install' to resolve the error.

    With all the components in place, it's now time to explore tangible examples of Bicep files. Our objective is to create a subscription that includes a resource group and an Azure Function.

    Image 6

Disclaimer

For the sake of conciseness, we will omit the creation of management groups here. However, in real-world scenarios, it is advisable to manage them using Bicep for better organization and governance.

Setting Up the Environment

  • Open Visual Studio, create a new solution.

    We will systematically create the required resources, starting with the subscription.

  • To deploy Azure resources, it is imperative to have the necessary permissions, specifically being designated as an Owner at the root scope level. This authorization can be granted using the following Azure CLI command:
    Azure-CLI
    az role assignment create --assignee <id_user_assignee> --scope "/" --role "Owner"

    "id_user_assignee" refers to the user account under which Azure CLI commands for deployments will be executed.

It's important to note that the ability to grant access to other users also requires authorization. Being a Global Administrator in Microsoft Enterprise ID alone is not sufficient; explicit declaration is needed to confirm the user's capability to manage access to all Azure subscriptions and management groups in the tenant.

To achieve this, navigate to Microsoft Entra ID, access Properties, and select Yes in the corresponding section.

Image 7

Creating a New Subscription

Image 8

  • Create a new file named create.subscription.bicep in the solution.
  • Add the following content within it:
    bicep
    metadata description = 'Creates a subscription'
    
    targetScope = 'tenant'
    
    param billingScope string = 
     '/providers/Microsoft.Billing/billingAccounts/432a9fe6-a481-55xxxxxxxxxxxxxxxxxxxxxxxx'
    
    param subscriptionName string = 'EOCS_Bicep'
    
    resource alias 'Microsoft.Subscription/aliases@2020-09-01' = {
      scope: tenant()
      name: subscriptionName  
      properties: {
        workload: 'Production'
        displayName: subscriptionName
        billingScope: billingScope
      }
    }
    
    output subscriptionId string = alias.properties.subscriptionId

Several noteworthy aspects deserve attention at this point.

  • In Bicep, the targetScope is a property that allows us to specify the deployment scope for the resources defined in a Bicep file. The targetScope property determines where the resources will be deployed when the template is executed.

  • We have the ability to define certain variables using the param keyword.

  • The ID of the recently created subscription is accessible through the output variables.

Information 1

The billing scope is essential information for creating a subscription, and obtaining it can be a somewhat intricate process. Follow these steps.

  • Execute the command 'az billing account list' in Azure CLI and note the name property.

    Image 9

  • Execute the command 'az billing profile list --account-name "<name_property>" --expand "InvoiceSections"'

    Image 10

  • Note the id property in the invoiceSections.

This property is the billing scope.

Information 2

In real-world scenarios involving a CI/CD pipeline, some variables should be configured in the current environment and subsequently replaced at runtime by a dedicated task. As an illustration, the subscription's name might be contingent on the environment (dev, staging, or production) in which it is generated, necessitating a name that appropriately reflects this context.

Important

This code is specifically designed for straightforward Microsoft accounts. However, it needs adaptation, particularly for usage with Enterprise Agreement setups.

Deploying the File

To deploy the file, open Azure CLI and execute the following command:

Azure-CLI
az deployment tenant create --template-file create.subscription.bicep --location westus

It's evident that the subscription has been successfully created.

Image 11

Creating a New Resource Group

Having defined a subscription, the next step is to include a resource group within it.

  • Create a new file named create.resourcegroup.bicep in the solution.
bicep
metadata description = 'Creates a resource group'

targetScope = 'subscription'

param resourceGroupName string = 'Infra'
param location string = deployment().location

resource infraResourceGroup 'Microsoft.Resources/resourceGroups@2021-04-01' = {
  name: resourceGroupName
  location: location
}
  • The targetScope is now subscription.

  • The resource group's location is acquired through deployment().location. This variable is actually supplied when the deployment is executed with Azure CLI, requiring us to specify the location explicitly. More on this later.

Deploying the File

Deploying a resource group requires being within a specific subscription. Therefore, it is essential to switch to the designated subscription first, ensuring that resources are provisioned in the correct location.

Azure-CLI
az account set --subscription EOCS_Bicep
az deployment sub create --template-file create.resourceGroup.bicep --location westus

In the preceding commands, it's evident that we specified the location where the resource group should be deployed. This parameter is subsequently reused by the Bicep file with the deployment().location helper.

Image 12

Creating a New Azure Function

Ultimately, we deploy an Azure Function within the resource group. It's important to note that an Azure Function requires an associated hosting plan and a storage account.

  • Create a new file named create.azurefunction.bicep in the solution.
    bicep
    param appName string = 'func-company-project-catalogueservice'
    
    param storageAccountType string = 'Standard_LRS'
    
    param location string = resourceGroup().location
    
    param runtime string = 'dotnet'
    
    var functionAppName = appName
    var hostingPlanName = appName
    var storageAccountName = '${uniqueString(resourceGroup().id)}azfunctions'
    var functionWorkerRuntime = runtime
    
    resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = {
      name: storageAccountName
      location: location
      sku: {
        name: storageAccountType
      }
      kind: 'Storage'
      properties: {
        supportsHttpsTrafficOnly: true
        defaultToOAuthAuthentication: true
      }
    }
    
    resource hostingPlan 'Microsoft.Web/serverfarms@2021-03-01' = {
      name: hostingPlanName
      location: location
      sku: {
        name: 'Y1'
        tier: 'Dynamic'
      }
      properties: {}
    }
    
    resource functionApp 'Microsoft.Web/sites@2021-03-01' = {
      name: functionAppName
      location: location
      kind: 'functionapp'
      identity: {
        type: 'SystemAssigned'
      }
      properties: {
        serverFarmId: hostingPlan.id
        siteConfig: {
          appSettings: [
            {
              name: 'AzureWebJobsStorage'
              value: 'DefaultEndpointsProtocol=https;
              AccountName=${storageAccountName};
              EndpointSuffix=${environment().suffixes.storage};
              AccountKey=${storageAccount.listKeys().keys[0].value}'
            }
            {
              name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
              value: 'DefaultEndpointsProtocol=https;
              AccountName=${storageAccountName};
              EndpointSuffix=${environment().suffixes.storage};
              AccountKey=${storageAccount.listKeys().keys[0].value}'
            }
            {
              name: 'WEBSITE_CONTENTSHARE'
              value: toLower(functionAppName)
            }
            {
              name: 'FUNCTIONS_EXTENSION_VERSION'
              value: '~4'
            }
            {
              name: 'WEBSITE_NODE_DEFAULT_VERSION'
              value: '~14'
            }
            {
              name: 'FUNCTIONS_WORKER_RUNTIME'
              value: functionWorkerRuntime
            }
          ]
          ftpsState: 'FtpsOnly'
          minTlsVersion: '1.2'
        }
        httpsOnly: true
      }
    }
    
    output azureFunctionId string = functionApp.id
  • The targetScope is not explicitly indicated; by default, it is set to resourceGroup, and it is at that scope that we intend to deploy our function.

Deploying the File

We execute the following command in Azure CLI.

Azure-CLI
az deployment group create --resource-group Infra --template-file create.azurefunction.bicep 

Image 13

And there we have it: our resources are provisioned, and we can confidently execute these scripts in another data center if the need arises. Furthermore, these Bicep scripts can be version-controlled, allowing for redeployment as needed.

Important

A final note: where is the current deployment's state stored? In other words, how does Bicep determine the existence of resources or those that need creation? In contrast to Terraform, where this state must be explicitly stored, the seamless integration between Azure and Bicep relieves developers of this responsibility (Azure automatically manages this for us).

Final Thoughts

If you wish to delve deeper into this topic, acquire the following books, which encompass all the concepts emphasized in this series and delve into more advanced ones.

Do not hesitate to contact me should you require further information.

History

  • 29th January, 2024: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)