13 Case studies

This chapter covers

  • Three extensive infrastructure examples created using Azure Bicep
  • A first use case that explores structuring Bicep for creating an Azure foundation, also called a landing zone or Azure platform
  • A second use case that explores subscription-level deployments for preconfiguring subscriptions with minimum standards or a baseline configuration
  • A third use case that builds out the infrastructure needed for a microservices architecture

With everything you’ve learned so far, you should be able to create all the Azure infrastructure you need, either through ARM or Bicep templates. Yet, it can be challenging to get started with larger infrastructure for the first time. To help you get started, this chapter contains three larger examples with step-by-step explanations of how the templates are structured and built.

Some parts of templates will be omitted, as they are repetitions of what you have learned already, or are very similar. The text will focus on the line of thought involved in writing the templates and less on the syntax. Where parts of a template are omitted, the text will describe what you need to do to follow along, or you can look at the completed templates on GitHub (https://github.com/AzureIaCBook).

In this chapter, you will see three examples, all from different points of view. In section 13.1, you will take up the role of engineer in a central IT team that is responsible for building Azure landing zones (sometimes also called an Azure foundation, cloud foundation, or Azure platform). This team is responsible for the most abstract configuration of the Azure tenant. They own the management group structure and set up default Azure policies. A team like this is often called an internal platform team, and the products they bring to consuming teams are preconfigured subscriptions.

Next up, you’ll take on the role of engineer in one of these teams. You’ll be responsible for further tailoring the configuration of subscriptions to the standards and needs of your own department. You will concern yourself with security settings, cost management, and other resources that mostly relate to nonfunctional requirements.

In the third example, you’ll take up the role of engineer in a product team. Here you will work on a larger example for an infrastructure that can be used for hosting a load-balanced, highly available microservice architecture.

All three examples will assume knowledge of the problem type at hand and how to solve that type of problem in Azure, as they will focus only on building the Bicep or ARM templates needed to execute that solution using IaC. This will not be an extensive introduction to the topics, and if you are reading this book while getting started with Azure, you may find this chapter tough to read. However, more experienced readers will benefit from these more extensive examples that show possible approaches to larger problems. Alright, let’s start with building an Azure foundation.

13.1 Building an Azure foundation

In this first example, let’s assume you are working in the central IT department of a medium-sized IT company. There are tens of teams working on different applications, all hosted in Azure, and you are responsible for creating the Azure subscriptions they work in. Before handing them over, you are also expected to put in some basic guardrails to ensure that the teams are using those subscriptions in accordance with the company guidelines.

13.1.1 The management group layout

As you will be creating tens or maybe even hundreds of Azure subscriptions, it makes sense to create a logical grouping of subscriptions, based on how and where they are used. There are many ways to do this, but let’s say you are getting your inspiration from the cloud adoption framework (see Microsoft’s “Resource organization” article: http://mng.bz/AyAe) and want to create the structure in figure 13.1.

Figure 13.1 An example management group structure

As you might recall from earlier chapters, subscriptions can be grouped into management groups, and management groups into other management groups. The top-level management group, Root, exists by default in every Azure environment. The other groups you create, and they serve the following goals:

  • Foundation—This management group contains all the subscriptions with foundational platform capabilities that are used by all other subscriptions.

  • Managed—This management group contains all the subscriptions that are used by other teams for production workloads.

  • Unmanaged—This management group contains all the subscriptions that are used by other teams for nonproduction workloads.

Finally, all these management groups are grouped into a single pseudo-root, called YourCompany. Later you will work with policies, and these cannot be created in, or assigned to, the Root management group. Hence the creation of this pseudo-root. Of course, you might make other choices, but this can be a valid approach for many situations.

Let’s start working on a template to create this structure, starting with the pseudo-root. Let’s refer to this template as the company main template from now on (13-01.bicep).

Listing 13.1 Creating a pseudo-root management group

targetScope = 'tenant'      
 
resource yourCompanyMG 'Microsoft.Management/managementGroups
     @2020-05-01' = {
    name: 'YourCompany'
    scope: tenant()
    properties: {
        displayName: 'Your Company'
    }
}

This template deploys to the Azure tenant, the top level of any Azure environment.

To deploy a template at the tenant scope, you can use the following command:

az deployment tenant create --location westeurope 
   --template-file .13-01.bicep

In listing 13.1, and in the code to deploy the template, you can see that this template is not targeting the resource group or subscription level but is deploying to the tenant. This is because all management groups live inside the tenant, and the hierarchy is created using a parent-child relation that you will see later. All management groups have a resource name, which you are advised to keep short and simple with no spaces, which in this case is YourCompany. You can use the displayName property to provide a longer, human-readable name. Let’s now add another management group, as the child of the first one (13-01.bicep continued).

Listing 13.2 Adding a pseudo-root management group

resource FoundationMG 'Microsoft.Management/managementGroups@2020-05-01' = {
    name: 'Foundation'
    scope: tenant()
    properties: {
        displayName: 'Foundation'
        details: {
            parent: {
                id: yourCompanyMG.id     
            }
        }
    }
}

Nesting one management group in another by referencing it as a parent

Here you see the same structure used to create another management group, but now with another property called parent. This property is used to specify that this Foundation management group should be nested in the YourCompany management group.

You can now continue this template on your own, adding the remaining two management groups or get the complete template from GitHub. With the management groups in place, let’s continue by assigning policies to them.

13.1.2 Assigning a policy initiative

One of the reasons many organizations use management groups is because they allow you to assign policies or policy initiatives. Assigning a policy to a management group makes it effective not only on that management group itself, but also on all the management groups below it, and on all the subscriptions within it and the management groups below it.

In the management group structure you just created, it makes sense to have only the most basic of policies on the pseudo-root, and to assign three different sets of policies to the three management groups, Foundation, Managed, and Unmanaged.

For each of those management groups, it makes sense to create a new Bicep file that you reference as a module from the previous file (13-01.bicep). Let’s start by writing a template that assigns the Azure built-in ISO 27001 initiative to the Managed management group. This way, all the built-in policies that help you be compliant with ISO 27001 are automatically applied to all subscriptions that are used to host production workloads.

ISO 27001 (www.iso.org/isoiec-27001-information-security.html) is a widely accepted standard for information security, which should be implemented with a combination of technical and nontechnical controls. Azure provides you with a predefined set of policies with all the technical controls for adhering to this standard, namely the initiative we are assigning here (13-01-01.bicep).

Listing 13.3 Assigning a policy initiative to a management group

targetScope = 'managementGroup'
 
param managementGroupName string
 
var managementGroupScope = '/providers/Microsoft.Management/managementGroups/
     ${managementGroupName}'
 
var builtInIso27001InitiativeId = '89c6cddc-1c73-4ac1-b19c-54d1a15a42f2'
 
resource iso27001 'Microsoft.Authorization/policyAssignments
     @2020-09-01' = {
    name: managementGroupName
    location: 'westeurope'
    identity: {
        type: 'SystemAssigned'
    }
    properties:{
        description: 'The assignment of the ISO27001 initiative'
        policyDefinitionId: '${managementGroupScope}/providers
             /Microsoft.Authorization/policySetDefinitions
             /${builtInIso27001InitiativeId}'  
    }
}

This template targets the managementGroup scope to deploy resources into the management group. Few resources can be deployed at this scope, but a policy assignment is one of them. The assignment here is just like those you saw in chapter 12. It is interesting to note that the GUID stored in the builtInIso27001InitiativeId variable is a constant, and it’s the same for all Azure users. Instead of assigning this template directly, let’s use it as a module and append the following to the template in the 13-01.bicep file.

Listing 13.4 Appending the policy assignments to the main template

module ManagedMGPolicyAssignments '13-01-01.bicep' = {
    name: 'ManagedMGPolicyAssignmentsDeployment'
    scope: ManagedMG
    params: {
        managementGroupName: ManagedMG.name
    }
}

From here on, you can build out your own policy and initiative assignments to all the management groups. You can follow the structure of using one module for all the policies or policy assignments for each management group, and including those modules in the main template. Let’s now move one level down in the hierarchy and look at how you can write a template that creates a new Azure subscription as part of an example Azure foundation.

13.1.3 Creating a management subscription

No matter your situation, the extent to which you follow common practices or the cloud adoption framework from Microsoft, it is likely you’ll need at least one subscription to implement some centralized concerns. A very common example is logging. In this section, you will create an Azure subscription, called Management, that contains a Log Analytics workspace where all the logs you want to collect can be stored.

Creating subscriptions using a Bicep or ARM template is only possible when you have an Enterprise Agreement with Microsoft or a Microsoft Customer Agreement. If you have an Enterprise or Microsoft Customer Agreement, you can look at the following Microsoft blog for instructions on creating subscriptions using templates: “Creating Subscriptions with ARM Templates” (http://mng.bz/ZA2N). Only larger organizations have such an agreement, so creating subscriptions using IaC is not available for individual learners.

If you don’t have such an agreement, you can complete building your Azure foundation by manually creating a subscription called Management under the Foundation management group.

Once this is in place, you can follow these steps to create a Log Analytics resource:

  1. Create a template called 13-01-02-subscription.bicep that is deployable to subscriptions and that creates a resource group with the name Logs in the subscription. You can manually deploy this template using the az deployment sub command, as you learned in chapter 2.

  2. Create a template called 13-01-02-resourceGroup.bicep that creates the Log Analytics resource with the name CentralLogStore. You can deploy this template as a module from the 13-01-02-subscription.bicep template.

Given all that you have learned in this book, you should be able to complete these tasks on your own, or you can look up these files on GitHub. Either way, you will now have a complete management group structure, a management subscription, and a centralized log store, as illustrated in figure 13.2. In the next section, you will complete this example by creating a subscription for use by another team.

Figure 13.2 Management groups, a subscription, a resource group, and a resource

13.1.4 Creating workload subscriptions

An Azure foundation is only useful if you use it to provision and preconfigure subscriptions for other teams, so in this section you’ll do just that. Before you can configure a subscription, you will have to create it. As this can’t be done through IaC, you should manually create a subscription called ToMaToe in the Managed management group.

Tip If you cannot create new subscriptions due to licensing, authorizations, or other limits, you can also reuse a subscription you already have. Just keep in mind that in the real world you would not reuse subscriptions but generally create one or more for each consuming team.

Remember that subscriptions in this management group are destined to be handed over to the team so they can host their own applications, so you will reuse this subscription in the final section of this chapter for our third and final use case, hence the name.

But before you get to the third use case, we’ll practice preconfiguring a subscription from the point of view of a centralized team. Centralized teams that manage an Azure foundation often want to preconfigure many things on subscriptions. Here you will see just one example: capturing all activity logs on an Azure subscription and storing them for a long time. Activity logs are the Azure audit logs, and they can be used to retrieve information about who made which changes. To accomplish this you are going to write—guess what? A Bicep template (13-01-03.bicep).

Listing 13.5 A template to capture activity logs in Log Analytics

targetScope = 'subscription'
 
param logAnalyticsWorkspaceId string
 
resource alConfig 'Microsoft.Insights/diagnosticSettings     
     @2021-05-01-preview' = {     
    name: 'activityLogsToLogAnalytics'
    properties: {
        workspaceId: logAnalyticsWorkspaceId                 
        logs: [                                              
            {
                category: 'Administrative'
                enabled: true
            }
            {
                category: 'Security'
                enabled: true
            }
            {
                category: 'Policy'
                enabled: true
            }
            {
                category: 'Alert'
                enabled: true
            }
        ]
    }
}

This resource is used to set the logging configuration on the subscription.

The Log Analytics workspace to send the logs to

An array describing which logs should be sent, and which not

In this template, you’ll see a resource of type Microsoft.Insights/diagnosticsSettings that is deployed at the subscription level. These are the settings that configure a routing for subscription-level logs to be sent to one or more destinations. In this case, the logs are being sent to the Log Analytics workspace you created earlier. Finally, in the logs property, you define which categories of logs should be forwarded and which not. Here, only the logs in the categories Administrative, Security, Policy, and Alert are sent.

To deploy this template, you can use the following command:

az deployment sub create -location <location> 
   --template-file 13-01-03.bicep

You can extend this template to further configure subscriptions as you want. For example, you can provision default resources using a deployment stack or provide access using RBAC assignments. These are just other types of Azure resources you can look up in the ARM documentation. Let’s now move on to another example and take up the role of engineer in a department that receives these subscriptions and wants to tailor their configuration to their own department’s needs.

13.2 Subscription level deployments

In this section, you will explore the options Bicep has to offer for building out an Azure subscription configuration. Let’s imagine you are working as a DevOps engineer in a larger organization that has many teams working on Azure applications. Your department is responsible for about a dozen of these applications and consists of six teams. Your specific responsibility is requesting and managing the subscriptions that you receive from the central IT department and preparing them for use by the application teams in your department.

In this section you will build out an orchestrating Bicep template and a number of resource templates to do just that. As you are now working on templates that target the subscription scope, you’ll need to start your template with the following:

targetScope = 'subscription'

Templates targeting the subscription scope are deployed with a slightly altered command:

az deployment sub create -location <location> 
     --template-file <template-file>

With that in place, let’s visit some topics that come up regularly when talking about subscription-level deployments, starting with budgets.

13.2.1 Configuring budgets

One of the first things that every department wants to keep tabs on is the money. By default, whenever you receive a new subscription, a default budget should be applied. To deploy a budget to a subscription, you can use the following template (13-02-01.bicep).

Listing 13.6 A subscription template for setting budgets

targetScope = 'subscription'
 
param maximumBudget int
 
resource defaultBudget 'Microsoft.Consumption/budgets@2021-10-01' = {    
    name: 'default-budget'
    properties: {
        timeGrain: 'Monthly'                                             
        timePeriod: {
            startDate: '2021-11-01'                                      
        }
        category: 'Cost'
        amount: maximumBudget                                            
        notifications: {                                                 
            Actual_Over_80_Percent: {
                enabled: true
                operator: 'GreaterThanOrEqualTo'
                threshold: 80
                thresholdType: 'Actual'
                contactEmails: [
                    '[email protected]'
                ]
            }
        }
    }
}

A resource of type Microsoft.Consumption is used for defining budgets and alerts on budgets.

This is a budget that defines a monthly maximum.

The date at which the budget becomes effective

The actual budget

A list of one or more notification rules that determine if and when notifications about the budget should be sent

In this template you see an Azure budget resource being created. By themselves, budgets don’t do anything, as they are not a spending limit. So, attached to this budget, a notification is set to fire whenever the forecasted spending on the subscription exceeds 80% of the budget. In a real-world situation you would add multiple notifications to a budget. For example, another notification that 90% of the allocated budget has been spent might be in order. Also, it can be beneficial to send notifications to the end user of the subscription, using lower limits. You can add this template to your orchestrating template as follows (13-02.bicep).

Listing 13.7 An orchestrating template for deploying subscription-level resources

targetScope = 'subscription'
 
module budgetDeployment '13-02-01.bicep' = {
    name: 'budgetDeployment'
    params: {
        maximumBudget: 1000
    }
}

13.2.2 Configuring Microsoft Defender for Cloud

Another thing you might want to preconfigure for your users is a basic Microsoft Defender for Cloud configuration. Microsoft Defender is an analysis and recommendation center that can automatically scan your resources for security misconfigurations, abuse, and other security risks.

In your organization you want to enable Microsoft Defender for Cloud by default for Key Vaults and VMs. The first because managing secrets is especially important for your teams, and the second because VMs are IaaS and leave a lot of security responsibly with your teams—you’ll want to guide them with advice whenever they deploy a VM. The following template provides such a configuration (13-02-02.bicep).

Listing 13.8 Configuring Microsoft Defender for Cloud

targetScope = 'subscription'
 
var enableDefenderFor = [
    'KeyVaults'
    'VirtualMachines'
]
 
resource defenderPricing 'Microsoft.Security/pricings@2018-06-01' =    
     [for name in enableDefenderFor: { 
    name: name
    properties: {
        pricingTier: 'Standard'                                        
    }
}]
 
resource MDfCContact 'Microsoft.Security/securityContacts              
     @2020-01-01-preview' = {   
    name: 'default'
    properties: {                                                      
        emails: '[email protected]'
        alertNotifications: {
            state: 'On'
            minimalSeverity: 'High'
        }
        notificationsByRole: {                                         
            state: 'Off'
            roles: [
            ]
        }
    }
}

Enabling Microsoft Defender for Cloud is done by switching the pricing tier from basic to standard.

The new pricing tier

The Microsoft.Security/securityContacts resource is used to define one or more receivers for security alerts.

The properties object describes who to alert.

The notificationsByRole property specifies who to alert using an RBAC role name, instead of email.

This template deploys two types of resources. First, a Security Center pricing configuration is deployed. This configuration enables the Standard tier (which is paid) for Defender, enabling more advanced protection, scanning, and recommendations. This resource is deployed twice by iterating through the enableDefenderFor array, which holds the two types Defender will be enabled for.

Note Microsoft renamed “Azure Security Center” to “Microsoft Defender for Cloud.” However, when they made that change, they left the name of the resource provider and resource types unchanged, to keep backward compatibility. For this reason, you’ll see both the product name (Defender for Cloud) and the technical name (Security Center) when talking about the same thing.

The second resource configures a contact to reach out to in the case of recommendations or alerts with a severity of High or Critical. You can add the deployment of this template to the template in listing 13.2, or look at the ch13-02.bicep file in the GitHub repository, after which you can redeploy the template to test your work.

13.2.3 Creating resource groups and providing access

Before handing the subscription over to the team that will use it, you can complete the configuration by adding one or more resource groups and providing access to specific users in the Azure Active Directory (AAD) or to AAD groups. The following listing does just that (13-02-03.bicep).

Listing 13.9 Creating a resource group and providing access to a user

targetScope = 'subscription'
 
var resourceGroupName = 'storageLayer'
resource storageLayerRef 'Microsoft.Resources/resourceGroups
     @2021-04-01' = {
    name: resourceGroupName
    location: 'westeurope'
}
 
module resourceGroupRBAC '13-02-03-roleAssignment.bicep' = {
    name: 'resourceGroupRBACDeployment'
    scope: resourceGroup(resourceGroupName)
}

In this listing, a storageLayer resource group is created. The second resource is captured in a module of its own. This is a common pattern, where you first create the container, the resource group, and next invoke the deployment of resources into that container. The only way to achieve this is by wrapping the resource in a module of its own and applying the scope property to the module deployment. The contents of this file, 13-02-03-roleAssignment.bicep, can be found on GitHub.

Finally, you can add the deployment of this template to the template from listing 13.2, or look at the ch13-02.bicep file in the GitHub repository. This template and the modules that it deploys are a good example of how you can preconfigure subscriptions using Bicep templates.

You can take and extend these templates with resources like policies, policy assignments, and role assignments, depending on your own interests. Using a combination of the Azure Quickstart Templates GitHub repository (https://github.com/Azure/azure-quickstart-templates) and the ARM templates reference (https://docs.microsoft.com/en-us/azure/templates), you should have all the tools you’ll need to figure this out on your own. In the next section, you will switch to the perspective of a product team and use this configured subscription to deploy your application.

13.3 Creating a highly-available microservice architecture

For this third example, you will be transferring to another role: this time you’re an engineer in a product team. Here you are working on a large web application, which is implemented using a microservices architecture. You can continue from the previous example and reuse that subscription.

There are two different frontend solutions for your system: a web user interface and a mobile app running on different platforms. To gather data, these systems communicate with a backend hosted in the cloud, using HTTPS. Since the web application and its usage is growing steadily, your company has decided to develop the backend using a microservices architecture. This case study will guide you through the architecture of this case and the Bicep template that describes it. Before we get into the details, though, let’s first get an overview of the infrastructure for this case.

All resources will be provisioned in a virtual network (vnet). External (internet) traffic will be directed to an application gateway that will act as a gatekeeper to the vnet.

Figure 13.3 shows how the services will be organized. All HTTPS requests will be redirected from Azure Application Gateway to Azure API Management. API Management interprets the requested routes and directs traffic to the desired microservice. Communication between services is allowed through an Event Grid domain. This Event Grid Domain only allows traffic from a private IP by using a private endpoint.

Figure 13.3 A visual representation of the virtual network with its subnets

Cost warning

If you decide to test this template, please know that this template creates resources that are potentially relatively expensive. Tear the environment down as soon as possible when you won’t actually be using the resources in the infrastructure. Azure services are billed per minute, or some even per second.

To help organize the resources in this infrastructure, the template will create two resource groups, which we’ll look at next.

13.3.1 Resources organized in resource groups

The template is organized using nested modules. The main template (main.bicep) is the top of the hierarchy, and it runs on the subscription scope. It creates resource groups to organize resources into logical groups. Then, for each resource group the main template calls a module that runs on the scope of that resource group to deploy the resources for that group.

Figure 13.4 shows an Azure subscription with resource groups containing the resources they will be provisioned with by the template described in this chapter. The darker resource groups in the figure are resource groups expected to be provisioned at a later stage. These resource groups will contain microservices that may be developed by your team or other teams.

Figure 13.4 Overview of resource groups and resources

The main template will target the subscription scope. You have already learned how to target different scopes in a Bicep file:

targetScope = 'subscription'

The names of the two resource groups consist of the system name, its purpose, the deployment location, and the deployment environment. If your system is called “tomatoe,” an example name could be tomatoe-net-weu-prd. The two resource groups are named net and int, for networking and integration. The system name, location, and environment are defined as parameters. The following listing contains a fragment of the main template (13.03/main.bicep).

Listing 13.10 Creating resource groups

targetScope = 'subscription'
 
param systemName string
param locationAbbreviation string
param environmentCode string
 
var resourceLocation = 'westeurope'
var resourceGroupNetworking = 'net'
 
resource networkingRg 'Microsoft.Resources/resourceGroups@2021-04-01' = {
    location: resourceLocation
    name: '${systemName}-${resourceGroupNetworking}-
         ${locationAbbreviation}-${environmentCode}'
    tags: {
        Kind: resourceGroupNetworking
        CostCenter: systemName
    }
}
 
module networkingModule 'networking.bicep' = {
    name: 'networkingModule'
    scope: networkingRg
    params: {
        ...
    }
}...

The preceding listing is a small fragment of the main template. It contains the parameters for the system name, location abbreviation, and environment code (development, test, acceptance, production). The actual template contains more parameters, and most parameters have attributes to describe them or limit their values. We’ve removed them here to make the example clearer. This template creates a resource group and then deploys resources inside that resource group. This process is then repeated for the resources required for integration.

Prerequisites for running this template

Because this template assumes you are going to accept web traffic to the application gateway, a domain name (FQDN) and SSL certificate are required. You can configure all these values through parameter files except for the SSL certificate.

For the SSL certificate, an existing Key Vault is required with the certificate already in it. This Key Vault must be configured to allow access from the Azure Resource Manager. This will allow the template to control access from all services required to access the Key Vault.

For internal network traffic you can generate a self-signed certificate with a trusted root certificate. The following PowerShell script generates a self-signed certificate that you can use for this deployment:

$trustedRootCert = New-SelfSignedCertificate `
    -Type Custom `
    -CertStoreLocation "Cert:CurrentUserMy" `
    -KeySpec Signature `
    -Subject "CN=Your Common Name" `
    -KeyExportPolicy Exportable `
    -HashAlgorithm sha256 `
    -KeyLength 4096 `
    -KeyUsageProperty Sign `
    -KeyUsage CertSign `
    -NotAfter (Get-Date).AddMonths(24)
  
$sslCert = New-SelfSignedCertificate `
    -Type Custom `
    -CertStoreLocation "Cert:CurrentUserMy" `
    -KeySpec Signature `
    -Subject "CN=*.your-internal-domain.int" `
    -DnsName "*.your-internal-domain.int","your-internal-domain.int" `
    -KeyExportPolicy Exportable `
    -HashAlgorithm sha256 `
    -KeyLength 2048 `
    -Signer $trustedRootCert
  
Export-Certificate  `
    -Type CERT `
    -Cert $trustedRootCert `
    -FilePath .certificate-trustedroot.cer
  
$pfxPassword = ConvertTo-SecureString -String 
     "Passwd01" -AsPlainText -Force
Export-PfxCertificate `
    -ChainOption BuildChain `
    -Cert $sslCert `
    -FilePath .certificate.pfx `
    -Password $pfxPassword

Note that you must change your-internal-domain.int to the actual name of your internal domain, and you may want to change the password Passwd01 to a stronger password.

Attaching your resources to a vnet is a nice first step toward security. It provides a lot of security-related possibilities and is therefore recommended in a lot of cases. Let’s see how the vnet for this case study is described.

13.3.2 Networking with Bicep

As mentioned previously, the Bicep templates are organized in such a fashion that all resources are declared in a Bicep file named after the resource group they are provisioned in. It is not mandatory to do so, but since this template is relatively large, it is a good idea to structure your IaC files in a way that will keep them easily maintainable, as we described in chapter 5.

The networking.bicep file will describe all resources that will end up in the networking resource group. The networking template will provision a vnet that allows for adding security measurements like a firewall and network security groups. This enables you to, for example, cut off external traffic and orchestrate traffic within the vnet.

To fit the needs of this case, a vnet will be provisioned with four subnets: gateway, apim, integration, and services. The subnets are defined as variables in Bicep—see figure 13.3 for a visual representation of this vnet (13.03/main.bicep).

Listing 13.11 Subnets for the vnet

var subnets = [
    {
        name: 'gateway'
        prefix: '10.0.0.0/24'
        privateEndpointNetworkPolicies: 'Enabled'
        privateLinkServiceNetworkPolicies: 'Enabled'
        delegations: []
    }
    {
        name: 'apim'
        prefix: '10.0.1.0/24'
        privateEndpointNetworkPolicies: 'Enabled'
        privateLinkServiceNetworkPolicies: 'Enabled'
        delegations: []
    }
    {
        name: 'integration'
        prefix: '10.0.2.0/24'
        privateEndpointNetworkPolicies: 'Disabled'
        privateLinkServiceNetworkPolicies: 'Enabled'
        delegations: []
    }
    {
        name: 'services'
        prefix: '10.0.3.0/24'
        privateEndpointNetworkPolicies: 'Enabled'
        privateLinkServiceNetworkPolicies: 'Enabled'
        delegations: []
    }
]

The gateway subnet will contain the instance of Azure Application Gateway, and the apim subnet will contain an instance of Azure API Management. The integration subnet will contain an instance of Event Grid with a private endpoint that only allows network traffic from the services subnet. The services subnet will contain all of your microservices.

To provision the vnet, a separate Bicep file is used, and it’s called as a module from the main template (13.03/Network/virtualNetworks.bicep).

Listing 13.12 Describing the vnet

param defaultResourceName string
 
param addressPrefixes array
param subnets array
 
var resourceName = '${defaultResourceName}-vnet'
 
resource network 'Microsoft.Network/virtualNetworks@2021-02-01' = {
    name: resourceName
    location: resourceGroup().location
    properties: {
        addressSpace: {
            addressPrefixes: addressPrefixes      
        }
        subnets: [for subnet in subnets: {        
            name: subnet.name
            properties: {
                addressPrefix: subnet.prefix
                privateEndpointNetworkPolicies: 
                     subnet.privateEndpointNetworkPolicies
                privateLinkServiceNetworkPolicies: 
                     subnet.privateLinkServiceNetworkPolicies
                delegations: subnet.delegations
            }
        }]
    }
}

The address space of the entire vnet

Loop through the subnets array to create an array of subnet objects.

You can see that the subnets for this vnet are created with a loop, running through all the entries in the subnets array passed in as a parameter for this template. Now that the vnet is in place, let’s see how you can access the deploy-time Key Vault to prepare for the deployment of API Management and Application Gateway.

13.3.3 Using the existing keyword to set access to a Key Vault

The template describes an instance of Application Gateway (AppGw). This AppGw will reference the certificates mentioned earlier in this chapter to allow for HTTPS traffic. You cannot download these certificates using a managed identity because the AppGw instance doesn’t exist yet. This means there is no way to set permissions on the deploy-time Key Vault to allow AppGw to download the certificates.

To solve this problem, the template creates two user-assigned identities (UAIDs), which are managed identities, but created by a user instead of the system. These UAIDs are provisioned as resources and granted access to allow downloading certificates from the Key Vault. At a later stage, one of these UAIDs will be attached to API Management and the other to Application Gateway so these services can use those UAIDs for downloading the certificates. Let’s see what the Bicep files that do this look like.

Listing 13.13 Provisioning a user assigned identity and granting access to a Key Vault

resource deployTimeKeyVault 'Microsoft.KeyVault/vaults@2021-06-01-preview' 
     existing = {                                               
    name: deployTimeKeyVaultName
    scope: resourceGroup(deployTimeKeyVaultResourceGroup)
}
 
module userAssignedIdentityModule 
     'ManagedIdentity/userAssignedIdentities.bicep' = {
    name: 'userAssignedIdentityModule'
    params: {
        defaultResourceName: defaultResourceName
    }
}
 
module keyVaultAccessPoliciesForUaidModule 
     'KeyVault/vaults/accessPolicies.bicep'
           = {
    name: 'keyVaultAccessPoliciesForUaidModule'
    scope: resourceGroup(deployTimeKeyVaultResourceGroup)
    params: {
        keyVaultName: deployTimeKeyVault.name                     
        objectId: userAssignedIdentityModule.outputs.principalId
    }
}

The “existing” keyword that indicates that the Key Vault already exists

Accessing properties of this Key Vault, in this case, to set permissions to that Key Vault

The preceding listing shows how you can use the existing keyword to reference a resource that should already exist by the time the deployment runs. The second block of code provisions a user assigned identity, and the third block grants get and list access to secrets and certificates on the already existing Key Vault by calling the accessPolicies.bicep module.

And with that, let’s conclude this chapter by adding a resource that integrates into the vnet. The following listing describes an API Management (APIM) instance (13.03/ApiManagement/service.bicep). This APIM instance will integrate into the vnet with a vnet type of Internal. This means that the APIM instance will only be reachable for resources inside the vnet. The vnet will block external traffic to APIM. This way, you are forcing external traffic to come in on the Application Gateway, which then routes traffic to APIM. Because Application Gateway has sophisticated tools for monitoring traffic, you can filter out malicious requests at a very early stage.

Listing 13.14 Provisioning APIM with vnet integration

param vnetResourceId string
param subnetName string = 'apim'
 
resource apim 'Microsoft.ApiManagement/service@2021-04-01-preview' = {
    location: resourceGroup().location
    name: resourceName
    identity: {
        type: 'SystemAssigned, UserAssigned'
        userAssignedIdentities: {
            '${existing_identity.id}': {}
        }
    }
    sku: {
        name: apimSkuName
        capacity: 1
    }
    properties: {
        publisherName: YourCompany Inc.'
        publisherEmail: '[email protected]'
        virtualNetworkType: 'Internal'               
        virtualNetworkConfiguration: {               
            subnetResourceId: '${vnetResourceId}/subnets/${subnetName}'
        }
        ...
    }
}

This is where the vnet type is set to “Internal”.

This is a reference to the subnet that APIM must be deployed in.

The preceding listing shows how to deploy API Management to integrate it into a vnet. We have omitted a couple of parameters and variables from the listing to keep this example readable. The module that describes the vnet outputs a full subscription resource ID that is passed in this Bicep file as the vnetResourceId parameter. The subnetName parameter (which defaults to apim) is used to define the subnet that API Management will be provisioned in.

And with that, you have read the final chapter of this book. Remember to clean up any resources you have created while trying things out, especially when working through this last chapter.

Thank you for reading our book! We hope you have enjoyed learning about Infrastructure as Code for Azure. Although the subject can be a bit dry and tough to master at times, you now have the tools you’ll need to make your life creating and operating software products on Azure a lot easier.

If you liked this book, please let us know by posting about it on Twitter or LinkedIn. We would love to hear about your learning stories. You can reach us at @henry_been, @ed_dotnet, or @erwin_staal. And for now: happy coding!

Summary

  • Templates can be nested in a hierarchical way to manage management groups, subscriptions, resource groups, and individual resources.

  • Subscription-level templates can be used to preconfigure subscriptions and set minimum standards and baselines.

  • You can organize your templates for a larger infrastructure, like a microservices architecture, into multiple templates that are deployed from a single orchestrating template.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.149.214.32