Azure infrastructure lifecycle management through deployment stacks
What are deployment stacks?
Azure infrastructure engineers, who had used Terraform, was always having complain related to lifecycle management of Azure infrastructure deployment using bicep or ARM template. There were no such cloud native way, through which you could maintain a group of deployments as well as tearing down the infra on-demand basis. Here comes the awesome deployment stack, which gives you the liberty to control the lifecycle of different resources with ease under the same umbrella like resource group.
A deployment stack is a native Azure resource type that enables you to perform operations on a resource collection as an atomic unit. Deployment stacks are defined in ARM as the type Microsoft.Resources/deploymentStacks.
Because the deployment stack is a native Azure resource, you can perform all typical Azure Resource Manager (ARM) operations on the resource.
Azure resources created using a deployment stack are managed by the deployment stack itself, which means that to update a resources, you update the deployment stack rather that individual resources, and clean up the environment (resources) can (should) also be done by updating the deployment stack.
Note: This feature is still in public preview, but without a doubt, it will be a popular one.
Microsoft Documentation
Its highly recommended to go through the Microsoft Documentation before staring your journey. Refer the documentation.
Document Purpose
In this small documentation, we'll try to figure out how to leverage deployment stack with Bicep as well as ARM Templates.
Document Scope
Deployment stack can be used through Portal, Azure CLI & Azure PowerShell. In this document we'll focus on the PowerShell part.
You can easily get all the commands available till date using the below cmdlet
Get-Command -Module 'Az.Resources' | Where-Object { $_.Name.tolower().contains('deploymentstack') }
Working through Bicep
Let's create a Bicep file same as documentation and name it as main.bicep
param resourceGroupLocation string = resourceGroup().locatio
param storageAccountName string = 'store${uniqueString(resourceGroup().id)}'
param vnetName string = 'vnet${uniqueString(resourceGroup().id)}'
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: resourceGroupLocation
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-11-01' = {
name: vnetName
location: resourceGroupLocation
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'Subnet-2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
]
}
}n
New-AzResourceGroupDeploymentStack
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none"`
-Verbose
Deployment stack should be created in portal
To verify the deployment you can easily use the below command
Get-AzResourceGroupDeploymentStack
-ResourceGroupName "rg-deployment-stack" `
-Name "demoDeploymentStack"`
Let's modify the bicep file to have resource group location as input parameter
param resourceGroupLocation string
param storageAccountName string = 'store${uniqueString(resourceGroup().id)}'
param vnetName string = 'vnet${uniqueString(resourceGroup().id)}'
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: resourceGroupLocation
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-11-01' = {
name: vnetName
location: resourceGroupLocation
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'Subnet-2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
{
name: 'Subnet-3'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
]
}
}
we can easily pass the parameter object like we use for normal deployment cmdlets
New-AzResourceGroupDeploymentStack `
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack" `
-TemplateFile ".\main.bicep" `
-DenySettingsMode "none" -Verbose -TemplateParameterObject @{ resourceGroupLocation = 'eastus' }
Let's update the main.bicep with one more subnet
param resourceGroupLocation string = resourceGroup().locatio
param storageAccountName string = 'store${uniqueString(resourceGroup().id)}'
param vnetName string = 'vnet${uniqueString(resourceGroup().id)}'
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: resourceGroupLocation
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-11-01' = {
name: vnetName
location: resourceGroupLocation
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'Subnet-2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
{
name: 'Subnet-3'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
]
}
}
Cmdlet to update the deployment stack
Set-AzResourceGroupDeploymentStack
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack" `
-TemplateFile ".\main.bicep" `
-DenySettingsMode "none" -Verbose`
If you only want to detach the resources and remove the deployment stack, run the below command
Remove-AzResourceGroupDeploymentStack
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack"`
If you want to delete the deployment stack as well as the resources, use the below command
Remove-AzResourceGroupDeploymentStack
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack" `
-DeleteResources`
The following parameters can be used to control between detach and delete.
Recommended by LinkedIn
Before moving to the usage of ARM template, let's have couple of experiments
Lets remove the storage account from our bicep template and update the deployment stack
param resourceGroupLocation string = resourceGroup().location
param vnetName string = 'vnet${uniqueString(resourceGroup().id)}'
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-11-01' = {
name: vnetName
location: resourceGroupLocation
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'Subnet-2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
{
name: 'Subnet-3'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
]
}
}
Update the deployment stack now
Set-AzResourceGroupDeploymentStack `
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack" `
-TemplateFile ".\main.bicep" `
-DenySettingsMode "none" -Verbose
Let's check the portal
param resourceGroupLocation string = resourceGroup().location
param storageAccountName string = 'store${uniqueString(resourceGroup().id)}'
param vnetName string = 'vnet${uniqueString(resourceGroup().id)}'
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: resourceGroupLocation
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-11-01' = {
name: vnetName
location: resourceGroupLocation
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
{
name: 'Subnet-2'
properties: {
addressPrefix: '10.0.1.0/24'
}
}
{
name: 'Subnet-3'
properties: {
addressPrefix: '10.0.2.0/24'
}
}
]
}
}
New-AzResourceGroupDeploymentStack `
-Name "demoDeploymentStack" `
-ResourceGroupName "rg-deployment-stack" `
-TemplateFile ".\main.bicep" `
-DenySettingsMode "none" -Verbose
let's do some modification now in the storage account properties and update the deployment stack to check whether or not deployment stack is reverting the changes.
Lets update the deployment stack with previous Bicep file and check what happens to the manual changes.
Interestingly, no changes are made to the properties updated manually.
Working through ARM Template
Deployment stack using ARM template can only be done through Template Specs, don't worry if you are new in Template Specs, let's discuss the template specs in brief.
Azure Template Specs take the power of Azure Resource Manager templates a step further by allowing you to package and version your templates along with other artifacts, such as parameter files and policies, into a single shareable and reusable entity. Think of it as a comprehensive blueprint that encapsulates not only the infrastructure definition but also any associated scripts, extensions, and documentation required for a successful deployment.
Let's create a simple ARM template and store it as singleTemplate.json
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"allowedValues": [
"Standard_LRS",
"Standard_GRS",
"Standard_ZRS",
"Premium_LRS"
]
}
},
"resources": [
{
"type": "Microsoft.Storage/storageAccounts",
"apiVersion": "2019-06-01",
"name": "[concat('sa', uniquestring(resourceGroup().id))]",
"location": "[resourceGroup().location]",
"kind": "StorageV2",
"sku": {
"name": "[parameters('storageAccountType')]"
}
}
]
}
Through Azure PowerShell the template specs can be deployed
New-AzTemplateSpec `
-Name TemplateSpecSingleARM `
-Version "1.0" `
-ResourceGroupName 'rg-deployment-stack' `
-Location eastus `
-TemplateFile '.\singleTemplate.json' -Verbose
We can check the template specs in the portal
Let's first create a master template (lets name it azuredeploy.json) which refers to another ARM Template
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"location": {
"type": "string",
"defaultValue": "westus2",
"metadata":{
"description": "Specify the location for the resources."
}
},
"storageAccountType": {
"type": "string",
"defaultValue": "Standard_LRS",
"metadata":{
"description": "Specify the storage account type."
}
}
},
"variables": {
"appServicePlanName": "[format('plan{0}', uniquestring(resourceGroup().id))]"
},
"resources": [
{
"type": "Microsoft.Web/serverfarms",
"apiVersion": "2022-09-01",
"name": "[variables('appServicePlanName')]",
"location": "[parameters('location')]",
"sku": {
"name": "B1",
"tier": "Basic",
"size": "B1",
"family": "B",
"capacity": 1
},
"kind": "linux",
"properties": {
"perSiteScaling": false,
"reserved": true,
"targetWorkerCount": 0,
"targetWorkerSizeId": 0
}
},
{
"type": "Microsoft.Resources/deployments",
"apiVersion": "2022-09-01",
"name": "createStorage",
"properties": {
"mode": "Incremental",
"templateLink": {
"relativePath": "artifacts/linkedTemplate.json"
},
"parameters": {
"storageAccountType": {
"value": "[parameters('storageAccountType')]"
}
}
}
}
]
}
In the same location, lets create a new folder named "artifacts" and inside that lets create a simple template (contents mentioned below) named "linkedTemplate.json"
Let's create the Template Specs
New-AzTemplateSpec `
-Name TemplateSpecMultipleARM `
-Version "1.0" `
-ResourceGroupName 'rg-deployment-stack' `
-Location eastus `
-TemplateFile '.\azuredeploy.json' -Verbose
We can check the Template Specs in portal
Before moving back to the deployment stack, let's quickly explore the versioning feature of Template Specs.
Lets deploy one more version of the Template Spec using single ARM template
New-AzTemplateSpec `
-Name TemplateSpecSingleARM `
-Version "1.1" `
-ResourceGroupName 'rg-deployment-stack' `
-Location eastus `
-TemplateFile '.\singleTemplate.json' -Verbose
In the portal, we'll be able to see both the versions and Azure gives us opportunity to deploy any versions. By default, latest version will be selected
Coming back to Deployment Stack, lets try to deploy Deployment Stack using template specs
$templateSpecId = (Get-AzTemplateSpec `
-Name TemplateSpecMultipleARM `
-Version "1.0" `
-ResourceGroupName 'rg-deployment-stack' ).Versions.id
Let's deploy the Deployment Stack
New-AzResourceGroupDeploymentStack `
-Name 'demoDeploymentStackTemplateSpec' `
-ResourceGroupName 'rg-deployment-stack' `
-TemplateSpecId $templateSpecId `
-DenySettingsMode none
Hope you find this blog interesting and exciting.
Well explained Suvadip!