<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Andrew Matveychuk]]></title><description><![CDATA[Hey! I'm an Azure and DevOps enthusiast, PowerShell chaos monkey, avid reader and blogger. Helping people move to the Cloud.]]></description><link>https://andrewmatveychuk.com</link><generator>RSS for Node</generator><lastBuildDate>Sat, 18 Apr 2026 16:19:56 GMT</lastBuildDate><atom:link href="https://andrewmatveychuk.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Optimizing Compute Costs in Azure (Webinar)]]></title><description><![CDATA[I recently presented on a webinar hosted by the Turbo360 team and Michael Stephenson, the host of Azure on Air and FinOps on Azure podcasts. There, we discussed the most common mistakes in optimizing compute costs in Azure and how to avoid them, the ...]]></description><link>https://andrewmatveychuk.com/optimizing-compute-costs-in-azure-webinar</link><guid isPermaLink="true">https://andrewmatveychuk.com/optimizing-compute-costs-in-azure-webinar</guid><category><![CDATA[finops]]></category><category><![CDATA[Azure]]></category><category><![CDATA[cost-optimisation]]></category><category><![CDATA[webinar]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 05 Aug 2025 09:58:30 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1754387785561/482ec8b9-197e-4570-8798-a5633a80d22d.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I recently presented on a webinar hosted by the <a target="_blank" href="https://turbo360.com/webinar/optimizing-compute-costs-in-azure">Turbo360</a> team and <a target="_blank" href="https://mikestephenson.me/">Michael Stephenson</a>, the host of <a target="_blank" href="https://turbo360.com/podcast/category/azure-on-air">Azure on Air</a> and <a target="_blank" href="https://turbo360.com/podcast/category/finops-on-azure">FinOps o</a><a target="_blank" href="https://turbo360.com/podcast/category/azure-on-air">n Azure</a> podcasts. There, we discussed the most common mistakes in optimizing compute costs in Azure and how to avoid them, the challenges of cost optimization at enterprise scale and how to address them with proper monitoring, automation and capacity management strategies. Additionally, we explored the hidden downsides of cost overoptimization, which can negatively impact the availability and security of your Azure applications.</p>
<p>If you missed it, please feel free to catch up on that topic by watching the webinar recording:</p>
<div class="embed-wrapper"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a class="embed-card" href="https://turbo360.com/webinar/optimizing-compute-costs-in-azure">https://turbo360.com/webinar/optimizing-compute-costs-in-azure</a></div>
<p> </p>
<p>Please feel free to ask any questions regarding the webinar topic or cost optimization in Azure in general in the comments 💬</p>
<p>I cannot promise you easy answers, but I will do my best to set you on the right path in your cost optimization and FinOps journey 😉</p>
]]></content:encoded></item><item><title><![CDATA[WordPress as a Radius application]]></title><description><![CDATA[I’ve been watching the development of Radius, a new application hosting platform that originated in the Microsoft Azure Incubations team and later became a CNCF project, for a while, and I finally managed to dedicate some time to trying it in practic...]]></description><link>https://andrewmatveychuk.com/wordpress-as-a-radius-application</link><guid isPermaLink="true">https://andrewmatveychuk.com/wordpress-as-a-radius-application</guid><category><![CDATA[Radius platform]]></category><category><![CDATA[radapp]]></category><category><![CDATA[WordPress]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Wed, 25 Jun 2025 15:54:35 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862355409/c2d18432-63b1-4265-84d0-9d710e8eebed.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I’ve been watching the development of <a target="_blank" href="https://radapp.io/">Radius</a>, a new application hosting platform that <a target="_blank" href="https://azure.microsoft.com/en-us/blog/the-microsoft-azure-incubations-team-launches-radius-a-new-open-application-platform-for-the-cloud/">originated in the Microsoft Azure Incubations team</a> and later became a <a target="_blank" href="https://www.cncf.io/projects/radius/">CNCF project</a>, for a while, and I finally managed to dedicate some time to trying it in practice.</p>
<p>Radius is still early in development, but it can be an interesting option for exploring <a target="_blank" href="https://learn.microsoft.com/en-us/platform-engineering/what-is-platform-engineering">platform engineering</a> practices. One way to see how promising it might be is to try deploying a simple application on it. In this post, I will try to show you how to run WordPress locally as a Radius application.</p>
<blockquote>
<p>I recommend visiting the <a target="_blank" href="https://docs.radapp.io/concepts/why-radius/introduction/">Radius official website</a> to learn more about how it differs from Kubernetes and what challenges it can help solve. It has plenty of guides and tutorials to help you get started with the Radius platform and run your applications on it.</p>
</blockquote>
<h1 id="heading-installing-radius-locally">Installing Radius locally</h1>
<p>First, we must <a target="_blank" href="https://docs.radapp.io/installation/">install and configure the required Radius components</a>: the Radius command line (rad CLI) and the Radius control plane. While the <strong>rad CLI</strong> can be installed locally on your machine, the platform control plane runs as a Kubernetes application. The initialization process for the Radius control plane using rad CLI is pretty straightforward, and you can use your preferred Kubernetes setup either locally or as a cloud service.</p>
<p>After the basic setup is finished, you can check your Radius installation status via the command line:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862372358/bca59dea-99f8-4276-8c08-3e64586561cc.png" alt class="image--center mx-auto" /></p>
<p>Later, when you deploy your Radius applications, you can also use the <a target="_blank" href="https://docs.radapp.io/guides/tooling/dashboard/overview/">Radius Dashboard</a> to check for all system properties:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862412595/684c1fc8-1187-434b-91b2-92e3ed8bf9ac.png" alt class="image--center mx-auto" /></p>
<p>Now, let’s take a step back for a moment and check how applications are defined in Radius.</p>
<h1 id="heading-understanding-radius-primitives">Understanding Radius primitives</h1>
<p>When you deployed the Radius control plane using the rad command line, you essentially initialized your first Radius environment (the <em>default</em> one). A <a target="_blank" href="https://docs.radapp.io/guides/deploy-apps/environments/overview/">Radius Environment</a> is an abstraction of your infrastructure to run your Radius-defined applications using predefined configurations and infrastructure components defined via <a target="_blank" href="https://docs.radapp.io/guides/recipes/overview/">Radius Recipes</a>. Your Radius environments can include <a target="_blank" href="https://docs.radapp.io/guides/operations/providers/">cloud providers</a> like Microsoft Azure and Amazon Web Services, as well as <a target="_blank" href="https://docs.radapp.io/guides/deploy-apps/environments/overview/#external-identity-provider">external identity providers</a> (Microsoft Entra Workload ID only for now).</p>
<p>You can combine multiple Radius environments into <a target="_blank" href="https://docs.radapp.io/guides/operations/workspaces/overview/">Radius Workspaces</a>, which are defined locally on the client side, to switch between different application contexts easily.</p>
<p>Radius Recipes abstract the configuration of infrastructure components in specific Radius environments. Your <a target="_blank" href="https://docs.radapp.io/guides/author-apps/application/overview/">Radius Application</a> uses Recipes and defines dependencies and relationships between your application components.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862434555/53693a48-68d8-4590-ade5-94c78a159258.png" alt class="image--center mx-auto" /></p>
<p>It might take you some time to grasp those Radius concepts, and it might be easier to do so while defining and deploying our demo application with Radius.</p>
<h1 id="heading-defining-a-radius-application-for-wordpress">Defining a Radius application for WordPress</h1>
<p>With Radius, you can <a target="_blank" href="https://docs.radapp.io/tutorials/new-app/">define and run your applications</a> using Azure Bicep, Terraform, and even Helm charts. For the sake of this demo, I will define our WordPress application using Bicep.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="0dc142bb885c6acb03eb266d1d4e8589"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/0dc142bb885c6acb03eb266d1d4e8589" class="embed-card">https://gist.github.com/andrewmatveychuk/0dc142bb885c6acb03eb266d1d4e8589</a></div><p> </p>
<p>Here, we are deploying WordPress as a container that will run locally in our default environment as a Kubernetes service. However, that won’t be enough to have a functional WordPress website, as <a target="_blank" href="https://wordpress.org/about/requirements/">it requires a database to run</a>.</p>
<p>Now, let’s add a backend database here. To do so, we can <a target="_blank" href="https://docs.radapp.io/guides/author-apps/portable-resources/howto-author-portable-resources/">create a custom Bicep Recipe</a> to provide our Radius application with an infrastructure abstraction to use. In my case, I chose to create a Radius recipe for a MySQL database:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="93d714be53ec9e625364e0e64e227fd5"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/93d714be53ec9e625364e0e64e227fd5" class="embed-card">https://gist.github.com/andrewmatveychuk/93d714be53ec9e625364e0e64e227fd5</a></div><p> </p>
<p>Looking at that code, you might notice that creating custom Radius recipes can be a bit challenging. Usually, a platform team abstracts the infrastructure management complexity from developers.</p>
<p>As that recipe is intended for local development, the MySQL service also runs as a container. Plus, we are exposing it as a Kubernetes service so that our WordPress resource can connect to it.</p>
<p>After you import that recipe into your environment, you should be able to see it in the list of available recipes to use:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862458342/575f3e40-53cf-448f-a860-4b1601933df8.png" alt class="image--center mx-auto" /></p>
<p>Now, we can update our application definition to leverage that local recipe:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3340bd04b82fb3339a7d0367f27b8074"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/3340bd04b82fb3339a7d0367f27b8074" class="embed-card">https://gist.github.com/andrewmatveychuk/3340bd04b82fb3339a7d0367f27b8074</a></div><p> </p>
<p>If we put ourselves in the developer's shoes, we can see that provisioning the database resource is quite easy, as all the nuances of its configuration are encapsulated in the corresponding Radius recipe.</p>
<blockquote>
<p>For the complete code of this project, please feel free to check my <a target="_blank" href="https://github.com/andrewmatveychuk/radius.demo"><strong>radius.demo</strong> project on GitHub</a>.</p>
</blockquote>
<h1 id="heading-checking-how-it-works">Checking how it works</h1>
<p>Let’s run our updated application definition and check the database configuration in WordPress:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862475966/b6eba065-a9fd-4c22-8347-f4369a536992.png" alt class="image--center mx-auto" /></p>
<p>As you can see, it refers to our MySQL server using the internal Kubernetes name service created as part of the MySQL resource deployment with the recipe we created.</p>
<p>The <a target="_blank" href="https://docs.radapp.io/guides/author-apps/application/overview/#query-and-understand-your-application-with-the-radius-application-graph">Radius Application Graph</a> will also show us the application layout with the MySQL resource dependency:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862529830/29ae0923-0b09-4718-84b7-0eff9f145d93.png" alt class="image--center mx-auto" /></p>
<p>So good, so far. Our simple WordPress application is up and running on Radius.</p>
<p>Now, let’s look at how our resources are represented at the Radius control plane:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750862548999/024cb058-9a5f-46c1-97cd-dd58a01362d3.png" alt class="image--center mx-auto" /></p>
<p>As you might have already noticed, we provide the database connection properties for the WordPress container as environment variables. The downside here is that our database credentials are exposed in plain text, which is not ideal. Unfortunately, the secret management on the Radius platform is far from being production-ready. The documentation is limited to <a target="_blank" href="https://docs.radapp.io/guides/author-apps/secrets/overview/">using secret stores for certificate management</a> and to <a target="_blank" href="https://docs.radapp.io/guides/author-apps/containers/volume-keyvault/">working with Azure Key Vault</a>. <a target="_blank" href="https://github.com/radius-project/radius/issues/8220">Passing the secrets as parameters to resources</a> and injecting them back into your application without exposing them is still problematic.</p>
<p>Apart from that, our basic WordPress setup in the mentioned configuration is fully ephemeral, meaning we don’t have any persistent storage for our data, such as the MySQL database and website content files.</p>
<h1 id="heading-next-steps">Next steps</h1>
<p>Now, you should have a basic understanding of a typical app configuration, consisting of a frontend web service and a backend database running in your local environment using Radius. It might not look like a good time investment, especially considering the management overhead of running such a simple app with a Radius dependency on Kubernetes and defining all required Radius abstractions. However, in the next post, I will explore how to deploy the exact application definition to Azure cloud native services using Radius Environment abstraction, which allows you to have a completely different infrastructure setup from the one we used locally.</p>
<p>We will also check how to add a state to your application by using persistent storage.</p>
<p>Do you want to know more about using Radius to host your applications? Let me know your thoughts in the comments below 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to test Azure Policy]]></title><description><![CDATA[It has been some time since I wrote my Azure Policy Starter Guide, in which I briefly touched upon the challenges of testing custom Azure Policy definitions. Azure Policy testing still remains an open question for me, offering a lot of room for vario...]]></description><link>https://andrewmatveychuk.com/how-to-test-azure-policy</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-test-azure-policy</guid><category><![CDATA[Azure Policy]]></category><category><![CDATA[Testing]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 24 Jun 2025 13:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1750758610668/fc7baafb-b954-4c43-a170-4b7b6c5f828e.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It has been some time since I wrote my <a target="_blank" href="https://andrewmatveychuk.com/azure-policy-starter-guide"><strong>Azure Policy Starter Guide</strong></a>, in which I briefly touched upon the challenges of testing custom Azure Policy definitions. Azure Policy testing still remains an open question for me, offering a lot of room for various testing approaches and practices.</p>
<p>Unfortunately, the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/evaluate-impact"><strong>official Microsoft guidelines</strong></a> provide limited information about testing Azure Policy, with most recommendations focusing on manual tests. The <a target="_blank" href="https://azure.github.io/enterprise-azure-policy-as-code/"><strong>Enterprise Azure Policy as Code</strong></a> starter kit requires adopting a predefined end-to-end process, which may not fully meet your needs, with numerous dependencies and minimal emphasis on testing. The <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/policy-as-code#test-and-validate-the-updated-definition"><strong>Azure Policy as Code Workflow</strong></a> documentation offers only a few hints about <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/samples/resource-graph-samples?tabs=azure-cli#azure-policy">using the Azure Resource Graph to check for the Azure Policy compliance state</a> for your resources.</p>
<p>Here, I want to discuss this topic in more detail and present some automation ideas. We will look into validating your Azure Policy syntax, how different Azure Policy effects can affect your testing strategy, how to test your custom policies individually, and how to evaluate their cumulative effect on resources within a specific scope. The testing approaches described below are organized according to the <a target="_blank" href="https://en.wikipedia.org/wiki/Software_testing"><strong>Testing Pyramid</strong></a>, going from the quickest to execute to the most time-consuming ones to run.</p>
<p>So, let’s start.</p>
<h1 id="heading-syntax-validation">Syntax validation</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750760799123/f779e5fe-a2de-4183-9f8d-c65290235e15.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-how-to-validate-azure-policy-when-authoring-in-visual-studio-code">How to validate Azure Policy when authoring in Visual Studio Code</h2>
<p>Unfortunately, there is no easy way to validate your custom Azure policy syntax. Although there is an official <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=AzurePolicy.azurepolicyextension">Azure Policy extension for Visual Studio Code</a>, I personally find it not very helpful when authoring your custom policy definitions or policy initiatives, as it doesn’t provide IntelliSense support in the editor. Plus, it depends on some outdated extensions, as I’m writing this. <a target="_blank" href="https://justingrote.github.io/">Justin Grote</a> even created a separate <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=justin-grote.azure-policy-intellisense">Azure Policy IntelliSense extension</a> to address that challenge, but it hasn’t been updated for quite some time.</p>
<p>As Azure Policy definitions are defined in the JSON format by default, probably the easiest way to validate them is to add the corresponding <a target="_blank" href="https://schema.management.azure.com/schemas/2020-10-01/policyDefinition.json">policy definition schema</a> to your policy definition files so that <a target="_blank" href="https://code.visualstudio.com/Docs/languages/json">Visual Studio Code can validate your JSON files</a> against it. Still, that is only a part of the story here.</p>
<h2 id="heading-how-to-validate-azure-policy-syntax-in-your-cicd-pipeline">How to validate Azure Policy syntax in your CI/CD pipeline</h2>
<p>In most cases, you will want to <a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-from-an-azure-devops-pipeline">validate your custom Azure Policy syntax as part of your automated build/deployment pipelines</a> to ensure consistent quality across your team or project. Some people try to <a target="_blank" href="https://dev.to/omiossec/using-powershell-and-pester-to-validate-azure-policy-syntax-2cko">perform a basic policy definition structure test using Pester to parse and validate their policy JSON files for specific elements</a>. To me, that approach is quite limited, as you will likely need to update your tests each time the Azure Policy definition schema is updated, and you create new policies using different schema versions.</p>
<p>I suggest the same approach with JSON schema validation to check your Azure Policy syntax when running your CI/CD pipelines. There are plenty of ready-to-use extensions for both GitHub Actions and Azure Pipelines to do so. Also, you can still implement it as a Pester test case using the <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/test-json">Test-Json</a> cmdlet and provide the policy definition schema file.</p>
<h2 id="heading-how-to-validate-azure-policy-syntax-when-they-are-defined-using-bicep-or-terraform-templates">How to validate Azure Policy syntax when they are defined using Bicep or Terraform templates</h2>
<p>I have already written a few posts explaining why I define my custom Azure Policy definitions using <a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policies-with-arm-templates">ARM</a> and later <a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-with-bicep">Bicep</a> templates. In short, I still find it somewhat inconvenient that Azure Policy, as an Azure resource, is, by default, defined using a separate format, which is different from other Azure resource definitions that you typically author using Bicep or Terraform templates.</p>
<p>For example, when you <a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-with-bicep">create your custom Azure Policy definitions in Bicep</a>, you have pretty good autocompletion and IntelliSense support in Visual Studio Code from <a target="_blank" href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-bicep">the Bicep extension</a>. Secondly, you can leverage <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/linter">Bicep linter</a> checks for syntax validation or simply execute the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/bicep-cli#build">Bicep build command</a> as part of your pipeline to validate the source code of your definitions. Thirdly, it helps you to define and deploy your cloud resources in a consistent and unified approach across your project, reducing the number of pipelines to run and the maintenance complexity of working with different formats. Lastly, it allows you to validate your templates against your actual environment. Let me explain this part a bit.</p>
<p>Validating your Azure Policy definition syntax locally or during a pipeline run is great, as it can be done quickly without any external dependencies. However, it is still not enough to ensure the correctness of your policy, because you can technically provide incorrect property values that will cause errors during actual policy deployment. When you <a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-with-bicep">define your Azure Policy in a Bicep template</a>, you can validate its correctness against the Azure Resource Management API with the <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/az.resources/new-azdeployment">New-AzDeployment</a> cmdlet (check the What-If parameter). Similarly, you can leverage the plan command in Terraform. That type of check will take longer to complete, but it will validate that your Azure Policy definition can actually be deployed without errors.</p>
<p>Now, when we are pretty sure that our Azure Policy definitions are syntactically correct, it is time to test them in action, but before we dive into the tests, let’s spend a couple more minutes reviewing Azure Policy effects and their nuances.</p>
<h1 id="heading-nuances-of-testing-azure-policy-effects">Nuances of testing Azure Policy effects</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750761144916/91c20837-7707-4cb4-bbd5-5f184252f714.png" alt class="image--center mx-auto" /></p>
<p>Understanding <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-basics">Azure Policy effects</a> and their nuances is crucial for properly designing your tests.</p>
<p>First of all, different policy effects have different outcomes. Those expected outcomes should be tested differently, and you can organize your test cases around the effects first.</p>
<p>Secondly, some Azure Policy effects are <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-basics#interchanging-effects">interchangeable</a>, meaning that, in most cases, testing for one effect only will be enough. As testing for some effects is easier than for others, it can save you a ton of time. For instance, testing the Deny effect is usually easier than testing the Audit one. The first one can be tested synchronously, while the second comes into effect asynchronously, introducing delays in your test cases.</p>
<blockquote>
<p>Probably the best description of Azure Policy effects from the test perspective so far is the <a target="_blank" href="https://github.com/fawohlsc/azure-policy-testing">Testing Azure Policy</a> project by Fabian Wohlschläger. I really admire his effort and dedication to digging into the Azure Policy APIs and providing sample Pester tests for different effects. The helper PowerShell functions he created can save you a lot of time when designing and running your Azure Policy tests.</p>
</blockquote>
<p>Thirdly, there is a specific <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-basics#order-of-evaluation">order for evaluating different Azure Policy effects</a> applicable to the same scope. Understanding that order is essential when you test your resource deployments against a few policies (more on this below).</p>
<p>Now, let’s talk about testing Azure Policy effects.</p>
<h1 id="heading-azure-policy-unit-tests">Azure Policy unit tests</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750760838430/6170da3f-6e31-4b70-bd56-72ad99ae4a47.png" alt class="image--center mx-auto" /></p>
<p>Basically, when we say that we want to test our Azure Policy, we essentially want to test its effect. For example, in the case of the Deny effect, we might want to test that our policy <a target="_blank" href="https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/Storage/StorageAccountMinimumTLSVersion_Audit.json">prevents new Storage accounts using insecure TLS versions from being created</a>. How can we test that?</p>
<p>Using the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/testing/unit-testing-best-practices#arrange-your-tests">Arrange-Act-Assert pattern</a>, we can create a Pester test that will:</p>
<ol>
<li><p>[<strong>Arrange</strong>] Deploy our Azure Policy definition at the subscription level. It would be wise to use a separate test subscription for that purpose, so your Azure Policy definitions to test don’t overlap with those in your production scope.</p>
</li>
<li><p>[<strong>Arrange</strong>] Create a test Resource Group and scope your policy assignment to it. I recommend creating resource group-scoped assignments for tests to isolate the test environment for each individual Azure Policy definition.</p>
</li>
<li><p>[<strong>Act</strong>] Deploy a non-compliant resource like a Storage account with incorrect TLS settings.</p>
</li>
<li><p>[<strong>Assert</strong>] Check that your deployment fails because of that specific Azure Policy. It is important to check for a particular policy compliance error to avoid false positives from other Azure Policy assignments that apply to the same scope. For example, you might have a subscription-scoped policy assignment to restrict resource deployment to specific Azure regions, and your test resource deployment might fail because of targeting the wrong region, not because of the actual policy you want to test.</p>
</li>
</ol>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="d51229079bbe33be9e917b3e85ea620e"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/d51229079bbe33be9e917b3e85ea620e" class="embed-card">https://gist.github.com/andrewmatveychuk/d51229079bbe33be9e917b3e85ea620e</a></div><p> </p>
<p>Optionally, you can also add an additional test to ensure that a policy-compliant resource can be deployed successfully.</p>
<blockquote>
<p><em>As I mentioned above, I strongly recommend looking into the sample test cases in the</em> <a target="_blank" href="https://github.com/fawohlsc/azure-policy-testing"><em>Testing Azure Policy</em></a> <em>project to get some ideas about validating different Azure Policy effects using the</em> <a target="_blank" href="https://pester.dev/"><em>Pester</em></a> <em>test framework.</em></p>
</blockquote>
<p>When you have a lot of Azure Policy definitions to test, such test cases can be executed with Pester in parallel with minimal delays. However, there will still be delays related to resource provisioning and evaluating the resource compliance state due to the nature of how the Azure Policy backend works. Depending on how heavy/long your tests are, you might want to split them into groups to be executed every time as part of your CI process and scheduled for nightly builds.</p>
<p>Automatically testing individual Azure Policy definitions is an excellent thing. Still, in real life, you usually have a few dozen or even hundreds of policies applicable to the same scope, introducing new edge cases and additional test complexity.</p>
<h1 id="heading-azure-policy-integration-tests">Azure Policy integration tests</h1>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1750760855424/9eb00772-e5d1-405a-a301-cd6cb19c6300.png" alt class="image--center mx-auto" /></p>
<p>In a production environment, you usually have many different Azure Policy assignments scoped to management groups or individual subscriptions, creating <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-basics#layering-policy-definitions">cascading effects when evaluating those policies</a> during resource deployments or updates.</p>
<p>Let’s use the previous example with two policies for the sake of simplicity. You might usually have an Azure Policy assignment at the top management group level to restrict the Azure regions allowed for resource deployment. At the same time, you might have another policy assignment at your production Azure subscription level to enforce the minimal TLS version. Both policies will deny new resource creation in the production subscription if either of them is violated. In order to pass those rules, a resource (a Storage account) must be created in an allowed region and with the correct TLS settings. To make things even more complicated, you might also have an Azure Policy with the Modify effect to automatically update the TLS settings to an outdated value when a new Service account is deployed. Imagine you are deploying a new Storage account into the correct region and with the correct TLS settings. What would be the outcome of your resource deployment in that case?</p>
<p>Remember, I mentioned that <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-basics#order-of-evaluation">Azure Policies are applied in a specific order depending on their effect</a>? As the Modify-effect policies are evaluated first, your TLS settings will be updated to the incorrect value. Then, the Deny-effect policies will be triggered and block your correctly defined resource from being deployed.</p>
<p>Now, assume you have hundreds of overlapping Azure Policy assignments, which is not uncommon. You can imagine how complex the testing becomes, as you don’t test individual policy effects anymore, but instead you need to test their compound effect on your resources.</p>
<p>That brings you one more step closer to the top of your testing pyramid: you should now test your resource deployment in the environment that is similar to your production setup as much as possible. In practice, that means you need to have a test environment where you deploy and assign your Azure Policy definitions to reflect their desired setup in a production environment. Then you can execute the same test cases you used for your Azure Policy unit tests to validate the resulting cumulative effect and desired behavior. Lastly, testing your actual application deployment end-to-end in such an environment would make a lot of sense. Only then you can be sure (to a greater extent) that your new or updated Azure Policy definitions won’t break your application.</p>
<p>Depending on your organizational structure and established development and test practices, the final integration testing can be performed as part of your Azure Policy management or your application project development processes. It’s also highly dependent on how you manage Azure Policy assignments in your organization, whether you use any landing zones to host your applications, and how isolated they are.</p>
<h1 id="heading-to-test-or-not-to-test">To test, or not to test</h1>
<p>Last but not least, there is a debatable question about testing <a target="_blank" href="https://learn.microsoft.com/en-us/azure/storage/common/policy-reference">built-in Azure Policies</a> maintained by Microsoft. Overall, they are well tested and have very few bugs, often resulting from an incorrect understanding of policy behavior. In most cases, you can skip testing them at the lower levels of your testing pyramid and include them in the testing scope only at the last step, when you test the integration of your application into the environment governed by your set of Azure Policies. You might also want to test your application functionality as a white box and/or a black box to validate its behavior in an updated environment, but that topic is beyond the scope of this article.</p>
<p>How do you test your custom Azure Policy definitions? Do you test them as part of your application deployment pipelines? Or do you manage and push them centrally across your whole environment? Please share your experience in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[Azure App Service Cost optimization strategies that you won’t get from Azure Advisor]]></title><description><![CDATA[When optimizing cloud costs for large enterprise environments, it’s common to focus on compute-intensive cloud resources, such as virtual machines, clusters, and container-based workloads, as they are usually the primary cost driver and top contribut...]]></description><link>https://andrewmatveychuk.com/azure-app-service-cost-optimization-strategies</link><guid isPermaLink="true">https://andrewmatveychuk.com/azure-app-service-cost-optimization-strategies</guid><category><![CDATA[Azure]]></category><category><![CDATA[Azure App Service]]></category><category><![CDATA[Cost Optimization]]></category><category><![CDATA[finops]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Mon, 10 Feb 2025 12:31:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1738942780300/cca5e454-4499-4399-8794-96d94971a66a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>When optimizing cloud costs for large enterprise environments, it’s common to focus on compute-intensive cloud resources, such as virtual machines, clusters, and container-based workloads, as they are usually the primary cost driver and top contributor to monthly cloud invoices. Luckily for us, there are already plenty of well-known approaches, straight guidelines, and easy-to-use tools from Microsoft and other <a target="_blank" href="https://turbo360.com">third-party vendors</a> that can save money on those workloads.</p>
<p>On the contrary, optimizing costs for PaaS services is an entirely different story, as there are a lot of service-specific nuances you should be aware of that can impact your resource costs. So, today, let’s explore how we can optimize our cloud spending on <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-manage-costs#understand-the-full-billing-model-for-azure-app-service">Azure App Service</a>, which is one of the most used PaaS services in the Azure cloud.</p>
<h2 id="heading-app-service-plans-consolidation">App Service plans consolidation</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738941907358/80d97b84-42e4-4dda-8258-3ea446ea01ae.png" alt class="image--center mx-auto" /></p>
<p>The first service-specific cost nuance for Azure App Service is that App service resources don’t incur costs independently. In other words, you don’t pay for your App Service resources. You pay for <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-hosting-plans">App Service plans</a> that host them. When an App Service represents your application, an App Service plan provides you with actual infrastructure resources to run them.</p>
<blockquote>
<p>You can think of App Service plans as an abstraction for web server farms managed by Microsoft. With that abstraction, you don’t need to manage virtual machines, configure web servers, do the networking, scaling, patching, etc. You just need to pay for the compute and storage resources you consume. That is a very simplified explanation of App Service plans, and they are much more under the hood, but those details are not so important in the context of our topic</p>
</blockquote>
<p>Understanding that concept is essential, as separating the application part from the infrastructure allows you to host many App Services on a single App Service plan. You can be surprised that many engineers don’t know about that. Plus, the Azure portal, by default, suggests you provision a new App Service plan each time when you create a new App Service, and that’s what most people do. The result is that you have an environment with a handful of underutilized App Service plans costing you lots of money, each hosting a single or very few apps.</p>
<p>In many cases, you can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/well-architected/cost-optimization/consolidation">reduce your spending on App Service applications tenfold by consolidating them on a single App Service plan</a> per environment or application group. Apart from that, you can discover that hosting a dozen apps on a single <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/app-service-configure-premium-tier">more ‘expensive’ App Service Premium tier is cheaper than having a dozen Basic or Standard service plan instances</a>.</p>
<blockquote>
<p>However, when consolidating multiple apps on a single App Service plan, you must understand that high load or errors in one application can impact the performance of other applications sharing the same App Service plan. So, it’s important to implement appropriate monitoring and autoscaling to mitigate the impact of such events.</p>
</blockquote>
<h2 id="heading-rightsizing-and-autoscaling">Rightsizing and autoscaling</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738941924681/4946c9e1-af3d-4331-8fdb-00c4dbafcebc.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/manage-scale-up">Rightsizing</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/manage-automatic-scaling">autoscaling</a> are probably the second most common topic for reducing your cloud spent on Azure App Services. As with Azure VM, overprovisioned resources for App Service plans are what you can see when you check their actual utilization. The usual argument is that we need some space capacity just in case there is a surge in requests. For some reason, many think resource scaling is only about adding CPU and memory to the existing resources. The horizontal scaling, when you load balance your traffic between multiple nodes and add or remove additional nodes when needed, is still overlooked. If you don’t use it, you simply don’t leverage the full flexibility of the cloud when you can pay only for what you consume.</p>
<p>Rightsizing and autoscaling work hand in hand. You can set up your scale-out rules for App Service and pay for additional resources only when you need them, which might significantly impact your App Service costs. <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/manage-automatic-scaling?tabs=azure-portal#how-does-automatic-scaling-work-behind-the-scenes">Adding additional instances happens pretty fast, as Azure data centers usually have some pools of ready-to-use instances for that purpose.</a> The only exclusion from that is the Isolated tier (aks App Service Environments), where scaling might take longer. So, if using them, please check if autoscaling speed is appropriate for your needs. If their scaling is too slow for you, you can consider moving to the Premium tier, as it now has many features that were available only on App Service Environments previously, and it might fully fit your requirements.</p>
<h2 id="heading-deployment-slots-vs-separate-environments">Deployment slots vs. separate environments</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738941945889/69a10494-e479-4ed9-8b97-02c46f1ce677.png" alt class="image--center mx-auto" /></p>
<p>Another place to look into for optimizing your App Service costs is reconsidering your <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/deploy-best-practices">application development and testing approach</a>. It is common to create separate environments for application development and running the same application in production. Despite the benefits of that approach, it comes with additional costs, as you need to have twice as many cloud resources for it. Regarding App Service, it means that you usually have two separate App Service plans to pay for – one for production and one for development purposes.</p>
<p>What you can (and probably should) do is leverage <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/deploy-best-practices#use-deployment-slots">App Service deployment slots</a>, which are available starting from the Standard tier. Basically, they allow you to create and manage separate hosting containers for your development, testing and production versions of your application. Those containers are hosted on the same App Service plan, effectively reducing the need for additional plans. What is more, with App Service deployment slots, it’s much easier to push new application versions to production by performing slot swaps. Plus, you can implement A/B testing and split incoming traffic between production and staging slots to live-test your changes on a smaller scope.</p>
<p>As with consolidating multiple applications, App Service deployment slots share resources of the same App Service plan. So, please keep that in mind if you need to perform some load testing for your application. It might make sense to deploy a separate environment for that purpose and decommission it when you are done with your tests.</p>
<h2 id="heading-network-isolation-and-pricing-tiers">Network isolation and pricing tiers</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738941962184/28c75573-194d-48f0-a69f-cbba74faeeae.png" alt class="image--center mx-auto" /></p>
<p>This one is somewhat connected to rightsizing, as <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/tutorial-networking-isolate-vnet">network isolation</a> and private networking were previously achievable only on <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-security#network-isolation">the Isolated tier</a> when you deployed your app service environments into your Azure virtual networks. Those days are long gone; you can isolate your App Service environment on the network level even when running on the Basic tier. <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-vnet-integration">Azure App Service virtual network integration</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-private-endpoint">Azure Private Link</a> allow you to lock down network access to your App Service instances and services they depend on for work. The virtual network integration allows your App Services to communicate with other services deployed in your Azure Virtual network without using their public endpoints. Private endpoints provide private connectivity to your App Services from your private virtual network, so the inbound traffic to them never leaves your network.</p>
<p>For example, you can completely turn off the public endpoint on App Service and make it available only via a private endpoint, so it’s accessible from your private network only. If you need to securely publish your application for public access without exposing your App Service public endpoint, you can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/frontdoor/private-link">do it with Azure Front Door Premium, which can connect to it via private endpoints</a>. Alternatively, you can configure <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-access-restrictions">access restrictions</a> for your App Service public endpoint so <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-access-restrictions#restrict-access-to-a-specific-azure-front-door-instance">it’s only accessible to your Azure Front Door Standard instance</a> and not for direct access.</p>
<p>With all that being said, you might reconsider using more expensive Isolated and Premium tiers in favor of more affordable Standard and Basic ones, knowing that you can achieve comparable network isolation and protection for your App Services. Plus, implementing <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cdn/cdn-add-to-web-app">traffic offloading</a> with such services as Azure Front Door might allow you to scale down your App Service plans, as few requests will be hitting them.</p>
<h2 id="heading-reservations-and-savings-plans-for-app-service">Reservations and Savings Plans for App Service</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738941978400/514db8f0-4c97-4771-ae14-c2ffd5687dd0.png" alt class="image--center mx-auto" /></p>
<p>If the previous recommendations required some changes in your App Service deployments, this one is the most effortless. Suppose you know that you are likely to run your App Service instances as they are for a year or more. In that case, you can look into purchasing <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/reservation-discount-app-service">Azure Reservations</a> if it’s a stable workload in a specific Azure region or <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/savings-plan-compute-overview">Azure Savings plans</a> if you need more flexibility in your service and region choice. Your savings from those commitments will vary depending on your App Service plan sizes and the length of the commitments. For maximum savings, it’s worth trying to refactor your App Service deployment according to the previously mentioned tactics first, then consider <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/decide-between-savings-plan-reservation">using reservation or savings plans</a> for your already optimized environment.</p>
<p>The downside of that optimization is that <a target="_blank" href="https://azure.microsoft.com/en-us/pricing/offers/savings-plan-compute/#Select-services">it applies only to Premium v3 and Isolated v2 App Service plan tiers</a>. However, as mentioned at the beginning of this article, it’s often possible to hit a cost reduction combo if you consolidate your multiple App Services on a few Premium v3 App Service plans and apply reservation or savings plans to them.</p>
<h2 id="heading-how-to-optimize-azure-app-service-costs-with-turbo360">How to optimize Azure App Service costs with Turbo360</h2>
<p>After going through all those optimization tactics for Azure App Service mentioned above, you might be thinking about implementing them in your practical scenarios. Depending on your specific use case of that cloud service and the scale of your infrastructure, you usually have the following options:</p>
<ul>
<li><p>With small-scale App Service deployments, you can spend a few hours examining your configuration and optimizing it according to your constraints.</p>
</li>
<li><p>In large-scale scenarios, such as enterprise deployments with hundreds or thousands of App Service instances or managed service providers (MSP) managing hundreds of client environments, performing such cost optimizations manually is usually impractical and unfeasible.</p>
</li>
</ul>
<p>For the latter option, it might make sense to look for third-party solutions like <a target="_blank" href="https://turbo360.com">Turbo360</a> or similar that allow you to significantly reduce the time spent analyzing your cost optimization options and implementing them at scale.</p>
<p>For example, <a target="_blank" href="https://turbo360.com/azure-cost-analysis">Turbo360’s Cost Analyzer</a> can enhance your Azure App Service cost optimization strategy. It provides insights and optimization recommendations that native cloud tools often lack. The Cost Analyzer features go beyond basic cost metrics to provide granular insights into resource utilization, including identifying the exact needs of your Azure App Service. With rightsizing recommendations that include upgrade, downgrade, idle, and no change options, you can allocate your App Service resources more efficiently, avoiding their overprovisioning and underutilization.</p>
<p>Plus, Turbo360 allows you to create optimization schedules to scale resources down during non-peak hours and up during high load by automating scaling based on real-time resource demand and business requirements.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738841148502/1d27e7d7-881c-4576-98a6-e045f73bdad5.png" alt class="image--center mx-auto" /></p>
<p>According to the insights from their existing client base, implementing such scaling schedules can reduce App Service costs by up to 65% for non-production environments.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1738837060046/1fb238e5-016f-4199-8e5a-3b2a5eec7b51.png" alt class="image--center mx-auto" /></p>
<p>Moreover, the Cost Analyzer’s monitoring feature allows you to set a predefined budget so that you do not exceed your spending and incur any surprise costs.</p>
<p>Frankly speaking, you can optimize your App Service (and any other cloud service) costs without using any third-party tools. However, you should understand that it might take a lot of time and effort to implement and monitor for new optimization opportunities on your own, using cloud-native tooling only. That’s why you might want to evaluate ready-to-use solutions like <a target="_blank" href="https://turbo360.com">Turbo360</a> (it has a 15-day free trial) for Azure App Service optimization so that you can free up your time for more impactful and profitable work.</p>
<h2 id="heading-in-conclusion">In conclusion</h2>
<p>To sum up, optimizing costs for your App Service deployments is a creative task. It might even seem counter-effective when upgrading to higher service tiers and adding more cloud services to reduce overall operational expenses. Apart from that, you shouldn’t just blindly sacrifice your reliability, security and observability requirements to save a handful of coins. <a target="_blank" href="https://turbo360.com/blog/azure-cost-optimization">Azure cost optimization</a> is not something you do in full isolation from other aspects of managing your applications and services. Moreover, it’s not something you do once and for all. Having good FinOps processes and tools to monitor and assess changes in your cloud expenses is equally important to one-time optimization, as changes are almost inevitable in the cloud, which can drive your cloud bill up as well as down.</p>
]]></content:encoded></item><item><title><![CDATA[Migrating from Ghost to Hashnode]]></title><description><![CDATA[Announcement
I’ve migrated my blog from Ghost(Pro) to Hashnode. All links should be working, but please let me know if you encounter something broken or missing.
Reasons

I used Ghost to run my blog for many years, self-hosting it first and becoming ...]]></description><link>https://andrewmatveychuk.com/migrating-from-ghost-to-hashnode</link><guid isPermaLink="true">https://andrewmatveychuk.com/migrating-from-ghost-to-hashnode</guid><category><![CDATA[Hashnode]]></category><category><![CDATA[ghost]]></category><category><![CDATA[migration]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Fri, 20 Dec 2024 09:01:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736865300011/eb0feaf1-acbe-44db-934c-857a96834641.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-announcement">Announcement</h2>
<p>I’ve migrated my blog from <a target="_blank" href="https://andrewmatveychuk.com/moving-to-ghost-pro">Ghost(Pro)</a> to <a target="_blank" href="https://hashnode.com/">Hashnode</a>. All links should be working, but please let me know if you encounter something broken or missing.</p>
<h2 id="heading-reasons">Reasons</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736864510883/b4c30f86-56eb-4681-8775-ae18c77fd93e.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://andrewmatveychuk.com/tag/ghost">I used Ghost to run my blog for many years</a>, self-hosting it first and <a target="_blank" href="https://andrewmatveychuk.com/moving-to-ghost-pro">becoming a paying customer of Ghost(Pro)</a> later. I would say that I was mostly satisfied with it as a blogging platform. Nevertheless, I decided to try something new last year, and Hashnode was on my radar for a while.</p>
<p>Ghost(Pro) has its niche in various modern blogging platforms. It’s feature-reach, reliable, SEO-optimized, and fast. However, the overall direction of its evolution and its pricing model push you toward monetizing your blog as your audience grows. Besides, its <a target="_blank" href="https://ghost.org/pricing/"><strong>Stater</strong> pricing tier</a> doesn’t allow you to use custom themes, significantly reducing your ability to customize your blog. You need to go with the <strong>Creator</strong> tier for that, which increases your annual spending on blog hosting three-fold. Those aspects made me question if there are better alternatives for the Ghost(Pro) Stater tier.</p>
<p>I think Hashnode, being relatively younger, cannot be compared to Ghost head-to-head. Also, it has a different product placement, positioning itself as <a target="_blank" href="https://hashnode.com/feed">a community blogging solution</a>, which is <a target="_blank" href="https://townhall.hashnode.com/hashnode-is-the-new-medium-for-the-tech-community">somewhat similar to Medium</a>. At the same time, you can map a custom domain to your Hashnode blog or self-host using the <a target="_blank" href="https://docs.hashnode.com/blogs/getting-started/hashnode-headless-cms">Hashnode Headless CMS</a>, which allows you not to be locked into using Hashnode as a managed platform only. Basically, you can have almost the same platform functionality on Hashnode for free as you could have it on the Ghost(Pro) Stater tier if you don’t need to use a paywall for your subscribers.</p>
<p>Apart from that, Hashnode allows you to import and export <a target="_blank" href="https://docs.hashnode.com/blogs/blog-dashboard/github/how-to-set-up-automatic-github-backups-on-your-blog">(backup) your articles to a GitHub repository</a> in the Markdown format. Even though I have the source documents for my articles and the related images stored in my OneDrive folder and backed up on the file level, manually restoring them would be a tedious task, considering the growing number of blog posts. Plus, I tend to edit many blog posts on the website after publishing them, like adding updates and fixing broken links, which makes my source Word documents somewhat outdated. Having all my blog posts up-to-date, versioned, in the Markdown format, and automatically syncing to my GitHub repository as a backup location makes it much easier to restore or transfer to another blogging platform if need be. Compared to Ghost, where your blog posts are stored in a database and can only be exported in a cumbersome JSON format on-demand, that GitHub integration was the most convincing reason to switch to Hashnode. Because, you know, having backups is what distinguishes good engineers from not-so-good ones.</p>
<h2 id="heading-migration-process">Migration process</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1736864524761/741f8e2d-208b-4efe-aa65-2fc9cb38bf23.png" alt class="image--center mx-auto" /></p>
<p>Unfortunately, there is very little information on the Internet about migrating your content from Ghost to Hashnode. Some people migrated their content <a target="_blank" href="https://thao.pw/simplifying-blog-migration-with-automation-ghost-to-hashnode">programmatically</a>, while others leveraged <a target="_blank" href="https://itheo.tech/import-ghost-cms-articles-into-hashnode">the Hahnode feature to import content from RSS feeds</a>, pointing it to their current Ghost-based blogs.</p>
<p>I wanted to spend as little time as possible on the migration and chose the migration via <a target="_blank" href="https://docs.hashnode.com/blogs/blog-dashboard/import/rss-importer">the RSS import</a>. Unfortunately, when I was ready to start the import process, that feature was turned off for unknown reasons. It was time to speak to Hashnode Support, and I was pleasantly surprised by their responsiveness and the workaround they provided.</p>
<p>It turned out that the Hashnode team (kudos to <a target="_blank" href="https://hashnode.com/@Favourite">Favourite Jome</a>) had already created <a target="_blank" href="https://ghost-hashnode-migration.vercel.app/">a migration app to migrate your content from Ghost to Hashnode</a>. You only need to provide it with the JSON export of your Ghost blog and the API token for your new Hashnode blog, and it will import most of your existing blog content, saving most of the source formatting and media, including images, GitHub gists, and other embeds. Your posts will be imported as drafts in Hashnode, so you can (and should) review them before republishing. Depending on the formatting of your original posts and the different embeds you might use, you might need to spend some time fixing some import errors, formatting, and checking for missing content.</p>
<p>In my case, it took me a couple of days to properly format my drafts on Hashnode, check that all images were in place and displayed correctly, and fix a few dozen broken links in some of my old posts.</p>
<p>Apart from importing your old posts from Ghost, you might also need to import your subscribers. <a target="_blank" href="https://ghost.org/help/exports/#members">The subscriber export in Ghost</a> and <a target="_blank" href="https://docs.hashnode.com/help-center/hashnode-newsletter/importing-your-subscriber-list-into-hashnode-newsletter">their import in Hashnode</a>v use the CSV file format for data, so migrating them wasn’t a big deal. It took me just a few PowerShell commands to clean up unnecessary fields before the import, and I was all set to send out mail notifications to my subscribers from my Hashnode blog.</p>
<p>Currently, <a target="_blank" href="https://docs.hashnode.com/blogs/blog-dashboard/appearance">managed Hashnode hosting doesn’t support custom themes</a>, and your customization options are limited to three predefined layouts, custom logos, and a branding color. However, it wasn’t an issue for me, as the resulting blog layout is very similar to my previous one in Ghost.</p>
<p>A few final touches were related to remapping my custom domain, transferring my custom page redirects, and repointing my user analytics projects to the new blog. Those hardly require mentioning, as their configuration was pretty straightforward and well-documented. I particularly liked that on Hashnode, you don’t need to mess up with code injections for Google Analytics or other similar solutions to make them work, and you only need to provide your unique tracking tags or IDs for configuration.</p>
<h2 id="heading-back-to-blogging">Back to blogging</h2>
<p>Now, I’m finally set up to continue my blogging on Hashnode, and I shall see what my impression of it is after using that platform for a while. I’ve already stopped drafting my articles in Microsoft Word and completely switched to the Hashnode editor. The ability to seamlessly switch between the WYSIWYG and raw Markdown editor modes is fantastic, and I’m using it to learn more about Markdown formatting. Plus, having my articles automatically backed up to a GitHub repo makes me less worried about restoring them if needed.</p>
<p>Although the current blog can run on Hashnode with the custom domain entirely for free, I happily paid for the <a target="_blank" href="https://townhall.hashnode.com/meet-hashnode-pro">Hashnode Pro</a> to support the team behind the great product. I hope that the Hashnode team will continue releasing new features and extending the functionality of the existing ones.</p>
<p>P.S. Please treat all of the above as my migration experience, which I’m happy with, and not a Hashnode advertisement in any way. If you have questions about my migration to Hashnode, please feel free to ask them in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[Ghost on Azure: Project Update (Ghost 5, MySQL Flexible Server, Private Link, RBAC for Key Vault, App Service access restrictions to Front Door)]]></title><description><![CDATA[It has been a while since I last updated my Ghost on Azure project, and many changes have been introduced to Azure services during that time. I decided to use the break in my work to update the project deployment templates to include those changes an...]]></description><link>https://andrewmatveychuk.com/ghost-on-azure-project-update</link><guid isPermaLink="true">https://andrewmatveychuk.com/ghost-on-azure-project-update</guid><category><![CDATA[Azure]]></category><category><![CDATA[ghost]]></category><category><![CDATA[containers]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 12 Nov 2024 09:40:51 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736867442838/652580ec-bb95-453e-bb86-1c09ccc32652.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>It has been a while since I last updated my <a target="_blank" href="https://andrewmatveychuk.com/tag/ghost">Ghost on Azure project</a>, and many changes have been introduced to Azure services during that time. I decided to use the break in my work to update the project deployment templates to include those changes and to use it as a learning opportunity to catch up on new cloud service features.</p>
<blockquote>
<p><a target="_blank" href="https://github.com/andrewmatveychuk/azure.ghost-web-app-for-containers">Ghost on Azure</a> is a one-click Ghost deployment on Azure Web App for Containers. It’s written as a Bicep template, spinning a <a target="_blank" href="https://github.com/andrewmatveychuk/docker-ghost-ai">custom Ghost Docker container</a> on Azure App Service, which uses Azure Database for MySQL as a backend. It can be a good starting point for anyone wanting to self-host the Ghost platform on the Microsoft Azure cloud. Plus, it leverages a lot of Azure-native services and their features. Thus, it can be considered a showcase of their practical usage.</p>
</blockquote>
<p>The project started as a simple multi-container deployment and later transformed into a comprehensive solution using PaaS services such as Azure Key Vault, Front Door, and Web Application Firewall. Now, we are focusing on further security improvements and migrating from deprecated Azure services.</p>
<h2 id="heading-new-ghost-5-container-image">New Ghost 5 container image</h2>
<p>I use a <a target="_blank" href="https://github.com/andrewmatveychuk/docker-ghost-ai">custom Ghost Docker image</a> in this project, which is based on <a target="_blank" href="https://hub.docker.com/_/ghost/">the official Ghost Alpine image</a> and extended to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/app/app-insights-overview">support Azure Monitor Application Insights</a>. I’ve updated it to Ghost 5 and removed explicit database connectivity check with <a target="_blank" href="https://github.com/vishnubob/wait-for-it">wait-for-it</a>, as it has not been very reliable in testing MySQL server readiness to accept connections. As a new instance of Azure Database for MySQL takes some time to be up and running, Azure App Service hosting the container restarts it a few times until the database backend is ready and the Ghost app can connect to the server to create its database.</p>
<blockquote>
<p><strong>Note.</strong> The initial deployment might take a few minutes before the Ghost container successfully starts and is ready to serve the content.</p>
</blockquote>
<h2 id="heading-mysql-flexible-server">MySQL Flexible Server</h2>
<p><a target="_blank" href="https://andrewmatveychuk.com/a-one-click-ghost-deployment-on-azure-web-app-for-containers/">The initial project configuration</a> used a multi-container deployment, placing a MySQL container alongside the application container running Ghost using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/configure-custom-container?pivots=container-linux&amp;ref=andrewmatveychuk.com&amp;tabs=debian#docker-compose-options">Docker Compose</a>. Later, <a target="_blank" href="https://andrewmatveychuk.com/how-to-connect-to-azure-database-for-mysql-from-ghost-container/">I switched to a single container deployment and used Azure Database for MySQL for the database</a>, as the support for multi-container deployment on App Service degraded and became unstable. Recently, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/mysql/migrate/whats-happening-to-mysql-single-server">Microsoft deprecated Azure Database for MySQL – Single Server</a>, and I needed to migrate my project to MySQL – Flexible Server.</p>
<p>As Ghost 5 supports MySQL version 8 as a primary database option, I also switched from previous version 5, which was used with the MySQL – Single Server, to that version with the Flexible Server deployment. However, the most notable change in the context of this project is probably <a target="_blank" href="https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-connect-tls-ssl">that Azure Database for MySQL - Flexible Server now enforces using encryption connections</a> only by default.</p>
<p>I think using encryption in transit, like connecting to a database, is a good thing, even if the data transfer happens in the internal network. It fully reflects the core principles of <a target="_blank" href="https://www.microsoft.com/en-us/security/business/security-101/what-is-zero-trust-architecture">Zero Trust Architecture</a>, as a malicious actor might also operate inside your network. The tricky part was <a target="_blank" href="https://sidorares.github.io/node-mysql2/docs/examples/connections/create-connection#createconnectionconfig--ssl">configuring the encrypted connection options in the MySQL client used by Ghost</a>. Putting <a target="_blank" href="https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-connect-tls-ssl#download-the-public-ssl-certificate">the public certificate used by MySQL - Flexible Server</a> into the container image or onto a file share wasn’t a good option and would introduce more unwanted dependencies. So, I ended up <a target="_blank" href="https://ghost.org/docs/config/#database">putting the content of that public certificate into an environment variable</a> <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/file#multi-line-strings">using multi-line string support in Bicep</a>:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7113bfa8d6f89fb558ef51baeadaccaa"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/7113bfa8d6f89fb558ef51baeadaccaa" class="embed-card">https://gist.github.com/andrewmatveychuk/7113bfa8d6f89fb558ef51baeadaccaa</a></div><p> </p>
<h2 id="heading-azure-private-link">Azure Private Link</h2>
<p>Another notable change is that I decided to leverage <a target="_blank" href="https://learn.microsoft.com/en-us/azure/mysql/flexible-server/concepts-networking-private-link">Azure Private Link</a> to further restrict access to the database server at the network level and completely block access to it over the public network.</p>
<p>Configuring <a target="_blank" href="https://learn.microsoft.com/en-us/azure/private-link/availability">Azure Private Link and private endpoints for Azure services</a> might seem like a daunting task at first, but when you grasp the core concepts of how it works under the hood, it should become your no-brainer option to reduce the attack surface of your cloud infrastructure. Yes, it requires a good knowledge of core network concepts, understanding how DNS resolution works, and configuring corresponding private DNS zones using Azure Private DNS zones or your other existing DNS services. Plus, it adds complexity to your deployments and requires extra work to automate its configuration using Bicep or Terraform templates. However, from the security standpoint, I would name locking down network access to your cloud resources the number one recommendation after setting up proper authorization and encryption controls.</p>
<p>Here is how the project network topology looked like before using Azure Private Link:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922736/58ddd6b9-856e-477b-9b49-ea0d1a175449.png" alt class="image--center mx-auto" /></p>
<p>Communication between the app service and dependencies happened over their public endpoints. The backend services enforced encrypted connections. Plus, you could put some restrictions using firewalls on their public endpoints to limit access from other Azure services only. Still, the information was traversing over the Internet, and any misconfiguration of service firewall rules could expose them to external attacks.</p>
<p>After <a target="_blank" href="https://learn.microsoft.com/en-us/azure/mysql/flexible-server/concepts-networking-private-link">configuring private endpoints for Azure Database for MySQL</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/key-vault/general/private-link-service">Key Vault</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/storage/common/storage-private-endpoints">Storage</a>, and configuring <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-vnet-integration">virtual network integration for App Service</a>, all traffic to backend services is isolated in a private <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-network/virtual-networks-overview">Azure Virtual Network</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923622/40658a88-05f3-4f12-93a0-22cb0f9c680b.png" alt class="image--center mx-auto" /></p>
<p>The connectivity to the public endpoints of backend services is completely locked down, making it much easier to control and enforce at the enterprise scale using Azure Policy. Now, you don’t have to validate and control myriads of firewall rules on various Azure resources.</p>
<p>As you can now integrate App Service with a virtual network <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-vnet-integration#limitations">even on the Basic tier</a>, Azure Private Link and private networking in Azure became even more accessible. When your App Service is integrated with a virtual network, it can connect to other services deployed in that network, like private endpoints, without going over the public network. Plus, you can apply all other security measures available in Azure Virtual Network to further segment and restrict network access using subnets, Network Security Groups (NSG), network policies for private endpoints, etc.</p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/dns/private-dns-privatednszone">Azure Private DNS zones</a> for hosting private DNS zones for Azure Private Link service also greatly simplifies its usage, as it automates the creation of corresponding DNS records for your private endpoints.</p>
<h2 id="heading-azure-key-vault-role-based-access-rbac">Azure Key Vault role-based access (RBAC)</h2>
<p>The next improvement is related to configuring access to Azure Key Vault using Azure roles instead of legacy Key Vault access policies. In my opinion, it greatly simplifies access management at scale, as you no longer need to control access at two different levels, and you can configure access to both management and data plane using Azure built-in and custom roles.</p>
<p>From the Bicep code perspective, we now create a separate Azure role assignment resource:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="96bdaab91d7793b684befa4d8325321e"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/96bdaab91d7793b684befa4d8325321e" class="embed-card">https://gist.github.com/andrewmatveychuk/96bdaab91d7793b684befa4d8325321e</a></div><p> </p>
<p>Although you can configure <a target="_blank" href="https://learn.microsoft.com/en-us/azure/mysql/flexible-server/how-to-azure-ad">Microsoft Entra authentication for Azure Database for MySQL</a> and leverage App Service managed identity for authorization, it seems that authentication using managed identities is not supported by the MySQL client library used by Ghost. So, I still rely on MySQL authentication and have to use Key Vault to securely store the database password used to connect to the Ghost database.</p>
<p>Also, when I tried to migrate the Storage account used to host the file share as persistent storage for the Ghost container to the role-based access model, I faced a similar issue, as <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/configure-connect-to-azure-storage?tabs=basic%2Cportal&amp;pivots=container-linux#limitations">that scenario is not supported by custom-mounted storage</a>.</p>
<h2 id="heading-azure-front-door-standard-and-app-service-access-restrictions">Azure Front Door Standard and App Service access restrictions</h2>
<p><a target="_blank" href="https://andrewmatveychuk.com/a-one-click-ghost-deployment-on-azure-web-app-for-containers/">The initial project version</a> used Azure CDN for traffic offloading. Later, I added <a target="_blank" href="https://andrewmatveychuk.com/ghost-deployment-on-azure-security-hardening/">an option to deploy the solution with Azure Front Door</a>, which used a managed Web Application Firewall (WAF) policy for inbound traffic inspection and site protection. Those Azure services, now renamed as classic ones, are <a target="_blank" href="https://azure.microsoft.com/en-us/updates/azure-front-door-classic-will-be-retired-on-31-march-2027/">scheduled for retirement in 2027</a>. Microsoft released a comprehensive guide on <a target="_blank" href="https://learn.microsoft.com/en-us/azure/frontdoor/tier-migration">migrating from the Azure Front Door (classic) to the Standard/Premium tier</a>. I would consider that service update a breaking change, as service features don’t map one-to-one between the classic and new service offerings. Plus, the pricing model of the new offerings is quite different, which requires careful consideration and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/frontdoor/understanding-pricing">cost estimation for your specific use case</a>.</p>
<p>Having said that, I’ve removed the option to deploy the solution with deprecated Azure CDN (Microsoft CDN (classic)) and updated the configuration to deploy Azure Front Door Standard as a more reasonably priced service for such a project. Unfortunately, the managed WAF policies are now supported only by the Premium tier, so Azure Front Door Standard now works more like a CDN, but you can still enhance it with custom WAF policies.</p>
<p>Apart from that, the Azure App service now supports <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/app-service-ip-restrictions?tabs=azurecli#restrict-access-to-a-specific-azure-front-door-instance">more targeted access restrictions</a>. In addition to restricting access to your Azure App service using the Azure Front Door service tag, you can now narrow it to a specific Front Door instance with HTTP headers. The Bicep template part for configuring such restrictions might look like the following:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="8f95d0064ab33434c0f717cb0846b225"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/8f95d0064ab33434c0f717cb0846b225" class="embed-card">https://gist.github.com/andrewmatveychuk/8f95d0064ab33434c0f717cb0846b225</a></div><p> </p>
<p>The master project template still contains <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/conditional-resource-deployment">conditional logic</a> to deploy the solution with an Azure App Service as a public endpoint or use an additional Front Door profile to serve the incoming traffic to your Ghost on Azure deployment.</p>
<h2 id="heading-other-minor-tweaks">Other minor tweaks</h2>
<p>In addition to updating Azure Resource Manager API versions for resources, removing discontinued pricing tiers for some services, and updating categories for Azure Monitor Logs diagnostic settings, I’ve also reduced the number of output values passed between the Bicep modules in the master deployment template and used <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/existing-resource">references to existing resources</a> whenever possible, as it provides more flexibility in referencing their properties in other resources. For example, instead of passing (aka exposing) Storage account access keys through output values, you can just reference them using the corresponding function on the referenced resource:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="ba3046eaf9203f40a132e3acf20ca366"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/ba3046eaf9203f40a132e3acf20ca366" class="embed-card">https://gist.github.com/andrewmatveychuk/ba3046eaf9203f40a132e3acf20ca366</a></div><p> </p>
<blockquote>
<p>For the complete deployment configuration details, check <a target="_blank" href="https://github.com/andrewmatveychuk/azure.ghost-web-app-for-containers">the source code in my GitHub repo</a>.</p>
</blockquote>
<h2 id="heading-to-be-continued">To be continued</h2>
<p>As you might have noticed, a few design decisions in that project originated from overcoming Azure service limitations. I think hosting a containerized app on Azure App Service is still quite limited, not production-wise, as it probably creates more challenges than solves them. I’m considering trying out Azure Container Apps, which will be explored.</p>
<p>Another planned modification is configuring the Azure Private link for Azure Monitor components used in the solution. It’s different from private links to other Azure services, so I plan to explore its specifics in a separate post using my Ghost on Azure project as a playground for that implementation.</p>
<p>Have you used Azure Container Apps or Azure Monitor Private Link Scopes in your projects? What was your experience with them? Please share your thoughts in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to authenticate to Azure with managed identities from non-Azure servers]]></title><description><![CDATA[In the third post in my series about secure authentication to Azure services, we will explore how to access Azure resources from servers hosted on-premises or in other clouds without storing any credentials, like client secrets or certificates, on th...]]></description><link>https://andrewmatveychuk.com/how-to-authenticate-to-azure-with-managed-identities-from-non-azure-servers</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-authenticate-to-azure-with-managed-identities-from-non-azure-servers</guid><category><![CDATA[Azure]]></category><category><![CDATA[Security]]></category><category><![CDATA[Entra ID]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 02 Jul 2024 11:00:17 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736867463233/bd3996da-18d8-4ec5-9dbe-75c113387747.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In the third post in my series about secure authentication to Azure services, we will explore how to access Azure resources from servers hosted on-premises or in other clouds without storing any credentials, like client secrets or certificates, on them.</p>
<p>For the previous posts in this series, please check the following articles:</p>
<ul>
<li><p><a target="_blank" href="https://andrewmatveychuk.com/how-to-securely-authenticate-your-applications-to-azure-services/">How to securely authenticate your applications to Azure services</a></p>
</li>
<li><p><a target="_blank" href="https://andrewmatveychuk.com/how-to-use-certificate-credentials-to-authenticate-to-azure-services/">How to use certificate credentials to authenticate to Azure services</a></p>
</li>
</ul>
<h2 id="heading-a-secure-password-is-the-one-you-dont-know">A secure password is the one you don’t know</h2>
<p>As I mentioned in my first post, I encourage people to <a target="_blank" href="https://andrewmatveychuk.com/how-to-securely-authenticate-your-applications-to-azure-services/">use managed identities to authenticate to Azure services whenever possible</a>. Using managed identities greatly simplifies the management of communication credentials in your application. Plus, it helps you mitigate many security risks related to storing and using the app’s secrets, passwords, keys, etc. You no longer need to worry about leaked passwords, rotating your credentials, or ensuring that different environments use different credentials.</p>
<p>When hosting your applications in Azure, managed identities should be your number one choice for most authentication scenarios between application components. However, what do you do in more common hybrid or multi-cloud setups when your application infrastructure is partially outside Azure?</p>
<p>In that case, you can extend Microsoft Entra ID’s identity functionality beyond Azure using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-arc/overview">Azure Arc</a> for free.</p>
<blockquote>
<p>Azure Arc provides many more useful features other than utilizing managed identities, but they are not the focus of this article.</p>
</blockquote>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922673/ce68034e-d996-4d29-aaff-2a7bff9fe3fd.png" alt class="image--center mx-auto" /></p>
<p>Technically, after you install <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-arc/servers/agent-overview">the Azure Connected Machine agent</a> on your non-Azure server, it will <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-arc/servers/managed-identity-authentication?ref=andrewmatveychuk.com#security-overview">link the server with the connected machine’s Azure identity</a> and create a local identity endpoint that can be used to request an access token for your application.</p>
<h2 id="heading-how-to-use-managed-identity-on-azure-arc-enabled-servers">How to use managed identity on Azure Arc-enabled servers</h2>
<p>As I explained in my post about <a target="_blank" href="https://andrewmatveychuk.com/how-to-use-certificate-credentials-to-authenticate-to-azure-services/">using certificate credentials to authenticate to Azure services</a>, you can configure your application to use specific identity types in a few ways.</p>
<p>The first and most preferable option is to rely on <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme?view=azure-dotnet&amp;ref=andrewmatveychuk.com#defaultazurecredential">the built-in fallback mechanism</a> of the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential">DefaultAzureCredential</a> class. If you don’t set <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme#environment-variables">the environment variables</a>, it will try to authenticate with a managed identity as the third option in the sequence. In practice, that means you don’t need to change anything in your code, and the same code that worked with authentication using client secret or client certificate will continue to work provided that you configured the required permissions for the managed identity to access the target Azure resource 👇:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="45e9c50b0be362cb90a75de0c3262868"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/45e9c50b0be362cb90a75de0c3262868" class="embed-card">https://gist.github.com/andrewmatveychuk/45e9c50b0be362cb90a75de0c3262868</a></div><p> </p>
<blockquote>
<p><strong>Note</strong>. <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-arc/servers/managed-identity-authentication?ref=andrewmatveychuk.com#prerequisites">The account used to run your application must be a member of a specific group</a> on an Azure Arc-enabled server to access the identity endpoint and get access tokens. Otherwise, you might get an error like the following:<br />Authentication Failed. ManagedIdentityCredential authentication failed: Access to the path 'C:\ProgramData\AzureConnectedMachineAgent\Tokens\292daa9f-1794-43f4-a246-6f6cc6ca4e03.key' is denied.<br />Also, if you run your application interactively, you need to do it with elevated permissions as administrator.</p>
</blockquote>
<p>The second option is to use application settings like <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/core/extensions/configuration">configuration in .NET</a> to explicitly tell your application to use a managed identity to connect to an Azure service. For example, the code below is the same that I used to connect from the sample .NET Worker service using certificate credentials configured in an appsettings.json file 👇:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="058213eac0d8ac301b8c29fde8404c19"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/058213eac0d8ac301b8c29fde8404c19" class="embed-card">https://gist.github.com/andrewmatveychuk/058213eac0d8ac301b8c29fde8404c19</a></div><p> </p>
<p>Now, you can <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/create-token-credentials-from-configuration#create-a-managedidentitycredential-type">tell the app to specifically use the managed identity</a> without changing your application code:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="5949b3497182f9a5aa466998b88e8cf7"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/5949b3497182f9a5aa466998b88e8cf7" class="embed-card">https://gist.github.com/andrewmatveychuk/5949b3497182f9a5aa466998b88e8cf7</a></div><p> </p>
<p>As you can see, compared to <a target="_blank" href="https://andrewmatveychuk.com/how-to-use-certificate-credentials-to-authenticate-to-azure-services/">using certificate-based authentication to Azure services</a>, you no longer need to worry about configuring, securing and rotating connection credentials with managed identities. All of it is done for you. You can focus more on your application functionality rather than on non-customer-facing tasks. Moreover, you can easily switch between different authentication methods by following the recommended approaches to using Azure Identity libraries in your application.</p>
<blockquote>
<p>You can also check the sample code in my GitHub repository: <a target="_blank" href="https://github.com/andrewmatveychuk/azure.authentication-samples?ref=andrewmatveychuk.com">azure.authentication-samples</a>.</p>
</blockquote>
<p>Have you tried to use managed identities on Azure Arc-enabled servers? What was your experience with it? Share your thoughts in the comments below 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to use certificate credentials to authenticate to Azure services]]></title><description><![CDATA[In my previous blog post, I showed how you could authenticate to Azure services other than using a username and password. Now, let’s explore some technical details for certificate-based authentication and how to implement it in your applications.

If...]]></description><link>https://andrewmatveychuk.com/how-to-use-certificate-credentials-to-authenticate-to-azure-services</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-use-certificate-credentials-to-authenticate-to-azure-services</guid><category><![CDATA[Azure]]></category><category><![CDATA[Security]]></category><category><![CDATA[how-to]]></category><category><![CDATA[authentication]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 18 Jun 2024 10:00:10 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736867485669/7a012433-ec1c-4527-8b9c-d49f942ff182.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In my <a target="_blank" href="https://andrewmatveychuk.com/how-to-securely-authenticate-your-applications-to-azure-services/">previous blog post</a>, I showed how you could authenticate to Azure services other than using a username and password. Now, let’s explore some technical details for certificate-based authentication and how to implement it in your applications.</p>
<blockquote>
<p>If you are wondering why you should prefer using certificates over client secrets, please check case #2 in <a target="_blank" href="https://andrewmatveychuk.com/how-to-securely-authenticate-your-applications-to-azure-services/">the previous blog post in this series</a>.</p>
</blockquote>
<h2 id="heading-overall-solution-design">Overall solution design</h2>
<p>From the overall design perspective, the required infrastructure configuration is mostly identical to what you might have when <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/on-premises-apps?">using app registration with client secrets to authenticate</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922748/daf56888-822f-4778-b927-94e8d9c0bb40.png" alt class="image--center mx-auto" /></p>
<p>You register your application in Azure, assign permissions to that app, import the <a target="_blank" href="https://azure.microsoft.com/en-us/downloads/">Azure Identity SDK</a> in your code, and use the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/on-premises-apps?#4---implement-defaultazurecredential-in-application">DefaultAzureCredential</a> object or more <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/create-token-credentials-from-configuration#support-for-azure-credentials-through-configuration">specific credential types</a> to authenticate to a target Azure resource.</p>
<blockquote>
<p><strong>Note</strong>. Here, I will use the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme">Azure Identity client library for .NET</a> to illustrate the concept. Using Azure Identity libraries for other programming languages should not be very different.</p>
</blockquote>
<p>Unfortunately, most official tutorials focus on using client secrets specified in environment variables, and there are very few samples for using client certificates. So, let’s see how we can use them.</p>
<h2 id="heading-generating-and-installing-certificates">Generating and installing certificates</h2>
<p>Before <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/quickstart-register-app#add-a-certificate">adding a certificate to your app registration</a>, first, you must get one. You can either <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/howto-create-self-signed-certificate">create a self-signed certificate</a> or get a valid certificate from a Certificate Authority. Remember that only the public key of that certificate is saved with your app registration. The certificate private key should be accessible to your application where you host it.</p>
<p>If you create a self-signed certificate, its private key will already be stored in your computer’s Windows Certificate store. If you obtained your certificate from a CA or need to use it on another machine, you need to retrieve the certificate file containing its private key and import (or save) it on your target machine.</p>
<blockquote>
<p>A .cer file contains only a public key, while a .pfx file can contain both public and private keys. For more certificate file formats, check the documentation for <a target="_blank" href="https://en.wikipedia.org/wiki/X.509">X.509 certificates</a>.</p>
</blockquote>
<p>In a production environment, certificates with their private keys should be imported into a certificate store as non-exportable if it’s a Windows host or saved into a designated folder with restricted access if it’s a Linux machine.</p>
<blockquote>
<p>Ideally, you should configure your production certificates to be <a target="_blank" href="https://learn.microsoft.com/en-us/windows/security/hardware-security/tpm/how-windows-uses-the-tpm#platform-crypto-provider">TPM-protected</a> or use another verified hardware-based solution that protects from exporting private keys. However, it’s a more advanced topic well beyond this demo scope.</p>
</blockquote>
<p>For the sake of this demo, I will use a self-signed certificate on a Windows machine:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923732/a9cd5190-2a56-48bc-b1b5-6b2f3a00f93c.png" alt class="image--center mx-auto" /></p>
<p>After you generate or import a certificate to your machine, you might want to configure permissions so the user account executing your application can access it in the certificate store. In my case, I just allowed all users on my machine to read the certificate’s private keys, so I don’t need to run it with elevated permissions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924458/01dc786c-383b-4180-97f2-64ce9372bb19.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>In a production setup, access to your certificates should be narrowed only to a specific user or local service identity used to run your application.</p>
</blockquote>
<p>The next question is how to use that certificate in your application for authentication.</p>
<h2 id="heading-using-certificates-in-your-code-to-authenticate-to-azure-app-registrations">Using certificates in your code to authenticate to Azure app registrations</h2>
<p>First, let’s see how we can retrieve our certificate from the certificate store and use it with the specialized <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/azure.identity.clientcertificatecredential">ClientCertificateCredential</a> class to understand the low-level work with certificates. Later, I will show how to abstract those details and use the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential">DefaultAzureCredential</a> class as a suggested approach.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="fb4d21ae6bf5753f4e9dd43329d43226"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/fb4d21ae6bf5753f4e9dd43329d43226" class="embed-card">https://gist.github.com/andrewmatveychuk/fb4d21ae6bf5753f4e9dd43329d43226</a></div><p> </p>
<p>So, what does that code do? First, it is trying to access the Local Machine certificate store, which we used to store our self-generated certificate. Next, it’s looking for the certificate in that store using its unique thumbprint. The Find method of the certificate store class (<a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509store">X509Store</a>) returns a certificate collection rather than a single certificate object, as you might <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509findtype">search by certificate name</a>, and the store might contain a few certificates with the same name. If the collection is not empty, we just use the first collection item as our <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.x509certificates.x509certificate2">certificate</a> (in our example, there will always be no more than one item, as we search by <a target="_blank" href="https://en.wikipedia.org/wiki/Public_key_fingerprint">certificate thumbprint</a>, which is unique for each certificate). Then, we use that certificate to initiate a ClientCertificateCredential object and read a Key Vault secret using the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/security.keyvault.secrets-readme">Azure Key Vault secret client library for .NET</a>.</p>
<blockquote>
<p>Here, I use Key Vault as a target service to access in Azure to demonstrate the concept and to have some values to output in the demo console app. In real life, Key Vaults might be used to store secrets, keys and other certificates required for an application to access other services. On how to cache Azure Key Vault secrets, take a look at the following article: <a target="_blank" href="https://learn.microsoft.com/en-us/samples/azure/azure-sdk-for-net/azure-key-vault-proxy/">Cache certain responses from Key Vault</a>.</p>
</blockquote>
<p>If you have configured everything correctly, you should see your Key Vault secret value in the console.</p>
<p>The drawback of that approach is that you explicitly rely on the certificate-based authentication method only in your application. If you later need to switch to another authentication method, you will need to modify your code, rebuild your app, and redeploy it. Plus, that approach won’t work in Linux environments, as there is no single designated certificate store. A better way would be to extract authentication configuration options into config files or environment variables so you can later change your authentication method without touching the application code.</p>
<p>The <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/azure.identity.defaultazurecredential">DefaultAzureCredential</a> class has a built-in fallback mechanism that <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme?#defaultazurecredential">attempts to use multiple authentication methods in a specific order</a>. Let’s see how we can simplify the code and make it more portable.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="45e9c50b0be362cb90a75de0c3262868"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/45e9c50b0be362cb90a75de0c3262868" class="embed-card">https://gist.github.com/andrewmatveychuk/45e9c50b0be362cb90a75de0c3262868</a></div><p> </p>
<p>As you can see, you no longer need to do any low-level work fetching and using certificates in your code. You delegate that part to the DefaultAzureCredential class, which, first in sequence, tries to use the authentication method specified via <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/overview/azure/identity-readme?#environment-variables">environment variables</a>.</p>
<p>In our case, we need to set the following environment variables to use certificate-based authentication:</p>
<ul>
<li><p>AZURE_CLIENT_ID, which is an application ID of your application registration,</p>
</li>
<li><p>AZURE_TENANT_ID, which is your Azure tenant ID,</p>
</li>
<li><p>AZURE_CLIENT_CERTIFICATE_PATH, which is your local path to a certificate file containing its private key,</p>
</li>
<li><p>AZURE_CLIENT_CERTIFICATE_PASSWORD (optional), which is required to read the password-protected certificate file,</p>
</li>
<li><p>KEY_VAULT_NAME (code-specific) represents your Azure Key Vault name, so you don’t have it hardcoded in your application.</p>
</li>
</ul>
<p>Unfortunately, you can only reference certificates stored locally in files when using that approach. On the one hand, it makes your application more portable, as you can read a certificate from a file both on Windows and Linux. On the other hand, it makes protecting your certificate’s private key harder, as you need to handle the password protecting the key, similar to handling a client secret. Plus, you should restrict access to the certificate file.</p>
<p>What can be done if you don’t want to lock to a specific credential type in your application and prefer storing your client certificate in a Windows Certificate Store? In that scenario, you can <a target="_blank" href="https://learn.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-8.0&amp;viewFallbackFrom=aspnetcore-3.0">use Microsoft.Extensions.Azure library</a> to <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/create-token-credentials-from-configuration">create different credential types from key-value pairs defined in appsettings.json and other configuration files</a>. The modified code might look like the following:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="058213eac0d8ac301b8c29fde8404c19"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/058213eac0d8ac301b8c29fde8404c19" class="embed-card">https://gist.github.com/andrewmatveychuk/058213eac0d8ac301b8c29fde8404c19</a></div><p> </p>
<p>We register our target Azure service with the <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.azure.azureclientservicecollectionextensions.addazureclients">AddAzureClients</a> method, and it will be automatically configured with a configured instance of a credential type. All credential configuration details will be specified in the following appsettings.json file:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="772bf1699c2ef695c53e1bff791aabfb"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/772bf1699c2ef695c53e1bff791aabfb" class="embed-card">https://gist.github.com/andrewmatveychuk/772bf1699c2ef695c53e1bff791aabfb</a></div><p> </p>
<p>In the sample above, we authenticate using the ClientCertificateCredential type and retrieve the certificate from the local certificate store, as in the initial sample code.</p>
<p>Unfortunately, there seems to be no way to explicitly configure the retrieval of a certificate from a file using that approach. So, if you need to run your application on a Linux host, you can reduct your appsettings.json file to use the DefaultAzureCredential class provided that you define the environment variables for certificate-based authentication, as in the previous example:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="005feff80a475ad3dc532d1e3b41ecf2"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/005feff80a475ad3dc532d1e3b41ecf2" class="embed-card">https://gist.github.com/andrewmatveychuk/005feff80a475ad3dc532d1e3b41ecf2</a></div><p> </p>
<p>The last approach is more flexible than the option with environment variables only, as you can choose where to store your certificate depending on your host environment.</p>
<h2 id="heading-whats-next">What’s next</h2>
<p>If you followed till the end of this post, you might conclude that using certificates to authenticate to Azure services is not easy. There are a lot of nuances about managing and securing access to certificates. Also, configuring your application to use certificate-based authorization is not as straightforward as using the DefaultAzureCredential class and putting client secrets in environment variables. However, I hope that provided samples will help you better understand how to implement certificate-based authentication in your applications.</p>
<blockquote>
<p>You can also check the sample code for certificate-based authentication in my GitHub repository: <a target="_blank" href="https://github.com/andrewmatveychuk/azure.authentication-samples">azure.authentication-samples</a>.</p>
</blockquote>
<p>In the next post in this series, I will show how you can simplify such an authentication process in your apps using managed identities. So, stay tuned and hit the subscribe button! 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to securely authenticate your applications to Azure services]]></title><description><![CDATA[There are multiple ways to authenticate your applications when accessing Azure services, and to be honest, authentication on its own is a vast and complex area. However, depending on your context, requirements, and application location (Azure-hosted ...]]></description><link>https://andrewmatveychuk.com/how-to-securely-authenticate-your-applications-to-azure-services</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-securely-authenticate-your-applications-to-azure-services</guid><category><![CDATA[Azure]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 28 May 2024 13:00:18 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736867522289/2bfcf0f9-75b1-4eec-bc8a-46f9476e11d1.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>There are multiple ways to authenticate your applications when accessing Azure services, and to be honest, authentication on its own is a vast and complex area. However, depending on your context, requirements, and application location (<a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/?tabs=command-line#recommended-app-authentication-approach">Azure-hosted or hosted outside of Azure</a>), your authentication options will usually be limited to only a subset of that variety. So, instead of reviewing and understanding all possible authentication approaches in detail, it might be more practical to explain possible authentication options using a few case studies.</p>
<p>My primary intent with this article is to show how to eliminate using plain text credentials in application configuration options and connection strings. In the follow-up posts, I will share some code samples and configuration details to help you get started.</p>
<blockquote>
<p><strong>Disclaimer.</strong> The topic of authentication and authorization in Azure is much more complex than the cases described in this article. The cases I present here don’t cover all possible authentication scenarios and should be treated as examples to help you understand the topic.</p>
</blockquote>
<h2 id="heading-case-1-use-managed-identity-whenever-possible">Case 1. Use managed identity whenever possible</h2>
<p>Imagine you have an Azure App Service, Azure Function, or Azure VM that needs to connect to some database like Azure SQL Database. The application somehow needs to authenticate to that data source. What you can usually see:</p>
<ul>
<li><p>People create an SQL user because it’s easy, or they already did it when developing locally on their machine, or they followed tutorials showcasing how to connect to a database using username and password, or for any other reason.</p>
</li>
<li><p>In the best case, that user has limited access to the target database only with required permissions. In the worst case, which is more common, that user has full access to the database (or all databases) because, you know, developers don’t want to spend their time troubleshooting permission issues.</p>
</li>
<li><p>The password complexity of that user is mostly far from what is considered to be a strong password. If you think of something like “Secret123”, you are close to guessing it.</p>
</li>
<li><p>That username and password are used in plain text in connection strings, configuration files, or environment variables, exposing them to anybody who can access the app environment.</p>
</li>
<li><p>Those credentials are shared with other applications that need access to the same database, making updating them painful and error-prone. So, forget about password rotation.</p>
</li>
<li><p>To make matters worse, the same username and password are used in both development and production environments.</p>
</li>
</ul>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922822/71b8708f-1dec-4eef-9a91-7666fd5c2549.png" alt class="image--center mx-auto" /></p>
<p>It looks like a terrifying nightmare for a security-aware person, and it is a harsh reality of what can be encountered even in top business-critical systems.</p>
<p>Besides the risk of such a user (aka a service account) being easily compromised, timely detection of its malicious use is usually not the case. In my experience, if people go so far as to make their application authentication insecure, such organizations also have little to no capabilities for detecting compromised accounts. Otherwise, they would pay more attention to that risk.</p>
<p>Luckily for us, the application authentication to the database in the described case can be improved with little effort. If you haven’t heard of the concept of <a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview">managed identities for Azure resources</a>, I strongly recommend familiarizing yourself with it. Their use can greatly improve your application security and boost your development experience.</p>
<p>A modernized version of our sample application can look like the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923770/e2c9664a-289d-48db-9c60-83032613768d.png" alt class="image--center mx-auto" /></p>
<p>Simply put, your application can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/app-service/overview-managed-identity">utilize a managed identity provided by the underlying Azure resource</a> on which it’s hosted. If you have ever configured a connection from an IIS-hosted application to an on-premises SQL Server, it is similar to <a target="_blank" href="https://learn.microsoft.com/en-us/iis/manage/configuring-security/application-pool-identities">using machine accounts to authenticate to target servers</a>.</p>
<blockquote>
<p>Back then, many administrators expressed their concerns that granting access via a machine account could expose that identity to any application running on that machine. However, cloud resources, especially serverless ones, are much more granular regarding the number of hosted applications. Plus, using a managed identity is much more secure than putting usernames and passwords in plain text in files or environment variables where they can be extracted from and then used elsewhere to connect to target resources.</p>
</blockquote>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/managed-identities-status">The list of Azure services supporting managed identity</a> and role-based control is constantly increasing. So, it’s always worth checking if your application’s Azure resources can leverage that authentication approach.</p>
<p>By upgrading your application authentication from password-based to using managed identities, you:</p>
<ul>
<li><p>No longer have a password that can be compromised or shared inappropriately.<br />  Don’t need to think about configuring credentials in your application.</p>
</li>
<li><p>Can forget about credential rotation, as it’s done for you.</p>
</li>
<li><p>Don’t have to share the same credentials with other applications, as it’s easier to configure access for another application using its managed identity,</p>
</li>
<li><p>Don’t need to worry about updating your connection credentials when pushing new application versions between environments.</p>
</li>
</ul>
<p>Of course, using managed identities doesn’t eliminate all security risks. You still need to grant permissions to them following the principle of least privilege to avoid excessive access. Plus, you should always consider other security risks as well. For example, if your application is vulnerable to SQL injection attacks, those attacks can be executed regardless of whether you use managed identities or not to connect to your database.</p>
<h2 id="heading-case-2-prefer-certificate-based-authentication-over-password-based">Case 2. Prefer certificate-based authentication over password-based</h2>
<p>You might say that everything I described in the previous case looks good if all your application parts run in Azure. However, what to do in hybrid scenarios when connecting to some Azure service from on-premises? Let’s look at what we can do in that case.</p>
<p>The preferred way, suggested by Microsoft, is to <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/on-premises-apps">register your application in an Azure tenant</a>, creating a logical representation of it, which can be used to assign permissions and access other Azure-hosted and Microsoft-provided services. Think of it as creating an identity for your application that is used to retrieve access tokens to various services.</p>
<blockquote>
<p>Why <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/?tabs=command-line#advantages-of-token-based-authentication">that authentication approach is preferable</a> to configuring access at the destination application level? For example, you can create an SQL-contained user in an Azure SQL Database and use it to connect from your on-premises server.<br />From the security perspective, it will be as bad as in the previous case. Firstly, you again have a username and password to take care of. Secondly, access management becomes more decentralized and complex as it’s delegated to the application (SQL Server) level. Lastly, you will lack all the identity protection features that an identity provider like Entra ID provides. If those credentials are stolen or misused, you might notice malicious activity in transaction logs long after it happens.</p>
</blockquote>
<p>Unfortunately, most tutorials and official documentation describe (and promote in a way) <a target="_blank" href="https://learn.microsoft.com/en-us/dotnet/azure/sdk/authentication/on-premises-apps?#3---configure-environment-variables-for-application">how to authenticate to Azure resources using client secrets</a>, which is similar to using passwords. Thanks to the application registration service design in Entra ID, client secrets are autogenerated complex passwords. Plus, you can <a target="_blank" href="https://learn.microsoft.com/en-us/graph/api/resources/applicationauthenticationmethodpolicy">apply policies to your tenants limiting their maximum validity period</a>, forcing application owners to rotate them on a regular basis.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924481/29e48525-2651-43a8-8a41-95c49993c7b3.png" alt class="image--center mx-auto" /></p>
<p>On the surface, it looks easy, as you just need to configure your environment variables, and you are good to go. The dark side of that approach is that those client IDs with their secrets can be shared as easily as usernames and passwords, making them vulnerable to misuse, unauthorized exposure, and credential leaks. The temptation and ability to generate never-expiring client secrets (many people don’t want to be burdened with secret rotation) make such app registrations ideal candidates for identity attacks.</p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/entra/identity-platform/security-best-practices-for-app-registration#certificates-and-secrets">Certificate-based authentication is considered more secure than password-based (client secret) one</a>. First of all, with certificates, you rely on asymmetric encryption, when only the public part of your certificate is stored along with an app registration, and the private part is secured on your application side. Apart from that, the certificate’s private key can be securely stored in certificate stores, where it can be read only by applications hosted on target machines without the possibility to export and transfer it somewhere else. Also, the complexity of working with certificates and encryption algorithms they use makes them less prominent targets for attacks compared to client secrets.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925255/2af35362-e5e4-417c-ab26-9158944ecc4d.png" alt class="image--center mx-auto" /></p>
<p>For example, in the diagram above, the client application uses a certificate stored in a Windows Certificate Store to authenticate to the related app registration and obtain an access token. Specialized administrative solutions can manage certificates, so developers don’t even need to touch them. Certificate rotation can performed independently by system administrators. Developers might not even have access to those certificates and can only use them in a controlled manner on managed devices.</p>
<h2 id="heading-case-3-use-managed-identity-with-azure-arc-enabled-servers">Case 3. Use managed identity with Azure Arc-enabled servers</h2>
<p>The complexity of using certificate-based authentication also makes it harder to adapt, especially when you don’t have the capacity or expertise to manage those certificates efficiently. So, what can we do in that case?</p>
<p>What if I told you that there is a way to use managed identities even for servers hosted outside of Azure? By <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-arc/servers/deployment-options">onboarding your on-premises server, AWS EC2 instance, or any external machine to Azure Arc</a>, you effectively create a logical representation of it in your tenant, which you can use to manage that resource from Azure and provide it access to other Azure resources using its service principal. The Azure Arc agent running on such machines is responsible for maintaining the connection to the Azure cloud and providing access to that machine authentication context. Applications running on an Azure Arc-enabled server can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-arc/servers/managed-identity-authentication">use that authentication context to access Azure resources the same way if that server was hosted in Azure and you used its managed identity for authentication</a>.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925884/e8632b43-8d27-4687-9677-aa3cb9fcbff1.png" alt class="image--center mx-auto" /></p>
<p>Although onboarding your servers to Azure Arc has an initial overhead, after the servers are connected, you can leverage their managed identities the same way you do with Azure VMs. For organizations with many on-premises resources, this can be a lifesaver when planning integration with Azure resources.</p>
<h2 id="heading-case-4-use-aws-secrets-manager-aws-certificate-manager-or-analogs-to-store-your-credentials">Case 4. Use AWS Secrets Manager, AWS Certificate Manager, or analogs to store your credentials</h2>
<p>You might ask, okay, what do I do if I need to connect to Azure resources from another cloud and managed identity is not an option for me? In that case, using cloud services specifically designed to store and retrieve sensitive information like secrets, keys, and certificates would be security-wise.</p>
<p>For example, consider your client is hosted in AWS. It uses AWS Lambda functions to retrieve information from an Azure-hosted API.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681926648/eabbf688-e4a5-43bf-99cf-60b15085f296.png" alt class="image--center mx-auto" /></p>
<p>You can create application registration for your AWS-hosted client in your Azure tenant and generate client credentials. Next, you store those credentials in AWS Secret Manager if it’s a client secret or in AWS Certificate Manager if it’s a client certificate. You <a target="_blank" href="https://docs.aws.amazon.com/secretsmanager/latest/userguide/retrieving-secrets_lambda.html">provide access to read those credentials to your Lambda function and then use them</a> to authenticate to the target Azure resource.</p>
<p>The bottom line is that if you cannot get rid of credentials, you should ensure their safety on the client side. If your Lambda function references those secrets, you don’t even need to know their values. In addition, those secrets can be rotated separately without touching the client application.</p>
<p>In some cases, like connecting from third-party applications or SaaS services, you might have no other option than using client secrets only due to application limitations. However, in such scenarios, you also entrust your credentials handling to that third party. Here, you should carefully consider what permissions you grant to that application in your Azure tenant. If it needs excessive write (or read) access, it definitely should be a red flag for you.</p>
<h2 id="heading-in-conclusion">In conclusion</h2>
<p>As you might see from the described use cases, there are plenty of more secure ways to authenticate to Azure services than just using usernames and passwords. Unfortunately, many developers overlook them when designing applications or configuring integration with Azure-hosted solutions. Old habits die hard. Despite the times when applications were mostly self-hosted and connecting to a database located on another server using database user and password are long gone, that legacy still lives in many organizations. The unwillingness to take that extra step to secure your application connections leads to leaked passwords, compromised accounts, and data breaches. So, next time when you deploy an application to production and use usernames and passwords to configure it, remember that <a target="_blank" href="https://www.ibm.com/reports/data-breach">the average cost of a data breach reached an all-time high in 2023 of USD 4.45 million</a> and increases year over year.</p>
<p>Subscribe 👇 to stay tuned for follow-up posts in this series. Also, share your thoughts on authenticating to Azure services in the comments section!</p>
]]></content:encoded></item><item><title><![CDATA[How to send custom Azure Automation Runbook logs to Log Analytics]]></title><description><![CDATA[In this blog post, I will explain why it might be helpful to implement custom logging in your Azure Automation runbooks, why to use Log Analytics as a log destination, and how to implement a logging framework that can be scaled and shared by your run...]]></description><link>https://andrewmatveychuk.com/how-to-send-custom-azure-automation-runbook-logs-to-log-analytics</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-send-custom-azure-automation-runbook-logs-to-log-analytics</guid><category><![CDATA[Azure]]></category><category><![CDATA[Powershell]]></category><category><![CDATA[Azure Monitor]]></category><category><![CDATA[logging]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Mon, 26 Feb 2024 13:00:03 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736868359551/640f4294-fd01-4252-86e4-ea2d5b56c699.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>In this blog post, I will explain why it might be helpful to implement custom logging in your Azure Automation runbooks, why to use Log Analytics as a log destination, and how to implement a logging framework that can be scaled and shared by your runbooks.</p>
<blockquote>
<p>Note. I will discuss PowerShell runbooks here, but many concepts described below also apply to other Azure Automation runbook types.</p>
</blockquote>
<h2 id="heading-why-use-custom-logging-for-azure-automation-runbooks">Why use custom logging for Azure Automation runbooks?</h2>
<p>Azure Automation accounts support extensive capabilities for tracing and logging runbook execution results. If you are familiar with <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_redirection">PowerShell streams and how they work</a>, you can also use them in your runbooks. All regular PowerShell logging practices with verbose, debugging, warning, and error outputs can be used in Automation runbooks without any modifications. The only thing to remember is that you need to explicitly enable their logging in the runbook job history for some streams.</p>
<p>However, the runbook job history is available by default only for <a target="_blank" href="https://learn.microsoft.com/en-us/azure/automation/automation-managing-data#data-retention">the last 30 days</a>. So, if you want to keep your logs longer or run some queries on them, you can send them for long-term storage to a Storage account, a Log Analytics workplace, or an external system <a target="_blank" href="https://learn.microsoft.com/en-us/azure/automation/automation-manage-send-joblogs-log-analytics">using diagnostic settings</a>. Just remember to include the JobStreams log category in your settings.</p>
<p>Those system options for runbook logging will be enough for most use cases. However, those default logging options can add too much irrelevant data to the logs. Plus, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/automation/automation-manage-send-joblogs-log-analytics#sample-queries-for-job-logs-and-job-streams">extracting application-specific details from those system logs</a> might pose some challenges, as there are no technical restrictions on what application-specific information should be present in those logs and how it should be formatted. So, can we implement <em>structured</em> custom logs, and where should we store them?</p>
<h2 id="heading-why-send-your-custom-runbook-logs-to-log-analytics">Why send your custom runbook logs to Log Analytics?</h2>
<p>Although there are no technical limitations on where to store your custom logs in Azure, some Azure services are better suited for that purpose than others. For instance, the available storage options in Azure resource diagnostic settings list Log Analytics workspaces and Storage accounts as the two most common places to store log data.</p>
<blockquote>
<p>You can certainly use any form of storage or database for your logs, as you might have specific requirements for your solution. However, if you don’t need to use Azure SQL Database, Azure Cosmos DB, or any other functional storage solution specifically, I suggest you start with one of the default options present in Diagnostic settings.</p>
</blockquote>
<p>Log Analytics workspaces store data in a tabular format and provide extensive query capabilities. So, if you need to build some analytics on top of your logs or use them extensively for troubleshooting, definitely consider them first.</p>
<p>Storage accounts can store data in different forms, such as files, blobs, and tables, but they lack any reporting and analytical capabilities at the service level. Using data stored in them requires using some external solutions, which can make your overall design more complex. They are a suggested log storage destination in scenarios when you need to keep a log archive that is rarely accessible and you want to make it as cheap as possible.</p>
<h2 id="heading-how-to-send-data-from-powershell-to-a-log-analytics-workspace">How to send data from PowerShell to a Log Analytics workspace?</h2>
<p>The current suggested approach for sending data to a Log Analytics workspace is to use <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/logs/logs-ingestion-api-overview">the Logs Ingestion API in Azure Monitor</a>. Compared to the deprecated Data Collector API, now you can create a Data Collection Endpoint (DCE) resource in your Azure subscription, which serves as an entry point for sending your data to a workspace. Plus, as a part of the required setup, you create Data Collection Rules (DCR) that group transformation logic you can apply to the incoming data before it is sent to the destination workspace.</p>
<p>Unfortunately, Microsoft hasn’t yet provided a PowerShell <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/logs/logs-ingestion-api-overview#client-libraries">SDK to abstract REST calls to the Log Ingestion API</a>. However, the provided sample PowerShell code can be easily adapted for making calls to the API from a runbook like the following:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3a747949610efb23fd39617395027e68"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/3a747949610efb23fd39617395027e68" class="embed-card">https://gist.github.com/andrewmatveychuk/3a747949610efb23fd39617395027e68</a></div><p> </p>
<p>As you can see from the runbook, it authenticates to Azure <a target="_blank" href="https://learn.microsoft.com/en-us/azure/automation/enable-managed-identity-for-automation">using a system-assigned identity for the Automation account</a>. That identity should be assigned <a target="_blank" href="https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#monitoring-metrics-publisher">the Monitoring Metrics Publisher role</a> so the target DCR can push data to it. That way, you don’t have to maintain a separate application registration in your tenant to access your collection rules.</p>
<blockquote>
<p>For sure, you can authenticate to your DCR rules using custom application registrations, as presented in official samples, if you want to make your access model as granular as possible. However, it might be less practical than using managed identities, as you must maintain those registrations and rotate their access credentials.</p>
</blockquote>
<p>After the authentication, it uses the obtained Azure context to acquire an access token for a specific API, which is Azure Monitor in our case. The token is immediately converted to a secure string <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-restmethod?#-token">to be used by the follow-up Invoke-RestMethod cmdlet</a>. Next, it makes the preconfigured API call and outputs response results.</p>
<p>Provided that you constructed the correct JSON payload that your DCR expects from your PowerShell object, you will see new log entries in your custom workspace table in a moment.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922713/20bbc883-8a12-4a3f-a51e-551af1e5ac9f.png" alt /></p>
<blockquote>
<p>Please note that the initial population of the destination custom table might take a while. After it is set up and indexed, all further entries can be queried much faster.</p>
</blockquote>
<p>You can stop here or make your runbook logging solution more robust and reusable. For example, you can extract the logic for pushing your log entries into a separate function packed in a custom PowerShell module that you can import into your Automation accounts. That way, you can make your runbook code cleaner and reuse your logging function as a regular PowerShell cmdlet in your other runbooks.</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="3c79a5a94a8254b43a185b05cd93d5a9"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/3c79a5a94a8254b43a185b05cd93d5a9" class="embed-card">https://gist.github.com/andrewmatveychuk/3c79a5a94a8254b43a185b05cd93d5a9</a></div><p> </p>
<p>In addition to that, you can also look into defining <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_classes">custom PowerShell classes</a> to improve the type safety of your log entry object and make your logs more structured, as with <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_pscustomobject">PSCustomObject</a>, you can assign its properties any values, which may lead to running into errors on the DCR transformation step due to mismatch in expected input format:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="f66e81864f2a96c8ff3a772b38cdc009"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/f66e81864f2a96c8ff3a772b38cdc009" class="embed-card">https://gist.github.com/andrewmatveychuk/f66e81864f2a96c8ff3a772b38cdc009</a></div><p> </p>
<p>As you can see from that sample code, you can define <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_enum">custom PowerShell enum types</a> to limit the number of allowed values for specific log entry properties used for categorization. Also, explicitly defining class property types will help you correct formatting errors when creating log entry objects in your runbooks, not when a receiving DCR transformation stream rejects those entries.</p>
<p>Now, your updated runbook that sends custom logs to a Log Analytics workspace might look like this:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="29064cb51f7c126698bbd121ecc0d404"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/29064cb51f7c126698bbd121ecc0d404" class="embed-card">https://gist.github.com/andrewmatveychuk/29064cb51f7c126698bbd121ecc0d404</a></div><p> </p>
<blockquote>
<p>There are also some community solutions for that, like the <a target="_blank" href="https://github.com/KnudsenMorten/AzLogDcrIngestPS">AzLogDcrIngestPS module</a> by Morten Knudsen, which I honestly recommend checking, that allows you to automate all the work around configuring the resources required for the Log Ingestion API. However, introducing an unofficial module dependency only for a small portion of its functionality, like only sending logs, might not always be desirable.</p>
</blockquote>
<p>Have you implemented custom logging in your Azure Automation runbooks? Did you find it helpful, and why? Share your opinions in the comments below 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to use change history in Azure Monitor Workbooks]]></title><description><![CDATA[Anyone who has worked in IT Operations long enough knows that many incidents and service outages occur due to recent application configuration changes. If an application has an SLA, incident handling routines often explicitly instruct users to check ...]]></description><link>https://andrewmatveychuk.com/how-to-use-change-history-in-azure-monitor-workbooks</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-use-change-history-in-azure-monitor-workbooks</guid><category><![CDATA[Azure Monitor]]></category><category><![CDATA[IT Operations]]></category><category><![CDATA[#howtos]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Mon, 07 Aug 2023 11:00:27 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736867563882/ac1c9114-6c53-4549-b965-40b18395b3f2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Anyone who has worked in IT Operations long enough knows that many incidents and service outages occur due to recent application configuration changes. If an application has an SLA, incident handling routines often explicitly instruct users to check for such changes as one of the first troubleshooting steps.</p>
<p>Despite Azure providing many options for tracking and analyzing resource configuration changes, those tools are <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/change/change-analysis#change-analysis-architecture">spread across different services and sections of the portal</a>. While some of them can be used out-of-the-box, others, like <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/vm/vminsights-change-analysis">Change analysis in VM insights</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/automation/change-tracking/overview">Change Tracking in Azure Automation</a>, or saving Activity logs to Log Analytics workspaces, require additional configuration before use.</p>
<p>Last year, Microsoft announced <a target="_blank" href="https://techcommunity.microsoft.com/t5/azure-observability-blog/change-analysis-ga-announcement/ba-p/3613302">the availability of multiple Change Analysis features</a>. What makes them great is that they use <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/resource-graph/how-to/get-resource-changes">Azure Resource Graph</a> under the hood, which has an extensive list of SDKs. From the practical perspective, that allows you to pull the data about changes from the graph into many Azure-native monitoring solutions and third-party ones. Here, I will explore how you can view the history of such configuration changes in Azure Monitor Workbooks.</p>
<h2 id="heading-pull-data-about-changes-in-azure-monitor-workbooks">Pull data about changes in Azure Monitor Workbooks</h2>
<p>In Azure Monitor Workbooks, you can use <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#azure-resource-graph">the Azure Resource Graph data source</a> to execute supported KQL queries and display their results in various formats. For example, you can use the following query to pull all changes for the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/resource-graph/how-to/get-resource-changes?#best-practices">last 14 days</a> scoped to a resource group:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="9ab0b3d8a05e63e81600fe60c77dcc43"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/9ab0b3d8a05e63e81600fe60c77dcc43" class="embed-card">https://gist.github.com/andrewmatveychuk/9ab0b3d8a05e63e81600fe60c77dcc43</a></div><p> </p>
<p>The resulting table might look like the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922771/c03b081b-52ce-4a96-a996-6a77415005b2.png" alt class="image--center mx-auto" /></p>
<p>Unfortunately, the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#change-analysis-preview">Change Analysis data source</a>, which is in preview as of now, scopes its results to a single resource only. That might not be so helpful in cases when a change in another resource causes an alert linked to your target resource. For example, an error in your web app might be caused by a changed or deleted certificate in a Key Vault.</p>
<h2 id="heading-list-active-alerts-in-azure-monitor-workbooks">List active alerts in Azure Monitor Workbooks</h2>
<p>Obviously, having a list of active alerts in the same workbook alongside the configuration changes would be helpful to correlate between them. Previously, there was a separate data source for pulling the information about Azure Monitor alerts, but now <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/resource-graph/samples/samples-by-category?tabs=azure-cli#view-recent-azure-monitor-alerts">the alert info is available via Azure Resource Graph</a>. The following query will return the list of all active Azure Monitor alerts in the target resource group:s</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="dbe46caabe9b89df1cc6c338600b927b"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/dbe46caabe9b89df1cc6c338600b927b" class="embed-card">https://gist.github.com/andrewmatveychuk/dbe46caabe9b89df1cc6c338600b927b</a></div><p> </p>
<p>A sample result of that query:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923856/cb1a7e01-8fa4-47f9-9c81-3fc0b9c2d488.png" alt class="image--center mx-auto" /></p>
<p>Another option to get the alert information is to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-commonly-used-components#use-azure-resource-manager-to-retrieve-alerts-in-a-subscription">pull it with an Azure Resource Manager query</a>.</p>
<h2 id="heading-improve-troubleshooting-experience-with-azure-monitor-workbooks">Improve troubleshooting experience with Azure Monitor Workbooks</h2>
<p>Sometimes, you might need to get back to resolved alerts to review and analyze them. You can achieve that by using parameters in Azure Monitor Workbooks.</p>
<p>For example, you can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-time">add parameters to display alerts that fired during the last N hours/days</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-dropdowns">optionally include resolved alerts to your results</a>. After that, you can reference those parameters in your query to make it more dynamic:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="6528ac35aa295ff215a132a09a81b5f8"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/6528ac35aa295ff215a132a09a81b5f8" class="embed-card">https://gist.github.com/andrewmatveychuk/6528ac35aa295ff215a132a09a81b5f8</a></div><p> </p>
<p>Also, you can improve your user experience by <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-commonly-used-components#traffic-light-icons">formatting the results with colorful icons</a> and using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-grid-visualizations#style-a-grid-column">column formatting</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924543/655274b3-9e16-4309-8c14-f54b9515d9a9.png" alt class="image--center mx-auto" /></p>
<blockquote>
<p>If you need to analyze changes preceding alerts fired beyond the last 14 days, you will need to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/resource-graph/tutorials/logic-app-calling-arg">schedule exporting those logs to some external store like Log Analytics</a> and modify your workbook to pull the change info from a workspace instead of Azure Resource Graph directly.</p>
</blockquote>
<p>Because, from the troubleshooting perspective, it makes sense to look into the changes that happened before the alert was fired and not after, you can take an extra step and make the results in your change history view depending on a specific alert you select in your alerts view. For that, you can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-configurations#set-up-a-grid-row-click">define a workbook parameter that is populated when you click on a row in your alert grid view</a> and use it in a follow-up change history query to filter the changes that happened earlier than the alert fired:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925134/34704399-3e9f-421e-8989-5366c547441f.png" alt class="image--center mx-auto" /></p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="d7722c8bdfc4a2ad5d82ad04e178adda"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/d7722c8bdfc4a2ad5d82ad04e178adda" class="embed-card">https://gist.github.com/andrewmatveychuk/d7722c8bdfc4a2ad5d82ad04e178adda</a></div><p> </p>
<p>The improved change history view:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925796/5d9f1e55-2002-4974-91e8-1f5b7a7c3ec3.png" alt class="image--center mx-auto" /></p>
<p>You can also extend your workbook with information about <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#azure-resource-health">resource</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#workload-health">workload</a> health in the target scope to immediately check if an outage is related to resource availability.</p>
<p>Feel free to check <a target="_blank" href="https://github.com/andrewmatveychuk/azure.monitor/tree/master/workbooks">my GitHub repository</a> for the complete sample workbook template for correlating between alerts and configuration changes in Azure.</p>
<p>What is your experience analyzing changes that caused application performance or availability issues? What tools do you use the most to review configuration changes in Azure resources? Please share your thoughts in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[Naming convention for Azure resources]]></title><description><![CDATA[The primary intent of having a naming convention for Azure resources is to be able to identify essential information about a resource, for example:

related service or product for the resource;

resource type;

role of the resource or resource identi...]]></description><link>https://andrewmatveychuk.com/naming-convention-for-azure-resources</link><guid isPermaLink="true">https://andrewmatveychuk.com/naming-convention-for-azure-resources</guid><category><![CDATA[Azure]]></category><category><![CDATA[Naming Conventions]]></category><category><![CDATA[Cloud Governance]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 25 Jul 2023 11:00:49 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736867587845/980705c7-dcaf-4311-a159-e1b059752a9a.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>The primary intent of having a naming convention for Azure resources is to be able to identify essential information about a resource, for example:</p>
<ul>
<li><p>related service or product for the resource;</p>
</li>
<li><p>resource type;</p>
</li>
<li><p>role of the resource or resource identifier used to differentiate between multiple instances of the same resource;</p>
</li>
<li><p>environment, etc.</p>
</li>
</ul>
<p>Many organizations tend to adopt some variation of the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-best-practices/resource-naming">Microsoft-suggested naming recommendations</a>. If you haven’t reviewed them yet, I strongly encourage you to do so. However, please be mindful and treat those naming patterns as suggestions and not strict rules. Picking them up without a second thought might not reflect how your organization operates Azure services.</p>
<p>If you haven’t implemented a naming convention in your Azure environment, now is a good time to review the following naming patterns, which can be a good starting point on your cloud journey.</p>
<h2 id="heading-generic-naming-rule">Generic Naming Rule</h2>
<p>The general convention I suggest for naming your Azure resources when you don’t have any specific requirements is the following:</p>
<p><code>&lt;product name&gt;-&lt;type of service abbreviated&gt; [-&lt;environment&gt;][-&lt;identifier&gt;]</code></p>
<p>Where:</p>
<ul>
<li><p><em>product name</em>: product, application, service or platform;</p>
</li>
<li><p><em>type of service abbreviated</em>: an abbreviation identifying Azure service type (see the list of sample abbreviations below);</p>
</li>
<li><p><em>environment</em> (optional): environment name, e.g., production (prd), development (dev), testing (qa) or staging (stg);</p>
</li>
<li><p><em>identifier</em> (optional): role of the resource or resource identifier used to differentiate between multiple instances of the same resource.</p>
</li>
</ul>
<blockquote>
<p>Why put the application name first and not the resource type? From my practice, it improves the search experience on the Azure portal and when using Azure PowerShell or CLI. Usually, when you are looking for a particular Azure resource, you first think of an application you work with and then of the resource type. Besides, people remember name patterns and tend to type names from the start. For example, when you have some resource name like ‘rg-myapp1’ and start typing ‘rg’ in the search box on the portal, you are likely to get the list of random resource groups first, which can be quite long. Then you need to continue typing to limit the results to a specific application. The console experience is similar – you need to type ‘*myapp1*’ or ‘rg-myapp1*’ when you could have saved your keystrokes and just typed ‘myapp1*’ to limit your search at the initial step to the resource of your application only. That change in the naming pattern might not seem like a big deal, but it can save your organization a lot of time, considering how often people search for a particular resource.</p>
</blockquote>
<p><strong>Use lowercase letters and numbers only</strong> in resource names. Spaces and special characters, except for hyphens, should not be used, as there are many cases when <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules">Azure resource restrictions</a> limit their use. Also, because of those restrictions (resource name length), <strong>all the abbreviations and codes should be as short as possible</strong> to leave more room for using meaningful product/application names.</p>
<p><strong>The</strong> <a target="_blank" href="https://en.wiktionary.org/wiki/kebab_case"><strong>kebab-case</strong></a> <strong>format should be used</strong> whenever possible. Hyphens can be removed for services where only alphanumeric characters are allowed, e.g., Storage accounts.</p>
<blockquote>
<p>As most resource names are case-insensitive, using <a target="_blank" href="https://en.wikipedia.org/wiki/Camel_case">Camel case</a> to save on delimiters like hyphens can lead to worse readability and more errors. Dots, underscores and spaces are not allowed for most of the resources. However, many second-level resources don’t have the same limitations as parent ones, and you can use more human-readable names for them.</p>
</blockquote>
<p>Some resources might need to use the same name in multiple resource groups, and those might not have the suffix, which is optional, appended at the end of the name. The same is true for the optional suffix.</p>
<p><strong>Optional name parts should be consistent across an entire subscription</strong>. For example, if you choose to omit a specific name part in a subscription name, you should do the same for the resource groups and resources in it.</p>
<blockquote>
<p>You might wonder why I don’t include the resource location in its name, as suggested by Microsoft. Nowadays, many Azure resources can be moved between regions, which wasn’t possible in the past. Unfortunately, renaming a resource to include a new region code in its name is impossible. I’ve seen many cases when having the resource location name part becomes completely dysfunctional and misleading. One possible exclusion from this is having multi-regional deployments when you use the location name/code as the identifier part in the resource name to distinguish between instances deployed in different regions, as resource movements rarely happen in that deployment model.</p>
</blockquote>
<p>Generally, your <strong>resource name should consist only of immutable properties</strong>. Any other property that can be changed, like location, SKU or service tier, should not be included in resource names. If such updatable properties are essential for your operation, it is best to <a target="_blank" href="https://andrewmatveychuk.com/practical-aspects-of-running-a-cmdb-for-azure-resources-fundamentals">use Azure tags for them</a>.</p>
<blockquote>
<p>If you are looking to <a target="_blank" href="https://andrewmatveychuk.com/how-to-enforce-naming-convention-for-azure-resources">enforce a naming convention</a> in your environment, you can check on how to do it with Azure Policy.</p>
</blockquote>
<h2 id="heading-naming-convention">Naming Convention</h2>
<p>To start with, your naming convention for Azure resources might look like the following:</p>
<h3 id="heading-subscriptions">Subscriptions</h3>
<p>Naming pattern:</p>
<p><code>&lt;organization&gt;-&lt;portfolio&gt;-sub[-&lt;environment&gt;]</code></p>
<p>Where:</p>
<ul>
<li><p><em>organization</em>: organization short name;</p>
</li>
<li><p><em>portfolio</em>: department, team or product line/portfolio;</p>
</li>
<li><p><em>environment</em>: (optional) environment name.</p>
</li>
</ul>
<p>Examples:</p>
<ul>
<li><p><code>contoso-digital-platform-sub-dev</code></p>
</li>
<li><p><code>contoso-digital-platform-sub-prd</code></p>
</li>
</ul>
<h3 id="heading-resource-groups">Resource Groups</h3>
<p>Naming pattern:</p>
<p><code>&lt;product name&gt;-rg[-&lt;environment&gt;][-&lt;identifier&gt;]</code></p>
<p>Where:</p>
<ul>
<li><p><em>product name</em>: product, solution, service or platform;</p>
</li>
<li><p><em>environment</em> (optional): environment name;</p>
</li>
<li><p><em>identifier</em> (optional): a unique identifier used to differentiate between multiple instances of the same application deployment to avoid <strong>naming collisions</strong> (more on this below). It should be scoped to the subscription containing your resource group.</p>
</li>
</ul>
<p>Examples:</p>
<ul>
<li><p><code>application-rg-dev</code></p>
</li>
<li><p><code>application-rg-prd</code></p>
</li>
</ul>
<h3 id="heading-resources">Resources</h3>
<p>Naming pattern:</p>
<p><code>&lt;product name&gt;-&lt;type of service abbreviated&gt; [-&lt;environment&gt;][-&lt;identifier&gt;]</code></p>
<p>Where:</p>
<ul>
<li><p>name parts follow the generic naming rule;</p>
</li>
<li><p>the unique <em>identifier</em> should be scoped to a resource group containing the resource.</p>
</li>
</ul>
<h2 id="heading-examples-of-naming-for-the-most-common-abbreviations-for-azure-services">Examples of naming for the most common abbreviations for Azure services</h2>
<div class="hn-table">
<table>
<thead>
<tr>
<td><strong>Resource type</strong></td><td><strong>Resource name abbreviation</strong></td><td><strong>Examples</strong></td></tr>
</thead>
<tbody>
<tr>
<td><strong>API Management services</strong></td><td>-apim-</td><td>application-apim-dev</td></tr>
<tr>
<td><strong>App Service Functions apps</strong></td><td>-func-</td><td>salesapp-func-qa</td></tr>
<tr>
<td><strong>App Service Plans</strong></td><td>-asp-</td><td>publicweb-asp-prd</td></tr>
<tr>
<td><strong>App Service Web apps</strong></td><td>-web-</td><td>publicweb-web-prd</td></tr>
<tr>
<td><strong>Application Gateways</strong></td><td>-agw-</td><td>publicweb-agw-prd</td></tr>
<tr>
<td><strong>Application Insights</strong></td><td>-ai-</td><td>publicweb-ai-qa</td></tr>
<tr>
<td><strong>Application Security Groups</strong></td><td>-asg-</td><td>connectapp-asg-qa</td></tr>
<tr>
<td><strong>Application Service Environments</strong></td><td>-ase-</td><td>landingpage-ase-prd</td></tr>
<tr>
<td><strong>Automation Accounts</strong></td><td>-aa-</td><td>monitoring-aa-prd</td></tr>
<tr>
<td><strong>Availability Sets</strong></td><td>-as-</td><td>partnertools-as-stg</td></tr>
<tr>
<td><strong>Cache for Redis</strong></td><td>-redis-</td><td>partnertools-redis-dev</td></tr>
<tr>
<td><strong>CDN profiles</strong></td><td>-cdn-</td><td>publicweb-cdn-prd</td></tr>
<tr>
<td><strong>Cognitive Services accounts</strong></td><td>-cogs-</td><td>salesapp-cogs-qa</td></tr>
<tr>
<td><strong>Container Registry</strong></td><td>*creg*</td><td>connectappcregcvhiyu5h2o5o</td></tr>
<tr>
<td><strong>Cosmos DBs</strong></td><td>-cos-</td><td>salesapp-cos-qa</td></tr>
<tr>
<td><strong>Event Hubs namespaces</strong></td><td>-eh-</td><td>connectapp-eh-qa</td></tr>
<tr>
<td><strong>Gateway connections</strong></td><td>-cn-</td><td>connectapp-cn-qa</td></tr>
<tr>
<td><strong>Key Vaults</strong></td><td>-kv-</td><td>publicweb-kv-prd</td></tr>
<tr>
<td><strong>Load Balancers</strong></td><td>-lb-</td><td>connectapp-lb-qa-frontend</td></tr>
<tr>
<td><strong>Log Analytics workspaces</strong></td><td>-la-</td><td>monitoring-la-prd</td></tr>
<tr>
<td><strong>Logic apps</strong></td><td>-lapp-</td><td>salesapp-lapp-qa</td></tr>
<tr>
<td><strong>Machine Learning workspaces</strong></td><td>-ml-</td><td>salesapp-ml-qa</td></tr>
<tr>
<td><strong>Network Interfaces</strong></td><td>-nic-</td><td>connectapp-nic-qa-01</td></tr>
<tr>
<td><strong>Network Security Groups</strong></td><td>-nsg-</td><td>connectapp-nsg-qa-backend</td></tr>
<tr>
<td><strong>Notification Hub namespaces</strong></td><td>-nh-</td><td>connectapp-nh-prd</td></tr>
<tr>
<td><strong>Public IPs</strong></td><td>-pip-</td><td>connectapp-pip-qa-02</td></tr>
<tr>
<td><strong>Resource Groups</strong></td><td>-rg-</td><td>application-rg-qa</td></tr>
<tr>
<td><strong>Route tables</strong></td><td>-rt-</td><td>connectapp-rt-qa-01</td></tr>
<tr>
<td><strong>Search services</strong></td><td>-srch-</td><td>bookingservice-srch-stg</td></tr>
<tr>
<td><strong>Service Buses</strong></td><td>-sbus-</td><td>bookingservice-sbus-stg</td></tr>
<tr>
<td><strong>SQL Server Managed Instances</strong></td><td>-sqlmi-</td><td>salesapp-sqlmi-qa</td></tr>
<tr>
<td><strong>SQL Databases</strong></td><td>-sql-</td><td>publicweb-sql-prd</td></tr>
<tr>
<td><strong>Storage accounts</strong></td><td>*st*</td><td>publicweb<strong>st</strong>tcvhiyu5h2o5o</td></tr>
<tr>
<td><strong>Traffic Manager profiles</strong></td><td>-tm-</td><td>applications-tm-prd</td></tr>
<tr>
<td><strong>Virtual Machines</strong></td><td>-vm-</td><td>connectapp-vm-qa-db</td></tr>
<tr>
<td><strong>Virtual Network Gateways</strong></td><td>-gw-</td><td>connectapp-gw-qa-01</td></tr>
<tr>
<td><strong>Virtual Networks</strong></td><td>-vnet-</td><td>connectapp-vnet-qa</td></tr>
</tbody>
</table>
</div><h2 id="heading-naming-collisions">Naming Collisions</h2>
<p>Some Azure resources must be named uniquely at the subscription scope or across all of Azure. It is common to encounter naming collisions for these resources.</p>
<p>One solution to get around naming collisions is to use a unique string when creating a resource. A unique string is typically a short hash of one or more concatenated input strings, so the output is sufficiently random to avoid naming collisions. Appending a unique string to a resource will help ensure the name satisfies any uniqueness constraints a resource requires. Creating a resource via an ARM/Bicep template is one way to create such <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-string#uniquestring">unique strings</a>.</p>
<p>Unique string scoped to a subscription:</p>
<p><code>"[uniqueString(subscription().subscriptionId)]"</code></p>
<p>Unique string scoped to a resource group:</p>
<p><code>"[uniqueString(resourceGroup().id)]"</code></p>
<p>A unique string that is globally unique and different between resource groups:</p>
<p><code>"[uniqueString(subscription().subscriptionId,resourceGroup().id)]"</code></p>
<p>When you need to create a new unique name each time you deploy a template and don’t intend to update the resource, you can use the <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-string#utcnow">utcNow</a> function along with <a target="_blank" href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-template-functions-string#uniquestring">uniqueString</a>, for example:</p>
<p><code>"[concat(uniqueString(resourceGroup().id), utcNow())]"</code></p>
<p>You can use this approach to create unique names when you employ <a target="_blank" href="https://www.hashicorp.com/resources/what-is-mutable-vs-immutable-infrastructure">immutable infrastructure</a>.</p>
<h2 id="heading-in-conclusion">In conclusion</h2>
<p>Please be aware that no single best approach exists for naming your Azure resources. The critical point is that you need to have a naming convention and follow it for resource maintainability and operation. Consistency in naming your resources is the key to speaking the same language between the members of different teams in your organization. So, please treat this guide as lessons from experience rather than the ultimate truth. If something doesn’t work for you, you must revisit it and adjust to your needs.</p>
<p>What naming patterns and anti-patterns have you encountered in your work with Azure? What parts of resource naming were the most debatable in your teams? Please share your experience in the comments section 👇</p>
]]></content:encoded></item><item><title><![CDATA[Azure Savings Plans vs. Azure Reservations]]></title><description><![CDATA[Last fall, Microsoft announced a new cost-saving option for Azure workloads called Savings Plans. Many customers who already were using Reservations (aka Azure Reserved Instances) to save on their cloud costs mistakenly perceived Savings Plans as a r...]]></description><link>https://andrewmatveychuk.com/azure-savings-plans-vs-azure-reservations</link><guid isPermaLink="true">https://andrewmatveychuk.com/azure-savings-plans-vs-azure-reservations</guid><category><![CDATA[Azure]]></category><category><![CDATA[finops]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Mon, 17 Jul 2023 11:00:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736870919330/2b79e3c3-e340-4d1d-8482-510304ab5b27.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Last fall, Microsoft announced a new cost-saving option for Azure workloads called <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/">Savings Plans</a>. Many customers who already were using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/">Reservations</a> (aka Azure Reserved Instances) to save on their cloud costs mistakenly perceived Savings Plans as a replacement for them. So, let’s try to demystify Savings Plans and whether you should migrate to them from Reservations.</p>
<h2 id="heading-key-differences-between-azure-reservations-and-savings-plans">Key differences between Azure Reservations and Savings Plans</h2>
<p>To make informed decisions when <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/decide-between-savings-plan-reservation">choosing among different cost-saving options in Azure</a>, we first need to understand their differences, summarized in the following table:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922662/145fcd64-7a6c-4c1c-b680-a1cbc79f6b3b.png" alt class="image--center mx-auto" /></p>
<p>The first difference to pay attention to is how those two options are applied. While a reservation is bound to one specific Azure region you chose when purchasing it, the saving plan will automatically target eligible resources in all regions in the assigned scope, which can be a shared scope, a management group, a subscription, or a resource group for both saving options. For example, your resource group contains two virtual machines deployed in two different regions. With Azure Reservations, you must purchase two of them bound to the respective regions and scope them to your resource group. Suppose you migrate any of those VMs to a region different from the original one. In that case, the corresponding reservation won’t be applied anymore, and you will be billed on a pay-as-you-go basis for them, plus still paying for the unused reservation(s). In the case of Savings Plans, the lower savings plan prices will still apply to those VMs regardless of the region.</p>
<p>Apart from that, while <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/reservation-discount-application">Reservations apply only to specific resource SKUs</a> you define during their purchase, Savings Plans will apply their ‘discount’ to all resource sizes (with some exceptions). For instance, if you resize your VM, you will likely lose the benefits from the reservation unless the new VM size is from the same SKU family and the reservation was configured for <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/reserved-vm-instance-size-flexibility">instance size flexibility</a>.</p>
<p>Secondly, <a target="_blank" href="https://azure.microsoft.com/en-us/pricing/offers/savings-plan-compute/#benefits-and-features">Azure Savings Plans target only a limited list of compute resources</a>, which are Azure Virtual Machines, Azure Container Instances, Azure Functions Premium Plans, Azure App Service (Premium v3 and Isolated v2 only), and Azure Dedicated Host at the time of writing this. That list might be extended in the future, so please refer to the official docs for up-to-date information. In contrast, Azure Reservations, in addition to compute resources, can also <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/save-compute-costs-reservations#charges-covered-by-reservation">target storage and a wide range of PaaS resources</a> such as databases, various data and analytics solutions, etc.</p>
<p>Thirdly, while <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/exchange-and-refund-azure-reservations">Reservations are partially refundable and exchangeable</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/cancel-savings-plan">Savings Plans are not</a>. That difference will disappear in the future, as Reservations purchased starting from Jan 1, 2024, will practically no longer allow exchanges.</p>
<p>Lastly, the level of potential savings. Please don’t be deceived by those numbers, as they are the maximum you can achieve with specific conditions like a certain VM size, region, commitment duration, etc. The actual numbers might vary significantly, so it’s always good to do your homework and understand your current savings and the potential ones <em>in your specific case</em>.</p>
<p>I intentionally omitted some similarities between those two options in my comparison to focus on the most critical factors that can affect your decision about going with Savings Plans, Reservations, or both. If you need full details, I encourage you to check the official documentation for them, as their usage policies might be updated, and new services might be added to the list of applicable savings:</p>
<ul>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/">Azure Reserved Instances</a></p>
</li>
<li><p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/savings-plan/">Azure Savings Plans</a></p>
</li>
</ul>
<p>Now, let’s look at how to use those options in practice.</p>
<h2 id="heading-practical-application-of-savings-plans">Practical application of Savings Plans</h2>
<p>Despite Microsoft doing a great job presenting that new saving option to its customers, I still observe some confusion when people start talking about using Savings Plans in practice. Most of it originates from the misunderstanding that Savings Plans intend to replace Reservations. In fact, Savings Plans should be perceived as a complementary cost optimization tool. Choosing the proper tool depends on your Azure environment and workload run patterns. Let’s review a few workload examples and see what the most appropriate cost-saving solution we can use for them. I will use compute workloads to compare apples to apples, as Savings Plans currently cover only them.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923838/03aa6f54-eba4-4fd9-ab46-3585d2753298.png" alt class="image--center mx-auto" /></p>
<p>In the first case, your environment consists mostly of IaaS components deployed in a few Azure regions. Your applications run on VMs that are mostly persistent resources with stable capacity demand and a relatively long lifetime (years). The movement of VMs between regions and their vertical scaling is rare, which is performed manually when a need arises. That is a typical picture of long-running, persistent infrastructure with no sudden changes in resource utilization. Azure Reservations would be the first consideration, as they can help you achieve greater savings when applied to such stable workloads.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924479/38eee0f2-dbfb-4d05-8cf5-bd78354dc1db.png" alt class="image--center mx-auto" /></p>
<p>In the second scenario, you are in the migration phase when your organization moves its application from VMs to more scalable PaaS and CaaS solutions. You still have many VMs in your hands, but at the same time, their list and configuration constantly change as you migrate to App Services, Functions, containers, etc. The landscape of those new resources is also changing and evolving as product teams learn, adapt and adjust to new business needs. Azure Savings Plans would be a preferable saving option here, as they apply to all those compute resources regardless of their type, size and location. The common denominator is the hourly commitment to consume compute resources for X dollars. Whether that money was spent on running a VM or App Service doesn’t matter. In any case, the usage less or equal to X will be billed at lower prices.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925209/103f1c50-9976-46d1-bb9c-faa787006846.png" alt class="image--center mx-auto" /></p>
<p>A combined scenario is more likely to be observed in large environments. You have different parts of your infrastructure at different levels of cloud adoption. Some pieces are old apps with specific technical and regulatory compliance requirements that run on VMs and shouldn’t be touched. Others are all bells and whistles of the most recent cloud services – autoscaling, serverless, microservices, etc. Plus, there are plenty of areas between those two edge cases. Here you are likely to weigh your cost-saving options and stack them to have the optimal solution. Reservations can cover stable compute resources. The dynamic ones with steady spending can benefit from Savings Plans. If the scope of a reservation and savings plan overlaps, the reservation discount will be applied first, and the savings plan reduced pricing second.</p>
<p>For example, achieving 100% utilization for Azure Reservations in a dynamic environment can be quite challenging – you need to <a target="_blank" href="https://andrewmatveychuk.com/how-to-monitor-azure-reservations-utilization">monitor their utilization</a>, analyze their resource coverage, rebalance existing underutilized reservations, and perform those activities regularly. Besides, it’s most likely that your savings from using Reservations at scale will differ from the ‘promised’ 72% maximum. If you analyze potential savings from using Savings Plans, you might find out that they can provide you with relatively the same level of savings in your environment, but you will need to spend far less time on administering them. Or, you can reduce the list of resources covered by Reservations to decrease the management overhead and use Savings Plans to cover the emerging difference.</p>
<p>Of course, those examples might look simplistic, and the real-life cost optimization strategy will likely account for more requirements. The important point is to understand your environment and always run the numbers before siding with specific cost optimization approaches. Just looking for higher potential savings without understanding the context may lead to an increase in FinOps management overhead, diminishing your returns.</p>
<p>Have you started using Azure Savings Plans in your environment? Were they beneficial, and how? Share your experience in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to find unused Azure resources]]></title><description><![CDATA[This is probably one of the top 10 questions I hear from customers running their workloads in Azure. The question’s intent might vary, but its ultimate goal is the same — to remove unused resources. Let’s explore why we keep facing that question and ...]]></description><link>https://andrewmatveychuk.com/how-to-find-unused-or-orphan-resources-in-azure</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-find-unused-or-orphan-resources-in-azure</guid><category><![CDATA[Azure]]></category><category><![CDATA[IT Operations]]></category><category><![CDATA[how-to]]></category><category><![CDATA[finops]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Fri, 23 Jun 2023 12:30:24 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736870938463/877e1195-8e84-47cd-8f6d-d3052f5ffac7.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This is probably one of the top 10 questions I hear from customers running their workloads in Azure. The question’s intent might vary, but its ultimate goal is the same — to remove unused resources. Let’s explore why we keep facing that question and why most people struggle to answer it.</p>
<h2 id="heading-why-find-unused-azure-resources">Why find unused Azure resources</h2>
<p>If no negative factors were associated with unused cloud resources, nobody would probably care about their existence and what to do with them. In other words, when you want to find such resources in your cloud environment, you usually pursue a specific goal. It can be:</p>
<ul>
<li><p>save on the associated resource costs (aka cost optimization),</p>
</li>
<li><p>reduce a possible attack surface on your infrastructure (aka security hardening),</p>
</li>
<li><p>reduce management overhead associated with supporting resources (aka operational excellence),</p>
</li>
<li><p>scale down overprovisioned resources (aka cost optimization),</p>
</li>
<li><p>remove ambiguity when deploying or updating your applications so no incorrect resources are used in the process (reduce errors) and others.</p>
</li>
</ul>
<p>Identifying your primary intent(s) for your search will help you to analyze the question from the proper perspective. Why I’m saying that? Because the answer to the question of unused resources depends on your understanding of it. Let me illustrate this with an example.</p>
<p>Have you ever considered why we get unused resources in the cloud? Large enterprises might run hundreds of applications and have thousands of infrastructure components. Infrastructure changes happen constantly: applications get updated, new services are deployed, some are decommissioned, internal processes change, reorgs happen, etc. Changes in one area might not be well-coordinated with others, causing gaps in the existing processes and creating misconfigurations. The number of those misconfigurations, which included unused resources, might be estimated in hundreds and thousands of individual cloud services. The time and resources you can spend processing so much data are usually limited, so focusing on the most important parts makes perfect sense. You should identify and focus on the primary cost drivers to optimize your cloud costs. If your focus is security, you likely want to address the most critical security issues caused by unused resources (an Azure VM with public IP and open management ports is a more significant threat to your environment than an empty resource group or unattached disk).</p>
<p>Now that you know what specific issues you want to address by finding ‘unused’ resources in your Azure environment, it’s time to do the assessment.</p>
<h2 id="heading-assessment-tools-to-identify-orphan-resources-in-azure">Assessment tools to identify orphan resources in Azure</h2>
<blockquote>
<p>Disclaimer. As I mentioned earlier, the exact definition of resource usedness depends on the issue you are trying to solve by looking for those resources. Most of the existing solutions for assessing your Azure environment for unused resources make their suggestions based primarily on the current resource configuration and look for the most obvious cases, like dangling resources that have no practical use in that state, e.g., unattached NSGs, or compute resource not associated with any workloads, e.g., App Service Plans running no apps. However, even those seemingly apparent cases of unused resources might be delusive. For example, an unattached disk might contain some data that is yet to be processed, an empty resource group might serve as a deployment boundary with preconfigured permissions for disposable deployments, etc. So, please remember to check with resource owners or responsible teams on such resources before planning them for removal.</p>
</blockquote>
<p>Now, let’s explore some starter tools that you can use to fund unused resources.</p>
<p><a target="_blank" href="https://github.com/dolevshor/azure-orphan-resources">The Azure Orphan Resources Workbook</a> is probably the best solution for quickly assessing your Azure subscriptions for unused resources. Under the hood, that workbook uses Azure Resource Graph queries to pull the data about resources in scope and can provide you with a solid starting point for further investigation. It’s a solid choice when you must analyze a new environment and identify the most problematic areas to focus on.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922731/35b06011-232d-4023-9be4-24cf89ea4412.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://github.com/Azure/CCOInsights">Continuous Cloud Optimization Insights</a> is a solution based on Power BI. It pulls data from different Azure services and combines it to give you more informative reports on different aspects of your cloud environments. Rather than focusing exclusively on tracking unused resources, it can help you to aggregate recommendations from different systems like Azure Advisor, Microsoft Defender for Cloud, Azure Monitor and so on, providing you with more meaningful insights into the actual resource usage.</p>
<p>Although <a target="_blank" href="https://learn.microsoft.com/en-us/azure/advisor/advisor-overview">Azure Advisor</a> might not be so powerful in generating recommendations about unused resources as more specialized tools, it can still give you a list of top cloud waste sources and recommendations on addressing them, especially regarding underused resource capacity and possible security risks. It’s free, part of the Azure portal, and doesn’t require any configuration. Its recommendations are structured into several categories that explicitly define your search end goal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923570/4c07cbe2-5481-4988-8699-0c48bdfe0fa5.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/resource-graph/">Azure Resource Graph</a> is a low-level tool for analyzing the configuration of Azure resources. Using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/">Kusto</a> (aka KQL) queries, you can quickly find unattached disks, NSGs not associated with any network or interface, etc. If the Azure Orphan Resources Workbook provides a good summary, the resource graph allows you to dig into details and tune your search.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924166/ee1a5a93-d88b-4fe0-94c2-806ef7fa915c.png" alt class="image--center mx-auto" /></p>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/overview">Azure Monitor</a> might not be an obvious choice for exploring unused resources, but it is the number one place to analyze your Azure resource utilization. There are many cases when Azure resource configuration tells you nothing about whether it is actually used or not. However, you can make some assumptions based on resource metrics. For example, you can check how many requests were served by an App Service, the resource utilization for a specific App Service plan, or how many reads and writes occurred from a Storage account over the last time. When you see low resource usage, you should add such resources to your analysis list to confirm their actuality and usefulness.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924770/59e7a581-dff6-4241-8fdc-90222b61b66c.png" alt class="image--center mx-auto" /></p>
<p>Still, using all those instruments to analyze your infrastructure for unused resources is a reactive action. What can we do to prevent or, better yet, minimize such a waste of cloud resources?</p>
<h2 id="heading-how-to-prevent-cloud-waste">How to prevent cloud waste</h2>
<p>To see how we can prevent the waste of cloud resources from happening, we first need to understand why it occurs.</p>
<p>Have you ever wondered why we store unused resources in the cloud? Unfortunately, there is no easy answer to that question.</p>
<p>Probably, the number one reason for cloud resource waste is the lack of lifecycle management for cloud resources, similar to <a target="_blank" href="https://en.wikipedia.org/wiki/Application_lifecycle_management">Application lifecycle management (ALM)</a>. It’s usually closely connected to absent or underperforming <a target="_blank" href="https://en.wikipedia.org/wiki/Configuration_management_database">CMDB</a> for cloud resources when you cannot identify resource owners, what applications or services they are related to, and if they are still in use. If you don’t have a CMDB for your Azure resources, I encourage you to check <a target="_blank" href="https://andrewmatveychuk.com/tag/cmdb/">my series on running CMDB for Azure</a> to understand why keeping your infrastructure neat and clean is essential.</p>
<p>In the second place, I would put the <a target="_blank" href="https://andrewmatveychuk.com/top-5-mistakes-in-it-monitoring/">lack of resource performance monitoring</a> and established resource <a target="_blank" href="https://en.wikipedia.org/wiki/Capacity_management">capacity management</a> processes. When you don’t monitor resource utilization and have no insights into how they are used, you cannot make any deliberate decisions about scaling your resources up or down to use them according to current demand. For example, your website might be in use and running just fine on a high-performant App Service Premium SKU. However, it utilizes just a tiny fraction of your plan capacity and can perform equally well on a far less powerful (and less expensive) App Service plan tier. The same applies to storage resources when your actual demand in throughput and data usage pattern can be satisfied using less performance storage tiers (e.g., HDD instead of SDD) and appropriate storage type (Hot, Cool, Archive, etc.). Although autoscaling and consumption-based (serverless) resources partially solve the issue of resource overprovisioning, those cloud-native patterns should be explicitly used when configuring your cloud resources.</p>
<p>The third and not-so-explicit reason for cloud waste is the lack of established security controls for cloud resources and apps running on them. Despite those two not being directly connected, there is often a clear correlation between how secure an environment is and how many wasted resources are in it. Let me explain.</p>
<p>Many security practices involve periodic assessments and automatic scanning for common security vulnerabilities, resource misconfiguration, possible unauthorized access, or data leaks. The more resources you have, the more actions you might be tasked to take to mitigate discovered security threats. Naturally, you would be eager to reduce the number of resources (and apps) in your responsibility to decrease the amount of work you need to perform during each security review. Besides, regular security assessments serve as a trigger to “delete those resources Bob/Dave/Sarah, etc., deployed to do some tests N-years ago.”</p>
<p>As you might already see from that non-inclusive list of reasons for cloud waste, completely preventing it might be quite problematic. Instead, I would suggest focusing on minimizing the negative impact of unused resources during both the design and operation phases of your cloud infrastructure management. When designing your cloud application, you should leverage cloud-native design patterns and prefer solutions that support autoscaling and consumption-based pricing. Whereas operating existing resources requires many infrastructure support processes to be present and executed efficiently. To name a few:</p>
<ul>
<li><p>Configuration Management (CMDB)</p>
</li>
<li><p>Monitoring (performance, events, infrastructure specific and application-tailored)</p>
</li>
<li><p>Capacity Management</p>
</li>
<li><p>Security/Vulnerability scans</p>
</li>
</ul>
<p>Also, other practices such as Continues Deployment, Infrastructure as Code, Disposable Environments, Immutable Infrastructure, Application Performance Management, etc., can help you minimize your cloud waste and bring the management of your cloud resources to a new level. Nevertheless, before deciding on what prevention measures to take, don’t forget to clarify your definition of resource usefulness and assess your environment for the largest gaps in effective resource utilization.</p>
<p>What is your experience in eliminating cloud waste and tracking unused cloud resources in Azure or another cloud? Please share your thoughts and suggestions in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to validate Azure tags]]></title><description><![CDATA[This blog post follows up on my series on running CMDB for Azure resources. In the previous parts, I shared some guiding principles for organizing your CMDB using Azure tags, so here, I will cover some practical examples of keeping your Azure tags ne...]]></description><link>https://andrewmatveychuk.com/how-to-validate-azure-tags</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-validate-azure-tags</guid><category><![CDATA[Azure]]></category><category><![CDATA[cmdb]]></category><category><![CDATA[configuration management]]></category><category><![CDATA[Azure Policy]]></category><category><![CDATA[ITIL]]></category><category><![CDATA[how-to]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Mon, 15 May 2023 12:00:33 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736870958076/089f23eb-0ee1-44dd-a059-7866ee8862c2.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>This blog post follows up on my series on <a target="_blank" href="https://andrewmatveychuk.com/tag/cmdb/">running CMDB for Azure resources</a>. In the previous parts, I shared some guiding principles for organizing your CMDB using Azure tags, so here, I will cover some practical examples of keeping your Azure tags neat and structured at scale.</p>
<h2 id="heading-the-issue-with-azure-tags-from-the-cmdb-perspective">The issue with Azure tags from the CMDB perspective</h2>
<p>Azure tag names and their values are just free text properties. You can choose whatever tag name and its value you want to apply to your resources, provided they are according to the <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-resources#limitations">Azure tag limitations</a>. On the one hand, it’s great because you have much flexibility in defining your tagging convention. On the other hand, your tag list can quickly become a mess as there are few to no default controls to validate them.</p>
<p>For example, you want to tag your resources with an internal application name and a reference to a resource owner. By default, nothing prevents you from tagging resources like the following:</p>
<ul>
<li><p>Resource 1 (tag name: tag value)</p>
<ul>
<li><p><code>App_name: MyApp1</code></p>
</li>
<li><p><code>owner: Jhon Doe</code></p>
</li>
</ul>
</li>
<li><p>Resource 2 (tag name: tag value)</p>
<ul>
<li><p><code>Application: MyApp1</code></p>
</li>
<li><p><code>_Owner: john.doe@contoso.com</code> (here underscore in the front stands for a space character)</p>
</li>
</ul>
</li>
</ul>
<p>As you can see from that example, those resources likely belong to the same application and to the same owner. However, tag names and value formats vary significantly. That might not be a problem if you manage a dozen resources manually, but it will become a huge automation challenge at the enterprise scale with thousands of resources, applications, and users involved when it’s impossible to logically organize your assets without technical means. So, let’s recap first what we can do to enforce specific tags that are applied to Azure resources.</p>
<h2 id="heading-enforce-tags-on-your-azure-resources">Enforce tags on your Azure resources</h2>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/tag-policies">Azure policy definitions for tag compliance</a> would definitely be a good place to start governing your tagging convention. Generally, those policies can be grouped by the control they implement. There are built-in policies to:</p>
<ul>
<li><p>require a specific tag (they will deny resource deployment without it),</p>
</li>
<li><p>inherit a tag from a parent resource container,</p>
</li>
<li><p>add/append a tag.</p>
</li>
</ul>
<p>From the enforcement perspective, the policies that require and/or inherit tags are the ones that can help you push your tagging requirements. The policies to add or append tags are usually helpful for remediation purposes when you want to apply specific tags to different scopes.</p>
<p>Those built-in policies lack examples for auditing your Azure resources for tag compliance, but it’s pretty easy to create custom ones using the require-tag policies as a starting point. You can find such policy samples in <a target="_blank" href="https://github.com/andrewmatveychuk/azure.policy/tree/master/other-samples/policies/definitions/tagging">my Azure Policy GitHub repository</a>.</p>
<p>When designing your tagging convention, you might come up with a list of mandatory tags and optional ones, where the mandatory tags are an absolute must for your inventory purposes, and resources cannot be deployed without them, whereas the optional tags are not strictly required but suggested for application teams to use according to their needs. It is also common for the optional tags to have some suggested naming and value format to follow to be consistent with your overall tagging structure. Therefore, for the mandatory tags, you would usually use require-tag policies, and for the optional tags – audit-tag ones (to keep track of their usage).</p>
<blockquote>
<p>As a rule of thumb, when assigning your Azure policies requiring specific tags (denying resource deployment without them), specify the corresponding custom non-compliance messages that clearly explain what went wrong and how to fix it. An error message like the following one can save you thousands of hours and empower users to correct their deployment without raising a support ticket:</p>
</blockquote>
<p><code>Azure resources must be tagged with the mandatory ‘&lt;tag_name&gt;’ in the following format: &lt;tag_name:tag_value&gt;. Please refer to KB#### (&lt;knowledgebase_URL&gt;) for additional details.</code></p>
<p>Having those controls in place can help you to keep your tag names consistent. Now, let’s see what we can do to validate tag values.</p>
<h2 id="heading-validate-tag-value-formatting">Validate tag value formatting</h2>
<p>Here, I will use some suggested tags from <a target="_blank" href="https://andrewmatveychuk.com/practical-aspects-of-running-a-cmdb-for-azure-resources-fundamentals">my previous blog post</a> to make my examples more practical.</p>
<blockquote>
<p>Make sure to check my list of <a target="_blank" href="https://andrewmatveychuk.com/azure-policy-best-practices">Azure Policy best practices</a> for crafting your custom policies like using parameters, testing, deploying, etc.</p>
</blockquote>
<p>For example, the Application Name tag is usually used to mark resources related to specific applications in the context of your organization. Depending on your requirements, its value might be just a free text or a <strong>text in a specific format</strong> according to your internal app/system encoding pattern:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="e53b43ce42a3673868b4b5149d1c92ee"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/e53b43ce42a3673868b4b5149d1c92ee" class="embed-card">https://gist.github.com/andrewmatveychuk/e53b43ce42a3673868b4b5149d1c92ee</a></div><p> </p>
<p>The Service Map and Documentation tags can be URL links pointing to specific resources containing a live service health map and internal documentation/wiki. To enforce the <strong>URL pattern</strong> for tag values, you can use the following policy rule condition:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="177625cfc131e4982335cd24a24ca925"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/177625cfc131e4982335cd24a24ca925" class="embed-card">https://gist.github.com/andrewmatveychuk/177625cfc131e4982335cd24a24ca925</a></div><p> </p>
<p>Similarly to the mentioned URL pattern validation, you can put a control to require the Owner and Technical Contact tags value to be in the form of an <strong>Email address</strong>:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7d8f587b20008d4432321caf8218f198"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/7d8f587b20008d4432321caf8218f198" class="embed-card">https://gist.github.com/andrewmatveychuk/7d8f587b20008d4432321caf8218f198</a></div><p> </p>
<p>Such tags as Business Unit, Data Profile, Service Class, and Criticality usually represent predefined <strong>lists of allowed tag options</strong>. For example, you can define the Data Profile tag to use only specific profile values:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="4f83b621a1882163b5bfcd53f3f9b465"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/4f83b621a1882163b5bfcd53f3f9b465" class="embed-card">https://gist.github.com/andrewmatveychuk/4f83b621a1882163b5bfcd53f3f9b465</a></div><p> </p>
<p>If you rate your Service Classes or application Criticality using <strong>numeric values</strong>, that validation approach will work, too. Just keep in mind that numeric values are treated as strings here:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="659666a1f2e6b11444c68a340a67ee58"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/659666a1f2e6b11444c68a340a67ee58" class="embed-card">https://gist.github.com/andrewmatveychuk/659666a1f2e6b11444c68a340a67ee58</a></div><p> </p>
<p>For <strong>date-related tags</strong> such as Created Date, you can ensure proper formatting using the following policy rule condition:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7bbcaa53d3c7fd60746e8759b2034367"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/7bbcaa53d3c7fd60746e8759b2034367" class="embed-card">https://gist.github.com/andrewmatveychuk/7bbcaa53d3c7fd60746e8759b2034367</a></div><p> </p>
<blockquote>
<p>Unfortunately, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure#conditions">Azure Policy conditional operators</a> don’t support regular expressions (aka regex). The closest expression validation you can do is by using the (not) like and match operators, and you might need to additionally validate tag values for more complex string patterns. More on that is in the next section.</p>
</blockquote>
<p>Please check <a target="_blank" href="https://github.com/andrewmatveychuk/azure.policy/tree/master/other-samples/policies/definitions/tagging/sample-tags">my Azure Policy GitHub repository</a> for the complete sample policy definitions.</p>
<p>Such Azure policies should help you keep your tags and values consistent across your resources. However, tag values are still just plain text properties with enforced formatting. So, what to do to ensure that a specific object your tag represents, like a user email address, exists in your systems of records?</p>
<h2 id="heading-automate-your-azure-tag-validation-processes">Automate your Azure tag validation processes</h2>
<p>As I mentioned earlier, tags like resource Owner, Technical Contact, or Budget Approver are usually specified as a user email address, which serves as a unique user identifier and provides an immediate contact point for communication with users using automation channels. However, enforcing the Email format validation with Azure Policy doesn’t prevent an editor from putting a mistyped or non-existing email address. Although Azure doesn’t offer any native capabilities for tag value validation, it has all the building blocks you can use to build your custom validation processes.</p>
<p>For example, you can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/event-grid/event-schema-subscriptions">subscribe to your subscription Activity logs with Event Grid</a> and then <a target="_blank" href="https://learn.microsoft.com/en-us/azure/architecture/reference-architectures/serverless/cloud-automation">use Azure Functions / Logic Apps to process those events</a> by looking for specific tags. The automation workflow can query your Azure AD to check if an entity with a specific email address exists and it’s active. Additionally, it can check if that email address belongs to a user or group account and implement some conditional logic according to your needs. If the email address is invalid, the workflow can send out a notification or log the event in a Log Analytics workspace with a corresponding Log alert rule configured to create alerts according to your unified monitoring process.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922497/92dc30b2-878b-467f-8158-8ea67b604319.png" alt /></p>
<p>As email addresses can become invalid due to changes in Azure AD, it also might be helpful to set up scheduled check-ups for Azure tags representing emails and run periodic scans for all such tags:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923368/42894618-632a-4f6a-b859-9754d4972891.png" alt /></p>
<p>Tags representing cost centers can rely on a fixed list of allowed values, but that list also might require periodic updates if the cost center structure in your organization changes. Such an update process can be implemented as a part of your deployment pipeline when you manage your Azure Policy via code:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923943/16b40b9f-cd33-446f-91b1-878b42b66bcf.png" alt /></p>
<p>Or, it can be independent of the policy deployment and implemented with an automation helper that pulls the list of valid cost center values from an external data source and updates the corresponding Azure Policy assignment with the new list of allowed tag options via a policy parameter.</p>
<p>After the policy is updated with the new allowed cost center list, some of your recourse might become non-compliant, so make sure you check and update invalidated tags with correct values. You can take it even further from there and <a target="_blank" href="https://blog.tyang.org/2021/12/06/monitoring-azure-policy-compliance-states-2021-edition/">configure Azure Monitor alerting for non-compliant resources</a>.</p>
<p>As with many IT systems, there is no single best implementation of those processes, and the final design heavily depends on your specific requirements and constraints. Nevertheless, I hope the described approaches to validating Azure tags can make your work easier and help you maintain your CMDB records consistent across the related systems.</p>
<p>How do you validate Azure tags in your environments? Please share your ideas and questions in the comments 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to audit Azure Hybrid Benefit usage with Azure Workbooks]]></title><description><![CDATA[UPDATED. It seems that Microsoft borrowed my idea of analyzing Azure Hybrid Benefit usage with Azure Monitor Workbooks and included the corresponding charts in the Cost Optimization workbook in Azure Advisor. Feel free to check it for AHUB numbers an...]]></description><link>https://andrewmatveychuk.com/how-to-audit-azure-hybrid-benefit-usage-with-azure-workbooks</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-audit-azure-hybrid-benefit-usage-with-azure-workbooks</guid><category><![CDATA[Azure]]></category><category><![CDATA[Azure Monitor]]></category><category><![CDATA[finops]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Thu, 05 Jan 2023 15:30:19 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736870974612/20bc419f-1660-4b89-b44d-050b4c1f313b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>UPDATED</strong>. It seems that <a target="_blank" href="https://azure.microsoft.com/en-us/updates/public-preview-assess-cost-optimization-opportunities-using-new-workbook-template-in-azure-advisor/">Microsoft borrowed my idea of analyzing Azure Hybrid Benefit usage with Azure Monitor Workbooks</a> and included the corresponding charts in <a target="_blank" href="https://learn.microsoft.com/en-us/azure/advisor/advisor-cost-optimization-workbook#azure-hybrid-benefit">the Cost Optimization workbook</a> in Azure Advisor. Feel free to check it for AHUB numbers and recommendations in your environment. It’s great to see that they listen to customer feedback!</p>
</blockquote>
<p>In one of my previous blog posts, I wrote about <a target="_blank" href="https://andrewmatveychuk.com/audit-and-enable-azure-hybrid-benefit-using-azure-policy">enabling Azure Hybrid Benefit at scale using Azure Policy</a>. Today, we will explore how you can keep track of actual license counts consumed by Azure services with that license benefit turned on. Moreover, we will try to analyze those resources for optimal benefit usage.</p>
<blockquote>
<p>If you want to skip the details and just get the Workbook for auditing Azure Hybrid Benefit usage, check for its template in <a target="_blank" href="https://github.com/andrewmatveychuk/azure.monitor/tree/master/workbooks">my GitHub repository</a>.</p>
</blockquote>
<h2 id="heading-what-are-azure-workbooks">What are Azure Workbooks?</h2>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-overview">Azure Workbooks</a> (also known as Azure Monitor Workbooks) is an excellent tool for visualizing data in the Azure Portal and sharing prebuilt reports with other users. They allow you to pull data from different data sources, extract some meaningful insights from that data, and present them to users in various formats. If you are familiar with <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-portal/azure-portal-dashboards">Azure Dashboards</a>, you can think of Azure Workbooks as a more advanced reporting solution.</p>
<p>There is enough documentation to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-getting-started">start working with Azure Workbooks</a>, so I will focus more on the practical case rather than explaining Workbooks’ functionality. If you are not familiar with them, in addition to the official documentation, I recommend checking the following videos produced by the people who created that great Azure service:</p>
<ul>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=Z5xRyy3HB8U">How to use Azure Monitor workbooks</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=EC7n1Oo6D-o">How to build Azure Workbooks using logs and parameters</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=ODHczGpGmEQ">How to build a graph and use links in Azure Workbooks</a></p>
</li>
<li><p><a target="_blank" href="https://www.youtube.com/watch?v=3XY3lYgrRvA">How to build tabs and alerts in Azure workbooks</a></p>
</li>
</ul>
<p>Working with most supported Azure Workbook data sources requires using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/">Kusto Query Language (KQL)</a> to query data and JSON to parse API responses, so some knowledge of those technologies will also be desirable.</p>
<blockquote>
<p>On a personal side, I must admit that I overlooked Azure Workbooks functionality for a long time because I could usually gather required data using PowerShell much faster. However, doing everything with a familiar tool only won’t help you explore other options, so you understand their pros and cons and can use them effectively. Therefore, I just decided to use this practical case as a learning opportunity for myself.</p>
</blockquote>
<h2 id="heading-what-data-are-we-going-to-use-and-how-can-we-bring-it-to-a-workbook">What data are we going to use, and how can we bring it to a Workbook?</h2>
<p>For the sake of simplicity, let’s focus on <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing">Azure Hybrid Benefit for Windows Server VMs</a> only. Technically, we need to get the list of all Azure VMs with Windows Server OS and Hybrid Benefit enabled. For that task, we can leverage the power of <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#azure-resource-graph">Azure Resource Graph</a>. A sample Graph query might look like this:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="019106dd6c6a1f5232dd8b2c939750eb"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/019106dd6c6a1f5232dd8b2c939750eb" class="embed-card">https://gist.github.com/andrewmatveychuk/019106dd6c6a1f5232dd8b2c939750eb</a></div><p> </p>
<p>That query looks up for all Azure resources where the resource type is Azure VM, the OS image publisher is Microsoft Windows Server and the corresponding VM property controlling the license benefit usage is equal to ‘<a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing#template">Windows_Server</a>’.</p>
<blockquote>
<p>Please note that <a target="_blank" href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/#what-is-a-query-statement">KQL is case-sensitive</a>, and I use <a target="_blank" href="https://learn.microsoft.com/en-us/azure/data-explorer/kusto/query/equals-operator">case-insensitive operators</a> as value naming is not always consistent across all Graph tables.</p>
</blockquote>
<p>As Azure Hybrid Benefit for Windows Server VMs entitles you to cover the software costs based on <a target="_blank" href="https://learn.microsoft.com/en-us/windows-server/get-started/azure-hybrid-benefit#getting-azure-hybrid-benefit-for-windows-vms-in-azure">the number of core licenses</a> you own, we will summarize query findings as the counts of discovered VM sizes:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922554/15cb47bb-554c-4ef3-a91b-6daa3961d90d.png" alt class="image--center mx-auto" /></p>
<p>Unfortunately, Azure VM size names don’t always correctly reflect the number of vCPUs according to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/vm-naming-conventions">the Azure VM sizes naming convention</a>. So, just parsing the VM size names for CPU counts won’t work. A suggested approach to get such data is to use <a target="_blank" href="https://learn.microsoft.com/en-us/rest/api/compute/resource-skus/list">the Resource SKUs API</a>. As that API is a part of Azure Resource Manager REST APIs, we can use an <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#azure-resource-manager">Azure Resource Manager query</a> in our workbook to pull the data from it:</p>
<p><code>/subscriptions/{Subscription:id}/providers/Microsoft.Compute/skus?api-version=2021-07-01&amp;$filter=location eq '{Location}'</code></p>
<p>Note that the API is subscription-scoped, and we provide the subscription ID via a <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-parameters">Workbook parameter</a>. We also scope the data to a particular Azure region, which is also a parameter, to limit the amount of data the query returns and avoid duplicates.</p>
<blockquote>
<p>Theoretically, if your Azure VMs are deployed across many different regions, you might miss data about VM sizes unavailable in the region scoped by the query. However, in practice, the VMs are usually deployed to a limited number of customer-selected Azure regions that support the same set of required VM SKUs. If that is not the case, you might need to pull the list of supported VM sizes for all regions and remove duplicate data from the results.</p>
</blockquote>
<p>As the mentioned API returns <a target="_blank" href="https://learn.microsoft.com/en-us/rest/api/compute/resource-skus/list?tabs=HTTP#lists-all-available-resource-skus-for-the-specified-region">a JSON payload containing SKU data for different resource types</a>, we can transform the result with <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-jsonpath">JSONPath</a> expressions:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923394/dab3c6a4-b23c-43bc-b291-a35f11bc906a.png" alt class="image--center mx-auto" /></p>
<p>Firstly, we filter items using Azure VM as the resource type. Secondly, we select only the VM size properties we want: a VM SKU name and the number of vCPUs. A sample query output might look like the following table:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924039/b8eee31d-6bdd-494e-a3e2-53ff663289a3.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-can-we-combine-data-from-different-data-sources">Can we combine data from different data sources?</h2>
<p>Now, we have two pieces of data – the counts of used Azure VM sizes with Azure Hybrid Benefit enabled and the number of vCPUs for each VM size. It would be great to combine that data by <a target="_blank" href="https://www.w3schools.com/sql/sql_join.asp">joining tables in SQL</a>. Fortunately, we can do that using <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-data-sources#merge">Azure Workbook Merge</a> functionality. It allows you to perform different types of joins, so here we will use the left outer join to combine all items from our Azure VMs grouped by size (right table) with the matching items from the Azure VM SKUs list (left table):</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924686/52d8b94d-6173-456b-8804-6ef5ed6c702a.png" alt class="image--center mx-auto" /></p>
<p>Also, we can select the columns we want to see in the resulting data set and remove duplicate ones. Additionally, the Merge data source allows you to extend your data set using calculated columns. For example, you can use expressions to calculate the total number of used vCPUs per VM size:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925293/b2a360f6-3d53-4f0f-9a6f-2b9cb559e5a3.png" alt class="image--center mx-auto" /></p>
<p>The resulting table might look like the following:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925918/6363ecd0-941c-49ec-a1c4-37b5cbef0b52.png" alt class="image--center mx-auto" /></p>
<p>However, those results are intermediate only, as <a target="_blank" href="https://learn.microsoft.com/en-us/windows-server/get-started/azure-hybrid-benefit#licensing-prerequisites">the number of CPU cores doesn’t directly translate to the number of core licenses you need</a>. So, how do we know how many core licenses are consumed by each VM size?</p>
<h2 id="heading-do-you-really-benefit-from-azure-hybrid-benefit">Do you really benefit from Azure Hybrid Benefit?</h2>
<p>As it turns out, regardless of the number of vCPUs, each <a target="_blank" href="https://learn.microsoft.com/en-us/windows-server/get-started/azure-hybrid-benefit#number-of-licenses">Windows Server Azure VM with less than 8 vCPUs always consumes 8 core licenses</a>. Servers with more than 8 vCPUs can stack more licenses on top of that minimum, but the license count should be rounded up to the nearest bigger integer divisible by 8. So, a 12-CPU VM will still consume 16 core licenses and so on.</p>
<p>To determine the actual number of required core licenses for our Windows Server VM fleet, we can use an additional calculated column with the following conditional logic:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681926503/776c157f-a485-4e5c-a242-05f1f5e48b85.png" alt class="image--center mx-auto" /></p>
<p>Those expressions round up the number of licenses to:</p>
<ul>
<li><p>8 if an Azure VM has less than 8 vCPUs,</p>
</li>
<li><p>the nearest bigger integer divisible by 8 in all other cases.</p>
</li>
</ul>
<p>So, now we can see precisely how many core licenses are needed for each Azure VM size group without doing any math manually.</p>
<p>On top of that, we can do another trick with calculated columns and <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-grid-visualizations#icons-to-represent-status">add icons</a> signaling if a specific VM size is using Azure Hybrid Benefit efficiently from the license point of view:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681927099/974c53ef-ea0a-47b1-92f4-38cde53ee817.png" alt class="image--center mx-auto" /></p>
<p>The condition here compares the licenses required to the number of vCPUs and produces a successful output if those numbers are equal. All other cases are considered suboptimal.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681927765/0c4de4ba-129e-496e-bd81-f8be34b8acd6.png" alt class="image--center mx-auto" /></p>
<p>That visualization can help us quickly understand which VM sizes in use don’t fully benefit from the license benefit. This can be especially helpful if you don’t have enough core licenses to cover all your Azure VMs, and it makes sense to redistribute them for maximum savings — enable Azure Hybrid Benefit only for VMs with 8 vCPUs and more.</p>
<h2 id="heading-can-we-make-our-solution-more-user-friendly-and-reusable">Can we make our solution more user-friendly and reusable?</h2>
<p>At this point, our resulting table contains all the details we might need to audit Azure Hybrid Benefit usage by Windows Server VMs in a subscription. However, what if we want top-level indicators based on that data to provide quick insights without digging into the records? For example, let’s add the total number of consumed core licenses and the total number of vCPUs to see how effectively we use licenses in general.</p>
<p>Instead of running additional queries in our workbook to fetch that data, we can <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-commonly-used-components#reuse-query-data-in-different-visualizations">reuse the existing dataset using the Merge data source</a>. It allows you to make a copy of another query results so that you can present them in a different format:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681928362/042df27e-a9a0-442f-86d0-dd31e30c2c85.png" alt class="image--center mx-auto" /></p>
<p>Then, we can remove unnecessary columns, choose a different visualization option, and select the category to focus on:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681929313/508ef739-c113-4450-b71b-88d223309599.png" alt class="image--center mx-auto" /></p>
<p>As the sample figures above show, using licenses in a lab environment is far from efficient.</p>
<p>Apart from those improvements, we can easily enable data export in Azure Workbooks, so other non-technical people can export the detailed results if they want to do some analysis with external tools:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681929989/058298d0-2b0c-423f-92fc-5eac602e23e2.png" alt class="image--center mx-auto" /></p>
<p>Azure Workbooks can be exported as <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-automate">Workbook templates or ARM templates</a>. Those templates can be imported into other environments(subscriptions) without any substantial preparation. This allows you to share them with other people as ready-to-consume solutions that don’t require technical expertise to use, making them a perfect case for solution adoption.</p>
<h2 id="heading-what-is-next">What is next?</h2>
<p>Finally, you can grab the ready-to-use Workbook for auditing Azure Hybrid Benefit usage from <a target="_blank" href="https://github.com/andrewmatveychuk/azure.monitor/tree/master/workbooks">my GitHub repository</a>, import it into your Azure subscription, and check the license numbers in your environment. Currently, it covers the cases for Azure Hybrid Benefit for <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing">Windows Server VMs</a>, <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/licensing-model-azure-hybrid-benefit-ahb-change">SQL Server VMs</a>, and Windows Client VMs (aka <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/windows-desktop-multitenant-hosting-deployment#subscription-licenses-that-qualify-for-multitenant-hosting-rights">Multitenant Hosting Rights</a>). Feel free to adjust it to your needs and add missing functionality for other Azure Hybrid Benefit use cases.</p>
<p>Got a question? Post it in the comments below and let me know if that guide was helpful for you 👇</p>
<p><strong>Update.</strong> The workbook was also updated to provide information about the license benefit usage by <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-sql/azure-hybrid-benefit">Azure SQL Database and SQL Managed Instance</a>.</p>
]]></content:encoded></item><item><title><![CDATA[Moving to Ghost(Pro)]]></title><description><![CDATA[A while ago, I wrote about how I run my blog with a self-hosted Ghost engine. Even though most processes were running on autopilot, they still required periodic attention and maintenance: the Ghost engine required updates, the blog theme needed to be...]]></description><link>https://andrewmatveychuk.com/moving-to-ghost-pro</link><guid isPermaLink="true">https://andrewmatveychuk.com/moving-to-ghost-pro</guid><category><![CDATA[Blogging]]></category><category><![CDATA[ghost]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Thu, 29 Dec 2022 18:00:44 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736870991892/41c64ebd-3395-4b1a-8657-c27cd93e20fc.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>A while ago, I wrote about <a target="_blank" href="https://andrewmatveychuk.com/how-i-run-my-blog">how I run my blog</a> with a self-hosted Ghost engine. Even though most processes were running on autopilot, they still required periodic attention and maintenance: the Ghost engine required updates, the blog theme needed to be fixed to be compatible with new engine versions, <a target="_blank" href="https://marketplace.digitalocean.com/apps/ghost">the DigitalOcean droplet hosting the blog</a> demanded patching, and so on. The most annoying part was probably rebooting and troubleshooting the server when the site went down, which started to happen every two to four weeks.</p>
<p>A lot has changed since then, and I decided that it would be the right time to eliminate the administrative overhead of managing the underlying infrastructure. The <a target="_blank" href="https://ghost.org/pricing/">pricing for Ghost(Pro)</a> became more attractive and comparable with other cheaper self-hosting options. Plus, I’m not very fond of spending time on something that is not of interest or a focus area for me.</p>
<p>Having said that, please expect the following changes:</p>
<ul>
<li><p>No <a target="_blank" href="https://developers.facebook.com/docs/plugins/comments">Facebook Comments</a> anymore. I regret choosing them over other solutions back then, as they happened to be very clunky, error-prone and practically unsupported by Facebook.<br />  I’m sticking to the native <a target="_blank" href="https://ghost.org/changelog/native-comments/">Ghost Comments</a> as of now.</p>
</li>
<li><p>I’m abandoning the email newsletters via <a target="_blank" href="https://andrewmatveychuk.com/how-to-fix-images-width-for-microsoft-outlook-in-mailchimp-rss-campaigns-sourced-from-ghost">RSS, Zapier and Mailchimp</a>. Although the related process worked very well, and the email campaigns worked just fine, I don’t see much value in keeping such a complex setup. As most of my blog visitors come from other sources, <a target="_blank" href="https://ghost.org/docs/newsletters/">the native email newsletters in Ghost</a> should be enough to update my subscribers on new blog posts.</p>
</li>
</ul>
<blockquote>
<p>Please expect the changes in the newsletter design as displayed in the post image.</p>
</blockquote>
<ul>
<li><a target="_blank" href="https://ghost.org/themes/wave/">The new clean theme</a> focuses more on the content and should improve overall readability and user experience on mobile devices.</li>
</ul>
<p>Under the hood, the domain was migrated from <a target="_blank" href="https://dnsimple.com/">DNSimple</a> to <a target="_blank" href="https://www.cloudflare.com/products/registrar/">Cloudflare</a>, and <a target="_blank" href="https://www.cloudflare.com/cdn/">the Cloudflare CDN</a> was turned off as <a target="_blank" href="https://ghost.org/help/cdn-provider-support/">Ghost(Pro) manages the CDN</a>. The hosting also handles the TLS encryption.</p>
<p>So, now I will spend less time “running” my blog and more time publishing new content 😊</p>
<p>Subscribe for new updates, and post your questions in the comments! 👇</p>
]]></content:encoded></item><item><title><![CDATA[Azure Policy Best Practices]]></title><description><![CDATA[Mastering a new tool might be challenging, so here I will share my best practices for working with Azure Policy. Those tips are based on my experience and intended to complement my Azure Policy Starter Guide. Although most of them will be about prett...]]></description><link>https://andrewmatveychuk.com/azure-policy-best-practices</link><guid isPermaLink="true">https://andrewmatveychuk.com/azure-policy-best-practices</guid><category><![CDATA[Azure Policy]]></category><category><![CDATA[Azure]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 13 Dec 2022 06:00:00 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736871007237/0f4de9cc-533b-4d07-9dc4-98f707807444.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>Mastering a new tool might be challenging, so here I will share my best practices for working with Azure Policy. Those tips are based on my experience and intended to complement my <a target="_blank" href="https://andrewmatveychuk.com/azure-policy-starter-guide">Azure Policy Starter Guide</a>. Although most of them will be about pretty simple things, to my mind, it’s usually the basics that people tend to overlook and suffer the consequences of their neglect. So, let’s start.</p>
<h2 id="heading-use-parameters">Use parameters</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922612/0f778bb2-ef06-4549-bad1-ca435bfdfd6c.png" alt class="image--center mx-auto" /></p>
<p>It might seem obvious that to make your code more reusable, you should use input parameters to control its behavior. Still, you can come across many examples, even with the built-in policies, when their authors didn’t bother with parameters and hardcoded some property values in the policy code. In such cases, what could have been accomplished with a simple update of policy assignment parameters now requires updating your Azure Policy definition, probably removing the current policy assignments and the definition if it’s incompatible with the new one, deploying the new policy definition into your target scope, and creating new assignments for it. Sounds like a ton of work, right? Now, imagine you need to update a dozen of those non-parametrized policies. On a second or third occasion, it’s not fun at all, and you probably start thinking about making your policy definition more adjustable to changing business requirements.</p>
<p>Let me illustrate how parametrizing a single policy value can instantly make your custom Azure Policy twice as valuable. If you have worked with Azure Policy before, you might know that they can have different <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effects">effects</a>. The <strong>Audit</strong> effect is the most used and usually the simplest to implement. You might find many custom policy definitions where it’s defined as follows:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="1770e42e4f80104934bc326e9a79aa56"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/1770e42e4f80104934bc326e9a79aa56" class="embed-card">https://gist.github.com/andrewmatveychuk/1770e42e4f80104934bc326e9a79aa56</a></div><p> </p>
<p>That’s what <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effects#audit-example">the official documentation</a> says.</p>
<p>However, if you go the extra mile and check a few Azure Policy definitions implementing the same effect, you might notice that the policy developers tend to define the policy effect as an input policy parameter like that:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="02fc8cedf53e98730b02da8e98853f7b"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/02fc8cedf53e98730b02da8e98853f7b" class="embed-card">https://gist.github.com/andrewmatveychuk/02fc8cedf53e98730b02da8e98853f7b</a></div><p> </p>
<p>Now, a simple switch flip can change your custom policy behavior from simply auditing for non-compliant resources to preventing them from deploying. It’s the same policy rule and the same logic, but twice as helpful thanks to that simple change. Now, when a business tells you they are tired of chasing Azure resource owners or fixing non-compliant configurations and want to block any such deployments from happening, you can implement such a control in the blink of an eye.</p>
<h2 id="heading-please-keep-it-simple">Please keep it simple</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923588/57e3809f-69be-40d0-ba75-1307159680b0.png" alt class="image--center mx-auto" /></p>
<p>Many <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/definition-structure#policy-functions">ARM template functions can be used in policy rules</a> to implement some advanced scenarios. You can reference resource properties, evaluate arrays, define conditional logic, work with strings, and do many other things. Sample Azure Policies for Guest Configuration or Kubernetes clusters can change your view of how powerful and complex(!) that tool can be. However, the complexity of your policy code doesn’t mean it’s a good thing to do all the time.</p>
<p>For instance, I occasionally read my own Azure Policy code from six or so months ago and have difficulty understanding how particular ‘fancy’ things work there. I often use my work notes and even read my old blog posts to recall why I liked them or how specific code works. Now, look at this from other people’s perspectives. Will it be easy for them to maintain and modify your custom policies in the future?</p>
<p>In my experience, 80% of using Azure Policy is all about simple stuff like auditing, modifying tags or creating guardrails for your cloud environments in Azure. So, why not keep its implementation simple, too? If it’s hard to do with Azure Policy, then it’s probably not the right tool for your task. Look at other Azure services, many of which have integrations with the policy APIs.</p>
<p>Another extreme, which is opposite to not using input parameters in Azure Policy, is defining parameters for everything and trying to make a Swiss knife out of your policy. Of course, you can specify the default values for those parameters so that you don’t have to provide them whenever creating a policy assignment. Still, it makes your policy definition harder to read and understand. For example, if your policy logic is about Azure VMs, there is no need to supply that resource type as an input parameter. Or, if you want to <a target="_blank" href="https://andrewmatveychuk.com/audit-and-enable-azure-hybrid-benefit-using-azure-policy">audit Azure Hybrid Benefit usage</a> across different eligible resource types, it might be better to define those controls in separate per-type policy definitions and combine their assignment with a policy initiative. That will make your solution cleaner and easier for other people to understand.</p>
<h2 id="heading-test-for-side-effects">Test for side effects</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924237/b2d3758c-9841-4d6b-b31d-cf8e1ddd30cd.png" alt class="image--center mx-auto" /></p>
<p>I might sound repetitive in my posts about Azure Policy, but please do test the policies first before assigning them to a production scope. It’s crucial in the case of the policies that modify, deploy or deny resources. Because most Azure Policy assignments happen at the subscription or management group levels, <a target="_blank" href="https://en.wikipedia.org/wiki/Blast_radius">the blast radius</a> of such changes is usually huge. Also, cloud adoption at organizations tends to evolve from prototyping with no or few rules first to catching up with cloud governance later. So, by the time you start putting your policies in place, hundreds or even thousands of existing resources might not comply with them.</p>
<p>Let’s take, for example, <a target="_blank" href="https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/General/AllowedLocations_Deny.json">the built-in policy that defines the list of allowed resource locations</a>. It’s commonly mentioned in cloud governance practices that suggest limiting the deployment of your Azure services to specific regions. There might be multiple reasons for that, but my point here is not about cloud governance. At first glance, it seems easy to assign that policy and be done with that control. However, if you have existing resources that don’t comply with that policy, you will effectively block them from further modification. That’s because, from the Azure Resource Manager API perspective, the same ‘write’ endpoints are used for creating and updating(!) existing resources. Now, we have a problem here.</p>
<p>In the case of community-crafted Azure Policies, you should be even more watchful. The fact that somebody already created a policy that seems to fit your needs ideally doesn’t mean that you should go and assign it straight ahead. It’s no different from copy-pasting a code from Stack Overflow without first understanding how it works. Many such policies might have errors in their logic or be not up to date with recent Azure API changes.</p>
<p>So, one more time — always test your policy in a lab environment first. Even if you’ve worked with it before and know it inside out, there are likely to be edge cases that you haven’t seen yet, as every new environment might be slightly different.</p>
<h2 id="heading-do-use-custom-non-compliance-messages">Do use custom non-compliance messages</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924944/b223484b-2831-4a40-b7fc-e3f5a7b0a67f.png" alt class="image--center mx-auto" /></p>
<p>In the past, if some Azure Policy blocked an Azure resource deployment, you would get a generic error message and reference to the blocking policy at best. To say that it was frustrating is to say nothing. You need to look into deployment and activity logs to determine what that error is about and what you must fix to pass the policy checks. Developers doing their deployments from a console or an automated pipeline are usually required to go to engineers managing cloud infrastructure to troubleshoot such issues, as the console output was even less verbose.</p>
<p>That user experience improved significantly with the introduction of <a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/assignment-structure#non-compliance-messages">custom non-compliant messages for policy assignments</a> and more verbose console output. Now, instead of a generic message that some policy blocked a resource deployment, you can provide a user with more helpful information like instructions on what exactly was wrong and how to fix that or a link to a knowledge base article with a detailed policy description and troubleshooting steps.</p>
<p>The functionality of custom non-compliance messages proved to be extremely helpful, especially for Azure Policies with the <strong>Deny</strong> effect. Please don’t skip out on it. Moreover, I suggest putting double effort into crafting your custom messages so they provide other users with clear instructions on how to pass the policy validation. Another person coming to you with a question (or a support request) about a policy-blocked deployment should encourage you to revisit the corresponding non-compliance message until it has enough details for self-service.</p>
<h2 id="heading-set-up-a-cicd-pipelines-for-azure-policy">Set up a CI/CD pipeline(s) for Azure Policy</h2>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925575/cc66c991-dea8-4cc9-9141-5e37eacacbce.png" alt class="image--center mx-auto" /></p>
<p>Although Azure Policy doesn’t introduce a separate programming language in a broad sense, they are still defined in code. I’ve already authored a couple of articles on that, so feel free to check them:</p>
<ul>
<li><p><a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-with-bicep">How to deploy Azure Policy with Bicep</a></p>
</li>
<li><p><a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policies-with-arm-templates">How to deploy Azure Policies with ARM templates</a></p>
</li>
<li><p><a target="_blank" href="https://andrewmatveychuk.com/using-arm-templates-to-deploy-azure-policy-initiatives">Using ARM templates to deploy Azure Policy initiatives</a></p>
</li>
</ul>
<p>As with any code, all DevOps-proven techniques, such as code versioning, automated testing, continuous integration, and continuous deployment, also apply to developing and maintaining your custom policy definitions and assignments. All the tooling for that is here and free to use. For instance, you can check my blog post on how to automate your policy deployment:</p>
<ul>
<li><a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-from-an-azure-devops-pipeline">How to deploy Azure Policy from an Azure DevOps pipeline</a></li>
</ul>
<p>Investing your time in setting up your automated deployment process for Azure Policy will drastically improve your development speed and increase your confidence in its quality. Even if you have no intention to develop your custom policies and plan to use the built-in ones only, it’s still reasonable to embed the creation of policy assignments in your CI/CD pipeline and use staging environments, quality checks, and gated approvals before deploying in production.</p>
<p>Of course, those tips are just a tiny fraction of the best practices to follow when working with Azure Policy, and I plan to update that list with other relevant highlights from my experience. If you think that I missed something important here, please feel free to comment and add your suggestion in the comment box below 👇</p>
]]></content:encoded></item><item><title><![CDATA[Audit and Enable Azure Hybrid Benefit using Azure Policy]]></title><description><![CDATA[What is Azure Hybrid Benefit?
From the cost perspective, the resulting price for an Azure resource is calculated from multiple parts, software licenses being one of them. For example, when using the Azure Pricing Calculator and estimating your Azure ...]]></description><link>https://andrewmatveychuk.com/audit-and-enable-azure-hybrid-benefit-using-azure-policy</link><guid isPermaLink="true">https://andrewmatveychuk.com/audit-and-enable-azure-hybrid-benefit-using-azure-policy</guid><category><![CDATA[Azure]]></category><category><![CDATA[Azure Policy]]></category><category><![CDATA[Azure Hybrid Benefit]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Tue, 29 Nov 2022 13:48:26 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736871029044/ca5e63db-1e21-43f6-a4c2-666ea8b84f41.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h2 id="heading-what-is-azure-hybrid-benefit">What is Azure Hybrid Benefit?</h2>
<p>From the cost perspective, the resulting price for an Azure resource is calculated from multiple parts, software licenses being one of them. For example, when using the <a target="_blank" href="https://azure.microsoft.com/en-us/pricing/calculator/">Azure Pricing Calculator</a> and estimating your Azure VM cost, you might notice that the total row changes depending on the operating system and VM configuration type (OS only, SQL Server, etc.). For some of that software, you can apply <a target="_blank" href="https://azure.microsoft.com/en-us/pricing/hybrid-benefit/">Azure Hybrid Benefit</a>, which basically allows you to bring your own license (BYOL, for short) and save on your cloud infrastructure spend in Azure.</p>
<blockquote>
<p>Speaking of Microsoft software, it’s a great cost optimization case for enterprise customers that usually already have lots of Windows and SQL Server licenses as part of their Enterprise agreements. That is especially true for customers with a significant portion of their existing infrastructure running outside of Azure, presumably in their on-premises data centers, and migrating their workloads to Azure.</p>
</blockquote>
<p>Different Microsoft products have different conditions and applicability logic for Azure Hybrid Benefit, and I will cover those details in the following sections.</p>
<h2 id="heading-what-is-azure-policy">What is Azure Policy?</h2>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/governance/policy/overview">Azure Policy</a> is an Azure service that can be used to “implement governance for resource consistency, regulatory compliance, security, cost, and management.” In other words, it’s a framework that allows you to define rules for resource configuration, audit resource compliance with those rules, and enforce the rules by preventing the deployment of non-compliant resources or modifying existing resources so they become compliant with them.</p>
<p>That service is well-documented, so I’m not going into the details about how it works here. If you need a quick overview, you can check my <a target="_blank" href="https://andrewmatveychuk.com/azure-policy-starter-guide">Azure Policy Starter Guide</a> and <a target="_blank" href="https://andrewmatveychuk.com/tag/azure-policy">Azure Policy-related content</a>.</p>
<p>The question worth asking here is whether Azure Policy is a good tool for our purpose, which is auditing and enabling Azure Hybrid Benefit. Firstly, as you can see from the service definition, it is specifically designed for such tasks. Secondly, it implements a declarative approach for configuration description. Lastly, Azure Policy provides all necessary tools for reporting, decoupling policy definitions and assignments, and enforcing resource configuration. So, let’s consider some practical examples.</p>
<h2 id="heading-azure-hybrid-benefit-for-windows-server-vms">Azure Hybrid Benefit for Windows Server VMs</h2>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/hybrid-use-benefit-licensing">Azure Hybrid Benefit applicability for Windows VMs</a> is controlled by the <strong>licenseType</strong> property of an Azure VM. Basically, you can switch it on or off by updating the license type to ‘<em>Windows_Server</em>’ or ‘<em>None</em>’. You don’t even need to restart the VM for that, as it is a feature of Azure Compute service and not an operating system.</p>
<p>The Azure Policy rule that defines the applicability logic of that benefit to your Azure VMs may vary depending on how you define your eligible VM instances. Generally, you can use specific OS images as a filtering condition, as Windows Server licenses are usually related to specific OS versions. That way, you can define the list of the benefit-eligible OS images as an input parameter and update it dynamically as your license term changes over time. Sample policy rule to enumerate eligible Windows Server Azure VMs in a policy assignment scope might look as follows:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="90fb0568ca12fed0f9b8c0074ab98d6d"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/90fb0568ca12fed0f9b8c0074ab98d6d" class="embed-card">https://gist.github.com/andrewmatveychuk/90fb0568ca12fed0f9b8c0074ab98d6d</a></div><p> </p>
<p>You might notice that the policy effect is also defined as a parameter here. I highly suggest using such an approach so you can change your policy assignment effect without editing the policy definition and go from auditing to preventing (denying) the deployment of eligible Azure VMs without Azure Hybrid Benefit enabled in a target scope.</p>
<p>Similarly, you can craft an Azure Policy to enroll the eligible Azure VMs into using the licensing benefit:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="80e2b2406bf963fad996f6c1704eb7d4"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/80e2b2406bf963fad996f6c1704eb7d4" class="embed-card">https://gist.github.com/andrewmatveychuk/80e2b2406bf963fad996f6c1704eb7d4</a></div><p> </p>
<p>You can find the complete policy definitions in <a target="_blank" href="https://github.com/andrewmatveychuk/azure.policy/tree/master/other-samples/policies/definitions/azure-hybrid-benefit">my Azure Policy GitHub repository</a>. As I prefer using Bicep to write my Azure deployment templates, you might want to check my other post on <a target="_blank" href="https://andrewmatveychuk.com/how-to-deploy-azure-policy-with-bicep">how to deploy Azure Policy with Bicep</a>.</p>
<h2 id="heading-azure-hybrid-benefit-for-windows-client-vms">Azure Hybrid Benefit for Windows Client VMs</h2>
<p>The same Azure VM property controls Azure Hybrid Benefit for Windows Client VMs, but it’s called <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/windows-desktop-multitenant-hosting-deployment#subscription-licenses-that-qualify-for-multitenant-hosting-rights">Multitenant Hosting Rights</a>, allowing you to use your existing Windows 10/11 licenses in Virtual Desktop scenarios. To apply the benefit for Azure VMs running the desktop OS, the license type should be set to ‘<em>Windows_Client</em>.’</p>
<p>From the Azure Policy perspective, you can use the same filtering approach as with Windows Server Azure VMs but target Windows Client OS images specifically:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7e4e102b84b30f416cdb886d4fa6cac2"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/7e4e102b84b30f416cdb886d4fa6cac2" class="embed-card">https://gist.github.com/andrewmatveychuk/7e4e102b84b30f416cdb886d4fa6cac2</a></div><p> </p>
<p>Just pay attention to a different image publisher and another license type here.</p>
<p>With virtual desktop scenarios like Azure Virtual Desktop, especially in the personal host pools scenario, it’s even more important to automate the configuration of provisioned hosts with enabled Multitenant Hosting Rights to reduce your AVD costs if they are eligible to use that benefit, which is a typical case for customers having <a target="_blank" href="https://learn.microsoft.com/en-us/azure/virtual-machines/windows/windows-desktop-multitenant-hosting-deployment#license-entitlement">Microsoft 365 E3, E5 and other entitlement licenses</a>. You can achieve this on the fly when a new personal AVD host is provisioned using the Modify effect of Azure Policy.</p>
<p><a target="_blank" href="https://github.com/andrewmatveychuk/azure.policy/tree/master/other-samples/policies/definitions/azure-hybrid-benefit">My GitHub repository</a> contains the complete policy definitions to audit and enable Azure Hybrid Benefit for Windows Client VMs.</p>
<h2 id="heading-azure-hybrid-benefit-for-sql-server-vms">Azure Hybrid Benefit for SQL Server VMs</h2>
<p><a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/licensing-model-azure-hybrid-benefit-ahb-change">SQL virtual machines in Azure can utilize your SQL Server licensed cores</a> to achieve a similar licensing benefit.</p>
<blockquote>
<p>Note that Azure Hybrid Benefit applies to Standard and Enterprise SQL Server editions only.</p>
</blockquote>
<p>From the technical point of view, your SQL Server VMs must be appropriately registered with <a target="_blank" href="https://learn.microsoft.com/en-us/azure/azure-sql/virtual-machines/windows/sql-server-iaas-agent-extension-automate-management?view=azuresql">the SQL IaaS Agent Extension</a>. Also, the applicability of the benefit is controlled by the <strong>sqlServerLicenseType</strong> property of the SQL Server VM resource. It should be set to:</p>
<ul>
<li><p>AHUB for the Azure Hybrid Benefit</p>
</li>
<li><p>PAYG for pay-as-you-go</p>
</li>
<li><p>DR to activate the free HA/DR replica</p>
</li>
</ul>
<p>A sample Azure Policy rule to evaluate SQL Server VMs for Azure Hybrid Benefit usage can be as follows:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="7b0b3f3b922b1724985371d6330a0d5d"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/7b0b3f3b922b1724985371d6330a0d5d" class="embed-card">https://gist.github.com/andrewmatveychuk/7b0b3f3b922b1724985371d6330a0d5d</a></div><p> </p>
<blockquote>
<p>Interestingly, you can apply Azure Hybrid Benefit for an underlying Windows Server VM and SQL Server instance running on it, provided that you have all corresponding licenses, archiving even more substantial savings. So, consider that when estimating potential Windows/SQL hosting costs in Azure.</p>
</blockquote>
<p>Suppose you want to logically combine your configuration rules (policies) for both Windows Server and SQL Server licensing benefits to apply them at once. In that case, I suggest using <a target="_blank" href="https://andrewmatveychuk.com/using-arm-templates-to-deploy-azure-policy-initiatives">Policy Initiatives</a>, aka Policy Definition Sets. Such a solution will be more flexible and modular to manage.</p>
<p>For a complete policy definition, check <a target="_blank" href="https://github.com/andrewmatveychuk/azure.policy/tree/master/other-samples/policies/definitions/azure-hybrid-benefit">my GitHub repository</a>.</p>
<h2 id="heading-in-conclusion">In conclusion</h2>
<p>Technically speaking, similar configurations can be enforced using the corresponding parameters in Bicep definitions for your resources or running an Azure PowerShell one-liner to modify resource properties. However, the primary disadvantage of those approaches is that they require additional effort to prevent configuration drift and ensure compliance at scale. Having all your infrastructure defined in code (IaC) is good, but being able to test and validate it for compliance is even better. In the case of Azure PowerShell (or Azure CLI), manually running a script is not enough to ensure resource compliance in an ever-changing environment with thousands of resources. You need to schedule regular jobs to validate resource configuration, report on non-compliant resources, and develop a mitigation strategy.</p>
<p>Similarly, Azure Policy is not a silver bullet, as it also has limitations. For example, to comply with your license agreements, you should keep track of the number of applied Azure Hybrid benefits and how they convert to the number of licensed OS and SQL Server cores. Implementing such logic with Azure Policy seems overcomplicated as it wasn’t intended for such tasks. In one of the upcoming posts, I plan to cover how you can achieve that using Azure Monitor Workbooks. If you don’t want to miss that update, please subscribe to my new posts using the form below 👇</p>
]]></content:encoded></item><item><title><![CDATA[How to monitor Azure Reservations utilization]]></title><description><![CDATA[UPDATE. On May 23, 2023, Microsoft announced a built-in functionality for alerting about underused Azure Reservations. If you don't need your reporting in a specific format and are okay with a standard notification template from Azure, it's totally f...]]></description><link>https://andrewmatveychuk.com/how-to-monitor-azure-reservations-utilization</link><guid isPermaLink="true">https://andrewmatveychuk.com/how-to-monitor-azure-reservations-utilization</guid><category><![CDATA[Azure]]></category><category><![CDATA[finops]]></category><dc:creator><![CDATA[Andrew Matveychuk]]></dc:creator><pubDate>Mon, 03 Oct 2022 13:56:20 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1736871047833/41c67984-c323-4031-a5a5-2f7c3f2fcd01.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<blockquote>
<p><strong>UPDATE</strong>. On May 23, 2023, <a target="_blank" href="https://azure.microsoft.com/en-us/updates/rualerts-2/">Microsoft announced a built-in functionality for alerting about underused Azure Reservations</a>. If you don't need your reporting in a specific format and are okay with a standard notification template from Azure, it's totally fine to use that option in your FinOps processes.</p>
</blockquote>
<p>Using Azure Reservations is <a target="_blank" href="https://andrewmatveychuk.com/practical-use-cases-of-cost-optimization-in-azure">one of many techniques</a> that can optimize your cloud spending in Azure. They are a great option to reduce costs for Azure resources with a long lifespan and predictable utilization. Have a VM, storage, or database that will run ‘permanently’? Buy a reservation for it, set it up to auto-renew upon expiration, and enjoy savings of up to 70% or so. Does it sound too good to be true? Let’s find out.</p>
<p>For small setups with few cloud resources or stable infrastructure with little changes, the easiness of using the reservations is mostly true. However, for large enterprise-scale environments, the reality is a bit different. The larger the environment, the more changes in it you inevitably have. Apart from that, the flexible nature of cloud services, which can be provisioned and decommissioned in a matter of minutes and not weeks or months, provides even more opportunities for engineers to modify their solutions and change their infrastructure requirements. Something intended to run for the next few years can be gone in a few months or replaced with other infrastructure components.</p>
<p>All those changes in a cloud infrastructure create the challenge of managing Azure Reservations efficiently. As the reservations are implicit commitments to pay for a fixed amount of cloud resources over one or three years, it’s essential to utilize those resources fully. Otherwise, you will be paying for resources you actually don’t consume, and all your savings from using those reservations will be diminished.</p>
<p>Luckily for us, Microsoft <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/exchange-and-refund-azure-reservations">allows exchanging existing reservations for new ones</a>. Of course, there are some limitations, but it’s still more optimal to return unused reservation units and buy new reservations for services that you do use. The obvious first question here is how you know what your reservations are underutilized and to what extent.</p>
<h2 id="heading-azure-reservations-utilization-info">Azure Reservations utilization info</h2>
<p>Microsoft provides <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/reservations/reservation-utilization">various options to check reservation utilization after its purchase</a>. You can check it on the Reservation blade of the Azure Portal, use the corresponding section in the Cost Management + Billing interface, or run some ready-to-use Power BI reports. Also, you can use <a target="_blank" href="https://learn.microsoft.com/en-us/powershell/module/az.reservations/get-azreservation">Azure PowerShell</a> and <a target="_blank" href="https://learn.microsoft.com/en-us/cli/azure/reservations/reservation?view=azure-cli-latest#az-reservations-reservation-list">Azure CLI</a> to get some reservation utilization details. Apart from that, you can utilize <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/costs/cost-analysis-built-in-views#review-reservation-resource-utilization">the Reservations insight preview feature of the Azure Cost Analysis</a>.</p>
<p>However, all those options have a common disadvantage — none of them has built-in functionality to automatically notify or alert when utilization for Azure Reservations drops below a certain value. Even though the platform collects data about reservation utilization, and you can check it in the reservation details on the portal, it’s not available as a metric in Azure Monitor.</p>
<p>One of the options to get the reservation utilization info programmatically is to use <a target="_blank" href="https://learn.microsoft.com/en-us/rest/api/billing/enterprise/billing-enterprise-api-reserved-instance-usage">Microsoft Cost Management APIs</a>. On the one hand, with such an API, you are not limited to vendor-provided monitoring options only, and you can use any custom or third-party monitoring solution that can query a REST API, parse a JSON payload, and extract the average utilization percentage value from it to be compared with some threshold.  On the other hand, something that could be a simple Azure Monitor metric alert requires a whole monitoring infrastructure for that and overcomplicates the monitoring setup.</p>
<h2 id="heading-monitoring-azure-reservations-utilization-with-power-automate">Monitoring Azure Reservations utilization with Power Automate</h2>
<p>As a decrease in Azure Reservations utilization is not something that you usually need to act upon immediately, it might make more sense to build an automated reporting that would provide you with insights about underused reservations on a regular basis related to your FinOps processes. So, by the time you are reviewing your monthly cloud invoices and analyzing your cloud spend, you have the data about what Azure Reservations require assessment and possible exchange or cancellation.</p>
<p>To build such a solution, I decided to try using Power Automate cloud flow. It’s user-friendly and usually available for most Microsoft customers as <a target="_blank" href="https://learn.microsoft.com/en-us/power-platform/admin/power-automate-licensing/types#seeded-plans">part of their Microsoft 365 plans</a> or as Logic apps in their Azure subscriptions.</p>
<blockquote>
<p>Alternatively, you can implement a similar logic using Automation Runbooks or Azure Functions. My choice of a Power Automate flow (aka Logic app) here is merely a matter of quickly prototyping a solution that can be maintained and modified by people with no specific programming knowledge.</p>
</blockquote>
<p>For a start, we can use the mentioned Cost Management APIs to <a target="_blank" href="https://learn.microsoft.com/en-us/rest/api/billing/enterprise/billing-enterprise-api-reserved-instance-usage#request-for-reserved-instance-usage-summary">get the list of all reservations with their usage summary</a>, and the HTTP action can help us with that:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681922757/44bbbdec-2cee-447b-bc1d-8e8687c0a3d4.png" alt class="image--center mx-auto" /></p>
<p>Be sure to use the correct enrolment number in the URI, which you can check on the Azure Portal or <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/manage/ea-portal-get-started">the Enterprise Portal</a> (for EA customers only). Also, the API authentication might be tricky here, as you need to <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/manage/ea-portal-rest-apis#generate-or-retrieve-the-api-key">generate/get the API Access Key on the Enterprise portal</a> first.</p>
<p>Then, you should <a target="_blank" href="https://learn.microsoft.com/en-us/azure/cost-management-billing/manage/enterprise-api#enabling-data-access-to-the-api">provide that key in the specific format in the request Authorization header</a>.</p>
<p>The successful API call will result in a JSON array, provided that you have some Reserved Instance purchases within the target enrollment.</p>
<blockquote>
<p>Depending on your needs, you can switch to the daily grain when querying the API. However, from the practical point of view, it’s rather suitable for an in-depth analysis than for regular monitoring/reporting.</p>
</blockquote>
<p>To parse that JSON output into an array we can manipulate, we can use the Parse JSON action, which is <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/data-operations">a common data operation in Power Automate</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681923680/b51f1a33-2ee5-4c7a-b946-43ac5a0bb579.png" alt class="image--center mx-auto" /></p>
<p>The parsing requires a schema for understanding the structure of a JSON payload. You can generate it by yourself from your sample Reserved Instance usage details payload or use the following schema, which I already prepared:</p>
<div class="gist-block embed-wrapper" data-gist-show-loading="false" data-id="88ec45ab135e28dadd76fc89d5a95574"><div class="embed-loading"><div class="loadingRow"></div><div class="loadingRow"></div></div><a href="https://gist.github.com/andrewmatveychuk/88ec45ab135e28dadd76fc89d5a95574" class="embed-card">https://gist.github.com/andrewmatveychuk/88ec45ab135e28dadd76fc89d5a95574</a></div><p> </p>
<p>The resulting array will contain the details for all your Reserved Instance purchases regardless of their utilization levels. Putting all that data into your utilization report might be unnecessary, as we are primarily interested in reservations that are not fully utilized — in other words, reservations with less than 100% utilization. However, as I explain next, it is not always necessary to achieve full utilization to benefit from the savings Azure Reservations provides.</p>
<p>Bad or good, there is no one universal threshold for reservation utilization that can be considered a best practice. That’s because, depending on the reservation terms, the savings from its usage may vary greatly. For example, in one case, the savings from using Azure Reserved VM Instances purchased for one year for a specific Azure VM SKU or instance flexibility size can be approximately 40%. In another case, purchasing reserved instances for a three-year term for the same Azure VM size(s) can result in more than 60% savings if utilized to the full extent.</p>
<p>Technically, if the utilization of your reservation with 60% projected savings drops by 50%, you are still saving 10% using it compared to on-demand costs. In contrast, if the utilization of the 40% reservation drops by the same amount, you are in the red.</p>
<p>Of course, that doesn’t mean you should target such low savings by applying Azure Reservations. For analysis purposes, you can rely on the guidelines provided by Microsoft on the Reservation blade of the Azure Portal:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681924279/8adad8fa-2f9e-4375-8e7d-e3ea806e88e9.png" alt class="image--center mx-auto" /></p>
<p>For the sake of this demo, let’s use the 90% utilization threshold to filter the reservations that require our attention. <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/data-operations#use-the-filter-array-action">The Filter array action</a> is to help us here:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925069/ac6470bb-308b-45c6-bd33-1b9fad2782f7.png" alt class="image--center mx-auto" /></p>
<p>The <code>avgUtilizationPercentage</code> property is provided as a float, so be sure to use the corresponding expression to compare it with your value.</p>
<p>Next, the resulting array will still contain data about reservation utilization details for past months that might have little value for us if we already took some action on them. It would be better to focus on the last (or current) month, depending on what day of the month you run this report.</p>
<p>In my case, I run the report on the first day of each month, so it makes sense to see the reservation utilization details for the previous month only:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681925652/40e6c029-062a-4db7-9b16-74760d10e6ef.png" alt class="image--center mx-auto" /></p>
<p>You can achieve that by filtering the <code>usageDate</code> property and using the following expression to get last month’s value in the same format as in the API response:</p>
<p><code>addToTime(utcNow(), -1, 'month', 'yyyy-MM')</code></p>
<p>Optionally, if you want to make your report cleaner, you can remove unnecessary data and select only properties you want to display in the resulting set with <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/data-operations#use-the-select-action">the Select action</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681926256/f53877c8-5168-47e9-9131-c08c81e61541.png" alt class="image--center mx-auto" /></p>
<p>Now, when your resulting list of Azure Reservations is ready, you can deliver it to your target audience.</p>
<p>To keep it simple, we will send the flow output as an email notification. For that, we need to convert the resulting list into an HTML table using <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/data-operations#use-the-create-html-table-action">the Create HTML table action</a> and then insert the formatted table into the message body:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681926839/2566d1f3-9528-4597-b14b-99888ed5b17b.png" alt class="image--center mx-auto" /></p>
<p>Lastly, to put the reporting on autopilot, you can set it to run on a schedule using <a target="_blank" href="https://learn.microsoft.com/en-us/power-automate/run-scheduled-tasks#configure-advanced-options">the Recurrence trigger</a>:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681927472/b71ad805-f567-4ae3-9b6b-72558a48d674.png" alt class="image--center mx-auto" /></p>
<p>Unfortunately, there is no easy way to schedule the trigger for a specific day of a month yet, so you can set it to execute daily with the following trigger condition:</p>
<p><code>@equals(utcNow('dd'), '01')</code></p>
<p>The final flow structure:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681928036/68200ef0-1c91-4ab5-a34a-12b3dbc9c080.png" alt class="image--center mx-auto" /></p>
<p>The result of your efforts might look like in the following example:</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1734681928988/357eb18a-1743-4113-b02d-35f53d09a8dc.png" alt class="image--center mx-auto" /></p>
<h2 id="heading-in-conclusion">In conclusion</h2>
<p>Monitoring your Azure Reservations utilization is really important as it explicitly correlates with the numbers in your cloud invoices. It should be a regular part of your <a target="_blank" href="https://andrewmatveychuk.com/tag/finops/">FinOps</a> practices in Azure, along with analyzing opportunities to purchase new Azure Reservations for the services in use. While Azure Advisor can suggest which reservation to purchase, the ‘rebalancing’ of existing ones is completely up to you. Having some food for thought, like the list of Azure Reservations you don’t use fully, and the list of new reservations to purchase, is the first step in the optimization process.</p>
<p>If you have questions about using Azure Reservations or would like to see more content about Azure <a target="_blank" href="https://andrewmatveychuk.com/tag/finops/">FinOps</a> practices in general, let me know in the comments! 👇</p>
]]></content:encoded></item></channel></rss>