Have you ever wondered why you can’t create some Azure resources using ARM or Bicep, or it’s not present in the Pulumi API, but sometimes you can do it in Terraform, or just not? If you work with Infrastructure as Code, you need to understand the difference between the Control Plane and Data Plane.
These two planes represent two different types of operations in Azure.
Most top-level Azure resources fall under the control plane. This has a single URL of mangement.azure.com for all control plane operations (with additional URLs for regional clouds). The control plane is what you are calling when you create top-level Azure resources via ARM, Bicep, Terraform or Pulumi. Control plane resources include:
- Virtual Machines
- AKS clusters
- Storage Account
- RBAC Permissions
Authentication to the control plane is done via Azure Active directory and permissions come from Azure RBAC.
Most of what we do with IaC is done on the control plane.
The data plane mainly performs operations on resources created through the control plane. Unlike the control plane, which has a single URL, each data plane operation will go to a resource-specific URL to talk to the specific resource. Authentication may be done with Azure AD, but it might be done through a resource-specific method.
Let’s look at a common resource you interact with on both planes, a storage account. When you create a storage account, you use the control plane to send a create request to the Azure Resource Manager API, which then provisions the storage account. However, if you want to put some blobs into the storage account, you can’t do this through the control plane; you need to use the data plane. The URL you interact with to use the data plane is the storage account URL, and the authentication can be done using resource-specific credentials (storage account keys or SaS tokens).
Some other examples of data plane operations:
- Storage Blob
- App Config Value
- SQL Table
- ACR Container
Impact on Infrastructure as Code
So what does all this mean for us when creating resources using Infrastructure as Code? It depends on the tool you’re using. Most IaC languages are auto-generated and use the REST API for the control plane. This includes ARM, Bicep and Pulumi (native provider). This means that these languages only have access to resources in the control plane. The exception is Terraform, which does not use auto-generation; instead, the language is handcrafted for each resource. This provides a layer of abstraction where extra methods can be added that interact with the data plane. Not all resources have data plane interaction in Terraform, but some do.
The lack of access to the data plane isn’t a huge deal for most resources. You don’t often want to create a Cosmos Database in your Bicep template and then create records in it. However, there are some areas that can make things a bit more tricky. Key Vault is a prime example of this; the control plane API provides the ability to create secrets but not to read them. This can be a pain if you want to read an existing secret in your template. ARM/Bicep works around this with some special methods in the parameter file, but for Pulumi, it can be problematic.
Dealing with Data Plane Operations
Suppose you do need to undertake data plane operations as part of your infrastructure as code. In that case, you need to look at finding a way to talk to the data plane in your code, which usually involves calling out to a separate operation:
- For ARM or Bicep, you can use deployment scripts to run some PowerShell or CLI scripts that can call the data plane API
- For Terraform, look to see if the resource supports data plane operations; if not, you can use local exec to call out PowerShell or CLI.
- For Pulumi, you can call the data plane API or SDK for the specific resource directly from whatever language you use.
- If you’re automating your IaC deployments using a pipeline tool like Azure DevOps, Jenkins, etc., then you can use this to run a task after your IaC deployment to run some PowerShell/CLI.