We have been working with the Microsoft-curated Azure Bicep Resource Modules (CARML) repository for over a year now, and while it is the next best thing after sliced bread, deciding on how to incorporate the modules into our workflow has taken some time.
Azure Bicep Resource Modules
The Resource Modules are atomic building blocks for Azure Infrastructure (as Code) – something akin to a Lego block – an implementation covering a single Azure resource and its services. They have some logic on configuring the resource and extending it with some accompanying, say, network services like Private Endpoints. The implementation follows the best practices of the Microsoft Well-Architected Framework and in essence is a reference implementation.
The modules are useful even if you are a seasoned IaC-professional, though in that case they might not provide as much productivity enchantment as for those less familiar with Azure, Bicep or IaC in general. Implementing Bicep from scratch with the help of things like VS Code extensions is relatively fast in any case, so using a pre-baked module only helps you so far. Still, they are very convenient to use.
For other scenarios, the modules are really useful – as long as you, or those above you in the food chain like the customer security apparatus – agree with the module implementation. On that front the pain for us in our current project is that we both need to provide both pre-baked Bicep modules for the rest of the business to use, and ensure that the modules implement the governance and security features that need to be imposed.
There are multiple ways of doing the impose-part in Azure (yes, like policies, for example), but for the sake of an argument, let’s say that we need to bake those expectations into the modules as well. This poses some problems that we have tried to solve with various levels of success. The CARML repository guidance lays down two scenarios on how to use the modules:
We’ll concentrate on consuming the modules here. If you are working in an Enterprise environment, the chances are that you won’t be forking a public Github repository on plain sight, which narrows the usage options to either downloading the repo, or downloading a zipped release of the repository assets.
Using modules as examples
We started our journey by just using the early version of CARML as an example implementation; we basically copied the source code to our repository and modified it as needed. We built our own pipelines with which we published the implementations to a Private Registry, and everything was pretty good for us and the few first modules we implemented, until we checked the CARML repo for changes after a few months.
It’s probably sufficent to say that a lot can happen in few months, and the module implementations had matured a lot, alongside with our own understanding on what to do with them. We then took a step forward and made the decision to use the Resource Modules as the base of our implementation, and keep up with their updates more promptly.
This, however, proved to be a bit tricky. First of all, we had a multirepo setup (due to demands from around us, not by choice – a great topic for another blog post), and we noticed that the CARML repository had a lot more intra-repo dependencies than before. We had, for example, implemented a different way of refencing the Private Endpoint implementation, but decided that it would probably be best to go along with the flow and just use the CARML way. At least we got another reason to switch to monorepo model.
However, the even if we would have gone all the way and do as, for example, the Landing Zone Vending module in public module registry repo does – just download the CARML code, stick it to a folder inside your repository – we would still have had a few problems to work around:
- Updating the modules would have required us to fetch and update the whole CARML folder structure with one big bang, which is time consuming.
- Or we would have had to do subfolders per CARML release.
- We would still need to change the module code to follow our guidelines, and when updating the code, would have had to do that again and again ad nauseaum.
Using Resource Modules with facade pattern
To solve these issues, at least to a satisfactory level, we decided to implement a facade modules for the Resource Modules. The setup is as follows:
- We have one monorepo for the ResourceModules, where we unzip the releases from CARML repository. We feel this is a more structured way of doing this than just fetching the newest state of main branch, or even working with CARML repo release tags, because we basically need to be aware of the change logs CARML ships with releases. More on this later.
- We have a second monorepo, let’s call it iac_modules, where we have our facade modules, pipelines and auxiliary stuff like custom PSRule rules.
- We configure the ResourceModules repository (main-branch) to be a Git submodule of the Modules repository.
- We then implement a facade module per ResourceModule, and reference a versioned implementation of a ResourceModule directly.
On code-level, we basically copy the parameter block from a Resource Module and copy it to a facade module. Then we might change some default values from parameters, to make the implementation compliant. Finally, we add a set of parameters for modules that implement resources that are supposed to be accesses via Private Endpoint.
These modules basically have a Boolean flag for wheter PEs should be enabled, some generic parameters like environments the workloads use, and possibility to override our generic Landing Zone network setup with a custom one. Again, for experienced developer things like PE setup work fine with the original Resource Module, but mere mortals might not be aware of how they should be configured – or should they be used at all.
Then we pass all the parameters we want to expose to the developers from facade to the Resource Module as-is, and hardcode the ones we don’t want to expose.
The most common change workload teams ask for us to implement in the modules are new output values. With the Resource Modules, you would have to modify the code to add output values, though in our usage we usually just referenced the deployed resource with existing -keyword and refenced the properties from there. With facades we actually implement the deployed resource as a submodule, and pull the output values from there. So for someone pulling our facade module from Private Registry those output values appear just as the original output values of the Resource Module.
There are, of course, some apparent drawbacks with the facade pattern. In the compiled ARM-template you can actually see two set of parameters – one for the facade and one for the original module. So far this hasn’t been a problem, and given that we’ve used a facade for the AKS and Storage modules, I doubt it will be anytime soon. Another downside is that deployment might take more time if there is implementation for existing resource. Third drawback is that when updating the modules you might actually miss a new parameter added to the Resource Module implementation, if that parameter has a default value.
Which brings us to the real reason we thought about the facades in first place: we wanted to simplify the process of taking new stable releases from CARML repository into use. The process for updating modules is now as follows:
- Download a new release from Github and unzip into a ResourceModule repository under it’s own folder (say, /0.10.0). This step could be automated, but haven’t bothered so far, since updating the modules does take some time and should be done with care.
- Change the bicep module reference in facade module to point from the earlier version to the new version.
- Check if the module parameters have changed in the new release. Breaking changes should actually show up in VS Code, as the reference from facade to Resource Module will break if the parameters have a breaking change. But at least new parameters with default values should be added to the facade as well. Also, it’s a good habit to read through the change logs to see if there’s anything else you should be aware of.
- Run a test deployment. We have copied and refactored the CARML module tests (usually the one deploying a PE), which we use for generating compliance reports with PS Rule (as it needs a complete implementation to work). At the moment we deploy the tests via CLI when needed, but this step would be easy and should be automated at some point, though then you have to clean up after the test deployments as well.
- Push the new module to Private Registry and notify interested parties.
This is what brings the most value out of the facade pattern – it simplifies updating the modules and makes the process easy to document and to teach to any would-be -maintainers of the module repositories.
The team is preparing CARML modules to be pushed into Microsoft public registry (so if you are already using them, do check this issue which lines out the changes on repo structure required), so in near future we need to evaluate how much business value the facade pattern brings, and wheter the compliance control could be implemented in any other way (they should be implemented in other ways in any case – we don’t have strict enforcement of using the modules, since that would make no sense towards experienced IaC developers). But so far the results with facade pattern have been good – and the path to them even better, even if we’ve had to refactor the implementation several times.
I originally called our approach to be a wrapper/decorator pattern, but was corrected by a more architecture pattern -oriented colleague that a facade pattern is a closer match. I’ve been scouting out for things related to modular IaC architecture, but there does not seem to be too much discussion around the subject around academic circles or internet (though it might just be my lack of google-fu), so I’ve basically had to default to software architecture patterns.
There is one exception, though, and that is a book called Infrastructure as Code (2nd edition) by Kief Morris, which I’ll shamelessly plug here if the subject matter is of interest. Morris specifically talks about different patterns (like facade) and anti-patterns (like obfuscation) regarding modular IaC code, which is super interesting and helpful when trying to wrap your head around these kind of things.