Addressing configuration drift using Deployment Stacks
Stephen Tulp
December 20, 2023
12 minutes to read
Introduction
In this blog post, we will look at one of the things that I am looking forward to in 2024, a preview capability called deployment stacks, which is the next evolution of Azure Blueprints
that never really took off and was preview for way too long before it was eventually deprecated. Deployment Stacks isn’t specifically related to Bicep but can also be used for ARM JSON templates and Template Specs as well.
Deployment stacks are currently in preview and are not recommended for production use at this stage.
What are Deployment Stacks?
Deployment stacks are a type of Azure resource that allow you to manage a set of Azure resources as a single unit. When a Bicep file or an ARM JSON template is submitted to a deployment stack, it defines the resources that are managed by the stack.
Why use deployment stacks?
Deployment stacks provide the following benefits:
- Simplify provisioning and management of resources across different scopes, that are treated like a single deployment.
- Preventing undesired modifications to managed resources through deny settings.
- Efficient environment cleanup by employing delete flags during deployment stack updates.
- Utilizing standard templates such as Bicep, ARM templates, or Template specs for the deployment stacks.
- Define specific actions and Service Principals that can perform changes on the stack.
- Management group scoped deployment stacks to deploy resources to multiple subscriptions.
Known issues
As this is a preview features there are some known issues and limitations, for me a lot of these are the main reason I haven’t invested more time in deployment stacks at this stage because my main uses cases touches one or more of these.
- Deleting resource groups currently bypasses deny assignments.
- Implicitly created resources aren’t managed by the stack. Therefore, no deny assignments or cleanup is possible.
- THe
What-if
command isn’t available yet. - Management group scoped deployment stacks can only deploy the template to subscription.
- If you create or modify a deployment stack in the Azure portal, deny settings will be overwritten.
- Management group deployment stacks are not yet available in the Azure portal.
Deployment Stack Examples
We will look at a few of the common scenarios that you might use deployment stacks for.
- Create a deployment stack
- List a deployment stack
- Update a deployment stack
- Delete a deployment stack
We have an example Main.bicep
file that we will use for the examples below.
param location 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: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
resource virtualNetwork 'Microsoft.Network/virtualNetworks@2022-11-01' = {
name: vnetName
location: location
properties: {
addressSpace: {
addressPrefixes: [
'10.0.0.0/16'
]
}
subnets: [
{
name: 'Subnet-1'
properties: {
addressPrefix: '10.0.0.0/24'
}
}
]
}
}
Create a deployment stack
Deployment Stacks can be created at the Resource Group, Subscription or Management Group level. In this example, we will create a deployment stack at the Resource Group level.
New-AzResourceGroupDeploymentStack `
-Name "exampleStack" `
-ResourceGroupName "exampleRg" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none"
The DenySettingsMode switch assigns a specific type of permissions to the managed resources, which prevents their deletion by unauthorized security principals
List a deployment stack
To list the example deployed deployment stack:
Get-AzResourceGroupDeploymentStack `
-ResourceGroupName "exampleRg" `
-Name "exampleStack"
The output shows two managed resources - one storage account and one virtual network:
Id : /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/exampleRg/providers/Microsoft.Resources/deploymentStacks/exampleStack
Name : exampleStack
ProvisioningState : succeeded
ResourcesCleanupAction : detach
ResourceGroupsCleanupAction : detach
DenySettingsMode : none
CreationTime(UTC) : 18/12/2023 8:55:48 PM
DeploymentId : /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/exampleRg/providers/Microsoft.Resources/deployments/exampleStack-2023-18-12-20-55-48-38d09
Resources : /subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/exampleRg/providers/Microsoft.Network/virtualNetworks/vnetzu6pnx54hqubm
/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/exampleRg/providers/Microsoft.Storage/storageAccounts/storezu6pnx54hqubm
Update a deployment stack
To update a deployment stack, make the necessary modifications to the underlying Bicep file, and then rerun the command for creating the deployment stack or use the set command in Azure PowerShell.
- Update a property of a managed resource.
- Add a resource to the stack.
- Detach a managed resource.
- Attach an existing resource to the stack.
- Delete a managed resource.
Update a property of a managed resource
We will now update the Sku
to be Standard_GRS
instead of Standard_LRS
for the storage account.
resource storageAccount 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: storageAccountName
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_GRS'
}
}
We can update the deployment stack.
Set-AzResourceGroupDeploymentStack `
-Name "exampleStack" `
-ResourceGroupName "exampleRg" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none"
If we list the stack now we should see the changes.
Add a resource to the stack
We are going to add another storage account to the stack.
resource storageAccount1 'Microsoft.Storage/storageAccounts@2022-09-01' = {
name: '1${storageAccountName}'
location: location
kind: 'StorageV2'
sku: {
name: 'Standard_LRS'
}
}
We can update the deployment stack.
Set-AzResourceGroupDeploymentStack `
-Name "exampleStack" `
-ResourceGroupName "exampleRg" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none"
If we list the stack now we should see the changes. This will include the new storage account in addition to the two existing resources.
(Get-AzResourceGroupDeploymentStack -Name "exampleStack" -ResourceGroupName "exampleRg").Resources
Detach a managed resource
Now lets remove that StorageAccount1
resource from the stack. Remove the resource from the Bicep file and then update the deployment stack.
Set-AzResourceGroupDeploymentStack `
-Name "exampleStack" `
-ResourceGroupName "exampleRg" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none"
If we verify the stack again we should see that the StorageAccount1
resource has been detached but not deleted.
(Get-AzResourceGroupDeploymentStack -Name "exampleStack" -ResourceGroupName "exampleRg").Resources
There are 2 managed resources in the stack. However, the detached resource is still listed in the resource group. We can see all the resources in the resource group by running the following command, which shows 3 resources, even though there are only 2 in the stack.
Get-azResource -ResourceGroupName "exampleRg"
Attach an existing resource to the stack
We now have one stack with 2 managed resources and one detached resource. We can attach the detached resource back into the stack, we need to add the storage account back into the Bicep file and then update the deployment stack.
Set-AzResourceGroupDeploymentStack `
-Name "exampleStack" `
-ResourceGroupName "exampleRg" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none"
If we verify the stack again we should see that the StorageAccount1
resource has been added back into the stack.
(Get-AzResourceGroupDeploymentStack -Name "exampleStack" -ResourceGroupName "exampleRg").Resources
Delete a managed resource
We can delete a managed resource from the stack by removing it from the Bicep file and then updating the deployment stack. We will know remove the storageAccount1
resource from the Bicep file.
Set-AzResourceGroupDeploymentStack `
-Name "exampleStack" `
-ResourceGroupName "exampleRg" `
-TemplateFile "./main.bicep" `
-DenySettingsMode "none" `
-DeleteResources
We have used the DeleteResources
switch, there are two other switches available: DeleteAll
and DeleteResourceGroups
. Details on the switches are below:
DeleteResources
- Deletes all resources that are not managed by the stack.DeleteAll
- Deletes all resources in the stack, including those that are not managed by the stack.DeleteResourceGroups
- Deletes all resources in the stack and the resource group.
Configure deny settings
When creating a deployment stack, it is possible to assign a specific type of permissions to the managed resources, which prevents their deletion by unauthorized security principals. These settings are refereed as deny settings
.
DenySettingsMode:
Defines the operations that are prohibited on the managed resources to safeguard against unauthorized security principals attempting to delete or update them. This restriction applies to everyone unless explicitly granted access. The values include:None
,DenyDelete
, andDenyWriteAndDelete
.DenySettingsApplyToChildScopes:
Deny settings are inherited by child Azure management scopes.DenySettingsExcludedActions:
List of RBAC operations that are excluded from the deny settings. You can have up to 200 actions per stack.DenySettingsExcludedPrincipals:
List of Microsoft Entra Service Principals that are excluded from the lock. You can have up to five principals per stack.
Conclusion
Whilst still in early days and not recommended for production use, I am looking forward to seeing how deployment stacks evolve and how they can be used to manage resources in Azure. I am hoping that they will be able to be used to manage resources in Azure in a similar way to how Terraform does things and be able to manage configuration drift of infrastructure deployments.