Azure, Deployment Environments, Professional

Azure Deployment Environments – Creating an Application Sandbox

Introduction

Application Deployment Environments can provide the ability to for developers to create a dedicated sandbox environment that deploys the infrastructure for their application. Previously I’ve discussed the Components to Azure Deployment Environments and the ability to create Resource Group sandboxes.

This post will cover the scenario where a development team owns an application and a developer would like to create their own instance of the application in Azure to deploy and test changes to. This environment is isolated from other developers and the developer may want additional access to be able to change various infrastructure settings. The complete source code for this can be found on my ToDo_AzureDeploymentEnvironment repository.

Prerequisites

This post already assumes you are familiar with the components of Azure Deployment Environments. It also will not go over how to import a Catalog as to save time and focus on the use case.

Within Azure the following should already be deployed and configured:

  • NoSQL Cosmos DB Account
    • Database named Tasks
    • Container named Item
  • Log Analytics Workspace
  • UID attached to the Environment Type has the ability to update RBAC roles on Cosmos DB

Application Architecture

To assist with this scenario I will provide an example of an application architecture a developer may be wanting to deploy. We are going to take the liberty and say there are shared components in this architecture. In this case the Application Insights will be tied to a shared Log Analytics Workspace as this is typical with most enterprise environments. Additionally, our application will be connected to a Cosmos DB. The instance of Cosmos should be maintained and deployed via a separate repository and pipeline. I say should here as data resources should be in their own resource group as the life cycle, scalability, and resource security will be different then our web components.

 

An application architecture

You can see in this architecture our ADE will have the following requirements

Our template for the ADE will match the one we use for production to ensure there is not drift or inconsistencies from the developer’s application sandbox and the instance running in a stable environment. An important item here is the deployment will be scoped at the Resource Group. That is because the ADE really is configured for Resource Group scoped deployments.

Bicep Registries

At the time of this writing ADE only supports ARM; however, we will leverage Bicep Registries and place the resulting ARM template as the Catalog. This approach could align with an infrastructure or operations team’s approach that developers can’t directly deploy resources into Azure. By creating the registry developers can leverage the templates their infrastructure team has already define as well as be responsible for what their main.bicep deploys.

For more information on how registries is utilized in this example check out my post on Bicep Registries.

Bicep File

Here is the main.bicep file that will be used to build the ARM template for the Catalog. As one can see we will import the Log Analytics and CosmosDB references as we will need those for Application Insights and App Service configuration.

@description('Location for all resources.')
param location string
@description('Base name that will appear for all resources.') 
param baseName string = 'adecosmosapp2'
@description('Three letter environment abreviation to denote environment that will appear in all resource names') 
param environmentName string = 'cicd'
@description('App Service Plan Sku') 
param appServicePlanSKU string = 'D1'
@description('Resource Group Log Analytics Workspace is in')
param logAnalyticsResourceGroup string 
@description('Log Analytics Workspace Name')
param logAnalyticsWorkspace string
@description('Resource Group CosmosDB is in')
param cosmosDBResourceGroup string
@description('CosmosDB Name')
param cosmosDBName string
@description('Dev Center Project Name')
param devCenterProjectName string = ''
@description('Name for the Azure Deployment Environment')
param adeName string =  ''


var regionReference = {
  centralus: 'cus'
  eastus: 'eus'
  westus: 'wus'
  westus2: 'wus2'
}

var language = 'Bicep'

//targetScope = 'subscription'
var nameSuffix = empty(adeName) ?  toLower('${baseName}-${environmentName}-${regionReference[location]}') : '${devCenterProjectName}-${adeName}'

resource logAnalytics 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = {
  name: logAnalyticsWorkspace
  scope: resourceGroup(logAnalyticsResourceGroup)
}

resource cosmosDB 'Microsoft.DocumentDB/databaseAccounts@2023-04-15' existing ={
  name: cosmosDBName
  scope: resourceGroup(cosmosDBResourceGroup)
}
module userAssignedIdentity 'br:acrbicepregistrydeveus.azurecr.io/bicep/modules/userassignedidentity:v1' ={
  name: 'userAssignedIdentityModule'
  params:{
    location: location
    userIdentityName: nameSuffix
  }
}

module appServicePlan 'br:acrbicepregistrydeveus.azurecr.io/bicep/modules/appserviceplan:v1' ={
  name: 'appServicePlanModule'
  params:{
    location: location
    appServicePlanName: nameSuffix
    language: language
    appServicePlanSKU: appServicePlanSKU
    appServiceKind: 'linux'
  }
}

module appService 'br:acrbicepregistrydeveus.azurecr.io/bicep/modules/appservice:v1' ={
  name: 'appServiceModule'
  params:{
    location: location
    appServicePlanID: appServicePlan.outputs.appServicePlanID
    appServiceName: nameSuffix
    principalId: userAssignedIdentity.outputs.userIdentityResrouceId
    appSettingsArray: [
      {
       name:'APPINSIGHTS_INSTRUMENTATIONKEY'
       value: appInsights.outputs.appInsightsInstrumentationKey
    }
    {
      name: 'CosmosDb:Account'
      value: 'https://${cosmosDB.name}.documents.azure.com:443/'
    }
    {
      name: 'CosmosDb:DatabaseName'
      value: 'Tasks'
    }
    {
      name: 'CosmosDb:ContainerName'
      value: 'Item'
    }
    {
      name: 'WEBSITE_RUN_FROM_PACKAGE'
      value: '1'
    }
    {
      name: 'SCM_DO_BUILD_DURING_DEPLOYMENT'
      value: 'true'
    }
    {
      name: 'ApplicationInsightsAgent_EXTENSION_VERSION'
      value: '~2'
    }
  ]
  }
}


module appInsights 'br:acrbicepregistrydeveus.azurecr.io/bicep/modules/appinsights:v1' ={
  name: 'appInsightsModule'
  params:{
    location: location
    appInsightsName: nameSuffix
    logAnalyticsWorkspaceID: logAnalytics.id
    language: language
  }
}

module cosmosRBAC 'br:acrbicepregistrydeveus.azurecr.io/bicep/modules/cosmossqldbroleassignment:v1' ={
  name: 'cosmosRBACModule'
  scope: resourceGroup(cosmosDBResourceGroup)
  params: {
    databaseAccountName: cosmosDB.name
    databaseAccountResourceGroup: cosmosDBResourceGroup
    principalId: appService.outputs.appServiceManagedIdentity
  }
}


Manifest YAML File

The manifest.yml file for the ADE will need to accept the inputs defined outside of the main template. This also would include the resource group and resource names for the Log Analytics Workspace and CosmosDB as mentioned and seen above these are required for importing the resources.

This structure also means that the ADE is detached from any backend datbase. This is a good design principle and now it allow us to deploy this application against any instance of the CosmosDB.

The complete YAML can be found on my public repository.

# yaml-language-server: $schema=https://github.com/Azure/deployment-environments/releases/download/2022-11-11-preview/manifest.schema.json
name: DemoApp
version: 1.0.0
summary: Architecture for self contained App Service
description: Deploys app service plan, app service, and application Insights
runner: ARM
templatePath: main.json

parameters:
  - id: baseName
    name: Base Name for resources
    description: 'Name of the service'
    type: string
    required: true
  - id: location
    name: Location for Resources
    description: 'Location to deploy new resources into default is eastus'
    type: string
    required: false
  - id: logAnalyticsResourceGroup
    name: Resource Group for Log Analytics
    description: 'The Resource Group the existing Log Analytics is deployed in'
    type: string
    required: true
  - id: logAnalyticsWorkspace
    name: Log Analytics Workspace
    description: 'The Resource name for an existing Log Analytics Workspaces'
    type: string
    required: true
  - id: cosmosDBResourceGroup
    name: Resource Group for CosmosDB
    description: 'The Resource Group the existing CosmosDB is deployed in'
    type: string
    required: true
  - id: cosmosDBName
    name: Cosmos DB Name
    description: 'The Resource name for an existing Cosmos Database'
    type: string
    required: true

After attaching the catalog we should the inputs look like the following when deploying the ADE via the devportal.

Screenshot of the Deployment Environment Inputs

End Result

Our ADE will now take these inputs and create a Resource Group in the Subscription attached to our Environment Type and provision the environment create and any additional access as defined by the Environment Type.

Our end result is that we now have an entire application infrastructure that has been deployed in an ADE for developer(s) to use. This instance isolated from others, yet shares some of the main components.

Screenshot of resources created by the ADE

Conclusion

ADE’s provide development teams the ability to create temporary infrastructure environments in Azure to deploy their application to. This process, depending on configuration, can really empower development teams to own the infrastructure deployed while also balancing control of the infrastructure templates to the operations team.

For more information on this topic feel free to check out my additional blogs: