<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>CLI (Command Line Interface) on Digi Hunch</title><link>https://static.digihunch.com/tags/cli-command-line-interface/</link><description>Recent content in CLI (Command Line Interface) on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 13:46:57 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tags/cli-command-line-interface/index.xml" rel="self" type="application/rss+xml"/><item><title>Infrastructure deployment in Terraform 2/2</title><link>https://static.digihunch.com/2021/11/infrastructure-deployment-in-terraform-2-2/</link><pubDate>Fri, 05 Nov 2021 01:05:00 -0400</pubDate><guid>https://static.digihunch.com/2021/11/infrastructure-deployment-in-terraform-2-2/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-terraform-2.webp" alt="Featured image of post Infrastructure deployment in Terraform 2/2" /&gt;&lt;p class="wp-block-paragraph"&gt;In a previous &lt;a href="https://static.digihunch.com/2021/08/scalable-infrastructure-deployment-in-terraform/"&gt;post&lt;/a&gt;, I introduced Terraform Cloud and covered how to use AWS profiles with Terraform. This time I explored some alternatives to Terraform Cloud, in the context of Azure. I use Scalr as an example of multi-cloud management platform. I will also discuss some issues I&amp;#8217;ve came across while managing permissions and variables for Terraform.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="scalr"&gt;Scalr&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Scalr is a multi-cloud management platform. I first used it in January but since then it seemed to focus on being a collaboration platform for Terraform. It organizes deployment by environments and workspaces. Accounts in the free tiers is allowed to have one Environment. You will also need to configure (cloud) providers and VCS providers. Once configured, it is important to link a cloud provider with an Environment. Each workspace inside of an Environment can be associated with a VCS provider. In the case of Terraform, this limits a workspace with a single cloud provider.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="permission-with-azure"&gt;Permission with Azure&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I have a resource group (e.g. named AutomationTest) under a subscription. My account has Contributor role of this resource group. To run Terraform, I could login to Azure as my own account on my environment using AWS CLI. Terraform will pick up the session from Azure CLI and execute as my user. However, it is recommended to run Terraform as a separate own entity. This would allow me to run Terraform template from Scalr, or Terraform Cloud. It is also a good practice for Terraform to use a separate account than a regular user account. There are a number of ways to do this as suggested on the &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs"&gt;guides&lt;/a&gt; for Terraform azurerm provider, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Authenticating via &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret"&gt;a Service Principal and a Client Secret&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Authenticating via &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_certificate"&gt;a Service Principal and a Client Certificate&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Authenticating via &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/managed_service_identity"&gt;Managed Identity&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;Authenticating via the &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/azure_cli"&gt;Azure CLI&lt;/a&gt;, only recommended when running Terraform locally.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I chose the first option and followed the instruction, using the following CLI command to create the service principal:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az ad sp create-for-rbac -n tf-sp --role&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Contributor&amp;#34;&lt;/span&gt; --scopes&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/subscriptions/9dd2c898-8111-4322-91d6-a039a00bd513/resourceGroups/AutomationTest&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command returns a few attributes (client ID, tenant ID, secret) that I needed to configure cloud providers in Scalr. The service principal will also be visible under App Registrations in Azure. Once configured I needed to link the provider to an Environment, for Scalr to make an connection to Azure. Otherwise, the Scalr run will return the following Error:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://static.digihunch.com/wp-content/uploads/2021/11/image.png" alt="" class="wp-image-2900" width="840" height="211"/&gt;&lt;figcaption class="wp-element-caption"&gt;Terraform error&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Once provider linking is completed, Scalr automatically populate required environment variables in the workspace. They show up as &amp;#8220;Shell&amp;#8221; variables under VARIABLES tab.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1039" height="485" src="https://static.digihunch.com/wp-content/uploads/2021/11/image-1.png" alt="" class="wp-image-2901"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Under the Terraform tab are input variables that you wish to put in for Terraform template to pick up. Then you can run the template. This works well until I came across a permission issue when I added azurerm_role_assignment resource in Terraform template. What I was trying to do is something like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-hcl" data-lang="hcl"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;resource&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;azurerm_role_assignment&amp;#34; &amp;#34;admin_assignment&amp;#34;&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scope &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;rbac_aks_id&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; role_definition_name &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Azure Kubernetes Service RBAC Admin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; principal_id &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;var&lt;/span&gt;.&lt;span style="color:#66d9ef"&gt;rbac_principal_object_id&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;And whenever at this line, the following error returned:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1230" height="287" src="https://static.digihunch.com/wp-content/uploads/2021/11/image-2.png" alt="" class="wp-image-2902"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apparently the code 403 indicates Azure doesn&amp;#8217;t think the Terraform Service Principal has the privilege to perform &lt;strong&gt;Microsoft.Authorization/roleAssignments&lt;/strong&gt; action. The reason dates back to the way I created service principle above, where I specified contributor role for resource group. However, &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#contributor"&gt;contributor&lt;/a&gt; as a built-in role does not include the permission to assign roles in Azure RBAC. To address this issue, I needed a custom role, named TerraformContributor, with the following definition:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;assignableScopes&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/subscriptions/9dd2c898-8111-4322-91d6-a039a00bd513/resourceGroups/AutomationTest&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;description&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Grants full access to manage all resources, but does not allow you to manage assignments in Azure Blueprints, or share image galleries.&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;id&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/subscriptions/9dd2c898-8111-4322-91d6-a039a00bd513/providers/Microsoft.Authorization/roleDefinitions/637824aa-52ae-42f6-a24e-26b2a443afdf&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;name&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;637824aa-52ae-42f6-a24e-26b2a443afdf&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;permissions&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;actions&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;dataActions&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;notActions&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.Blueprint/blueprintAssignments/delete&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.Compute/galleries/share/action&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.Blueprint/blueprintAssignments/write&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;notDataActions&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; []&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;roleName&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;TerraformContributor&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;roleType&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;CustomRole&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;type&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;Microsoft.Authorization/roleDefinitions&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Compare this with the JSON statement of built-in &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#contributor"&gt;contributor&lt;/a&gt; role, the exclusion of actions for Microsoft.Authorization are removed. The creation can be completed with &lt;a href="https://docs.microsoft.com/en-us/cli/azure/role/definition?view=azure-cli-latest#az_role_definition_create-examples"&gt;CLI command&lt;/a&gt; &amp;#8220;az role definition create&amp;#8221; in the subscription, or use &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/custom-roles-portal#step-4-permissions"&gt;Azure portal&lt;/a&gt;. Once the role is created, create a new service principal using the &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az ad sp create-for-rbac -n tf-sp --role&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;TerraformContributor&amp;#34;&lt;/span&gt; --scopes&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/subscriptions/9dd2c898-8111-4322-91d6-a039a00bd513/resourceGroups/AutomationTest&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This solution is suggested on &lt;a href="https://docs.microsoft.com/en-us/answers/questions/287573/authorization-failed-when-when-writing-a-roleassig.html"&gt;this&lt;/a&gt; thread. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="composite-type-for-input-variable"&gt;Composite type for Input variable&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Sometimes we want to define an input variable that describes a hierarchy of attributes on a resource. A good example would be Azure Kubernetes service. We can use either object or map as the variable type. The example below has a variable of each type. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;variable &amp;#34;cluster_detail&amp;#34; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description = &amp;#34;AKS cluster&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = object({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resource_group = string,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cluster_name = string,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kubernetes_version = string,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; node_subnet = object({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_name = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vnet_name = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resource_group = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; pod_subnet = object({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_name = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vnet_name = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resource_group = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; lb_subnet = object({&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; subnet_name = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vnet_name = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resource_group = string&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }),&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ad_admin_group_object_ids = list(string)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; })&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;variable &amp;#34;common_tags&amp;#34; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description = &amp;#34;common tags&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type = map(any)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; default = {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tagA = &amp;#34;valueA&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tagB = &amp;#34;valueB&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When using object as the type, the default value needs to define all fields.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://www.danielstechblog.io/terraform-working-with-aks-multiple-node-pools-in-tf-azure-provider-version-1-37/"&gt;this&lt;/a&gt; example where node pool configuration is exposed, we can see how using variables with hierarchy helps template user customize infrastructure specification.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="parsing-map-as-input-variable"&gt;Parsing Map as Input variable&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To find out the best multi-cloud management platform, I tested a few of them. I find it inconsistent when I have an input variable of the &lt;a href="https://www.terraform.io/docs/language/expressions/types.html#map"&gt;map&lt;/a&gt; type. With Terraform Cloud, I have to specify the variable to be parsed as HCL, and the value has to be:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&amp;#34;Environment&amp;#34; = &amp;#34;Dev&amp;#34;, &amp;#34;Owner&amp;#34; = &amp;#34;info@digihunchtest.com&amp;#34;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With Scalr, I also have to specify the variable to be parsed as HCL, and the value can be either the one above, or the one below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&amp;#34;Environment&amp;#34;:&amp;#34;Dev&amp;#34;,&amp;#34;Owner&amp;#34;:&amp;#34;info@digihunchtest.com&amp;#34;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;So Scalr is more flexible in parsing maps. Apart from Scalr and Terraform, I also tested &lt;a href="https://www.env0.com/"&gt;env0&lt;/a&gt; but I gave up after an hour. I could not specify to parse a variable as HCL. They need to work harder on this. Neither was I able to figure out the right syntax as plain variable. I did not test &lt;a href="https://www.runatlantis.io/"&gt;Atlantis&lt;/a&gt; or &lt;a href="https://spacelift.io/"&gt;SpaceLift&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="enterprise-deployment"&gt;Enterprise Deployment&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When deploying code to enterprises with their own network environment, Scalr supports running a self-hosted agent inside of the Enterprise network. This is also supported by Terraform Cloud (manage in cloud, execution in enterprise network). This is very useful when the execution machine needs to access the resource created in the enterprise environment. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A good example, is using Terraform&amp;#8217;s Azure provider to provision an AKS cluster on the corporate network. Then use Terraform&amp;#8217;s kubernetes provider to connect to the newly created cluster and create some Kubernetes object such as service account, as illustrated in &lt;a href="https://pumpingco.de/blog/use-service-accounts-for-terraform-with-aad-integrated-aks-clusters/"&gt;this&lt;/a&gt; blog post. &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/10/notes-on-azure/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Azure Deets&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/11/istio-ingress-egress/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Istio Lab – Ingress and Egress&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Azure Deets</title><link>https://static.digihunch.com/2021/10/notes-on-azure/</link><pubDate>Mon, 25 Oct 2021 23:13:52 -0400</pubDate><guid>https://static.digihunch.com/2021/10/notes-on-azure/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-azure-lesson.webp" alt="Featured image of post Azure Deets" /&gt;&lt;p class="wp-block-paragraph"&gt;Both Azure and AWS are leading players in public cloud. AWS developed a lot of SMB customer in technology. Azure attracted many enterprises from their on-prem customers. In addition to clienteles, their models to manage resources in the cloud are also different in several aspects. While this post is by no means a comprehensive comparison, it serves as a refresher on how Azure is different from AWS in cloud engineering.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-subscription-and-resource-group"&gt;Subscription and Resource Group&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both subscription and resource group are means to manage resources in group. Subscription is associated with a credit card and groups resource financially. Resource group groups resources logically. A subscription can have multiple resource groups, as illustrated &lt;a href="https://docs.microsoft.com/en-us/azure/cloud-adoption-framework/ready/azure-setup-guide/organize-resources"&gt;here&lt;/a&gt; in the hierarchy map.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-azure-cli"&gt;Azure CLI&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are two CLI tools: Azure PowerShell for PowerShell users, and Azure CLI for Linux users. The distinction is not clear, because PowerShell can also run on multiple platforms such as &lt;a href="https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos?view=powershell-7.2"&gt;MacOS&lt;/a&gt; and &lt;a href="https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux?view=powershell-7.2"&gt;Linux&lt;/a&gt;. On the other hand, Linux Bash can run on Windows (e.g. using &lt;a href="https://static.digihunch.com/2020/06/wsl2-environment-on-windows-10/"&gt;WSL2&lt;/a&gt;). The different lies more in the command nomenclature. For example, to list subscriptions, the PowerShell Cmdlets reads:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Get-AzSubscription&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The Azure CLI comes much cleaner:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az account list&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command and argument naming in Azure CLI align with Linux commands. I prefer Azure CLI for its succinctness. When looking up VM skus, we can use this command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az vm list-skus -l eastus2&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The return is a JSON document. In advanced use cases, we can filter the result by using jq utility. Alternatively, we can filter the result and groom the output with &lt;a href="https://jmespath.org/"&gt;jmespath&lt;/a&gt; query, just like AWS CLI. For example, in eastus2 region, we look for instances that:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Has virtualMachines as resource Type&lt;/li&gt;&#10;&lt;li&gt;Has AcceleratedNetworking enabled&lt;/li&gt;&#10;&lt;li&gt;Has EncryptionAtHostSupported enabled&lt;/li&gt;&#10;&lt;li&gt;Has PremiumIO enabled &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For each of the result, we print out:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;number of vCPUs&lt;/li&gt;&#10;&lt;li&gt;Memory size&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For this use case we will have to provide a JMESPath query as below, then output the result as a table:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az vm list-skus -l eastus2 --query &lt;span style="color:#e6db74"&gt;&amp;#34;[?resourceType==&amp;#39;virtualMachines&amp;#39; &amp;amp;&amp;amp; capabilities[?name==&amp;#39;AcceleratedNetworkingEnabled&amp;#39; &amp;amp;&amp;amp; value==&amp;#39;True&amp;#39;] &amp;amp;&amp;amp; capabilities[?name==&amp;#39;EncryptionAtHostSupported&amp;#39; &amp;amp;&amp;amp; value==&amp;#39;True&amp;#39;] &amp;amp;&amp;amp; capabilities[?name==&amp;#39;PremiumIO&amp;#39; &amp;amp;&amp;amp; value==&amp;#39;True&amp;#39;]].{Name:name,vCPUs:capabilities[?name==&amp;#39;vCPUs&amp;#39;].value|[0],MemoryGB:capabilities[?name==&amp;#39;MemoryGB&amp;#39;].value|[0]}&amp;#34;&lt;/span&gt; --output table&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The result looks like this:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="578" height="584" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-10.png" alt="" class="wp-image-5548"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use variations of the command above very often to find out the best instance for AKS nodes. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-infrastructure-as-code"&gt;Infrastructure as Code&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The native infrastructure as code option is &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/management/overview"&gt;ARM&lt;/a&gt; (Azure Resource Manager) template in JSON format. It is extremely wordy and perhaps why Azure later developed &lt;a href="https://docs.microsoft.com/en-us/azure/azure-resource-manager/bicep/overview#:~:text=Bicep%20is%20a%20domain%2Dspecific,and%20support%20for%20code%20reuse.&amp;amp;text=A%20Bicep%20file%20declares%20Azure,programming%20commands%20to%20create%20resources."&gt;Bicep&lt;/a&gt; as the second generation of IaC tool. Terraform has a provider for Azure as well. For comparison among ARM, Terraform and Bicep, I have written a blog &lt;a href="https://medium.com/slalom-build/how-azure-bicep-is-different-d89322d5fe3a"&gt;post&lt;/a&gt; for Slalom build covering more details.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-networking"&gt;Networking &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://devblogs.microsoft.com/premier-developer/differentiating-between-azure-virtual-network-vnet-and-aws-virtual-private-cloud-vpc/#:~:text=A%20subnet%20is%20public%20if,and%20subnets%20for%20each%20region."&gt;Here&lt;/a&gt; is a great post comparing Azure Network with AWS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At a high level, &lt;a href="https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-overview"&gt;Azure Virtual Network&lt;/a&gt; (or VNet) is the equivalent of VPC in Amazon. Likewise, peering can be configured between VNets. As to subnet, Azure is different because there is no conceptual distinction between public subnet and private subnet. In AWS, public subnet is subnet attached with an Internet Gateway via a network route. So &amp;#8220;private&amp;#8221; or &amp;#8220;public&amp;#8221; are in terms of outbound traffic. On the other side, Azure does not distinguish between private or public subnet. Resources connected to a VNet have access out to the Internet by default. As to inbound traffic, you can make a VM available on Internet by giving it a public IP (same as AWS). You can make it available to other VNet, by configuring a service endpoint. Customers typically need &lt;a href="https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-udr-overview#custom-routes"&gt;custom routes&lt;/a&gt; to redirect outbound traffic (e.g. through firewall). In VPC, subnets are mapped to availability zones one-to-one, whereas in Azure, a subnet may traverse multiple availability zones.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With Azure, it is also important to understand &lt;a href="https://medium.com/awesome-azure/azure-difference-between-azure-private-links-and-azure-service-endpoints-private-links-vs-service-endpoints-8fb0f80ca196#:~:text=Service%20endpoint%20%E2%80%94%20It%20remains%20a,who%20connects%20to%20your%20service."&gt;difference&lt;/a&gt; between Azure service endpoint and Azure private endpoint:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Azure service endpoint: provides connectivity to Azure services over n optimized route over the Azure backbone network. Traffic will leave your VNet.&lt;/li&gt;&#10;&lt;li&gt;Azure private endpoint: a NIC that uses private IP from your VNet. This NIC connects you privately and securely to a service powered by Azure Private Link. By enabling a private endpoint, you&amp;#8217;re bringing the service into your VNet.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On security group, we can associated a network security group with a network interface, or with a subnet. In contrast, in AWS, a security group can only be associated with an instance&amp;#8217;s network interface.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-azure-bastion-and-jump-box"&gt;Azure Bastion and Jump Box&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a managed service called Azure Bastion. It is a SSH/RDP proxy fully managed as PaaS. However, its use case is virtual machines. It cannot be used to access other services. For example, if you create a private AKS cluster, then you need a command terminal to access the API server. This is not what Azure Bastion can do. Instead, you either need a virtual machine in the AKS network as jump box.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The alternative is an Azure Cloud Shell, which will require storage but can be configured to be placed inside of a V-Net. However, Azure Cloud Shell is not running inside of a full-fledged Linux operating system. You cannot install commands.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To create a bastion host, e.g. without public IP address, use the following CLI command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az vm create -n MyBastion -g AutomationTest --image UbuntuLTS --subnet suitable-porpoise-node-subnet --vnet-name suitable-porpoise-vnet --ssh-key-values ~/.ssh/id_rsa.pub --authentication-type ssh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If the bastion host is needed with a public Ip, configure the network security group accordingly. The AZ CLI command above will create a VM, with an OS user named after the command line terminal user.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-iam"&gt;IAM&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-whatis"&gt;Azure AD&lt;/a&gt; is a managed identity service. &lt;a href="https://docs.microsoft.com/en-us/azure/active-directory/fundamentals/active-directory-compare-azure-ad-to-ad"&gt;Here&lt;/a&gt; is the difference between Active Directory and Azure AD.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/overview"&gt;RBAC&lt;/a&gt; is a mechanism for authorization. Just like IAM policies, Azure RBAC enforces permissions using role assignment, which consists of:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;security principal ( user, group, service principal, or managed idenity)&lt;/li&gt;&#10;&lt;li&gt;role definition: defines what actions is allowed and what is not allowed&lt;/li&gt;&#10;&lt;li&gt;scope: the object of the action&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To enforce RBAC, one needs to create role assignment objects, each specifying principal, role, and scope.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-service-principle-and-managed-identity"&gt;Service Principle and Managed Identity&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The two concepts may appear confusing. I find &lt;a href="https://devblogs.microsoft.com/devops/demystifying-service-principals-managed-identities/"&gt;this article&lt;/a&gt; a great reference to demystify them. The takeaway is: service principle is the equivalent of service account in old Active Directory. Managed identity is a service principle automatically managed by a resource. Managed identity can be user assigned or system assigned.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Managed Identity is a &amp;#8220;wrapper&amp;#8221; around a service principal. It is automatically created and automatically rotated. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-azure-devops"&gt;Azure DevOps &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I think of Azure DevOps (ADO) of a managed pipeline implementation, with a repository (just like BitBucket), a board to manage tickets (similar to JIRA), Wiki (just like Confluence), Artifactory. The Pipelines is the part that&amp;#8217;s similar to Jenkins. ADO calls a build pipeline a &lt;em&gt;Pipeline&lt;/em&gt;, and a release/deployment pipeline a &lt;em&gt;Release&lt;/em&gt;. A pipeline and a release are fundamentally the same but they are used in different ways. A pipeline&amp;#8217;s input is usually the code repository, and the output is artifact. A release&amp;#8217;s input is usually an artifact, and it connects to infrastructure in different environments. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ADO has its own ecosystem for plugins, managed under Visual Studio &lt;a href="https://marketplace.visualstudio.com/azuredevops"&gt;marketplace&lt;/a&gt;. Many extensions are open-source. If you are not happy with an extension, you can publish your own extension to market place. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-logging-and-monitoring"&gt;Logging and Monitoring&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure Monitor manages metrics, logs and alerts. To further analyze logs, create a log analytics workspaces, where you can run Kusto queries. You can create a workbook and embed Kusto queries into visual objects on the workbook.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-storage"&gt;Storage&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure manages storage resources under storage account. The resource classes include Blob (object), File, Queue, Table and Disk (block). They have a few acronyms on redundancy levels.&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Redundancy Option&lt;/td&gt;&lt;td&gt;Data Copy&lt;/td&gt;&lt;td&gt;Access level&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LRS &amp;#8211; Locally redundant&lt;/td&gt;&lt;td&gt;synchronously copy your data three times within the AZ in the primary region.&lt;/td&gt;&lt;td&gt;Write is acknowledged after three synchronous writes.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ZRS &amp;#8211; Zone-redundant&lt;/td&gt;&lt;td&gt;synchronously copy your data across three AZs in the primary region.&lt;br&gt;your data is still accessible for both read and write even if one AZ becomes unavailable.&lt;/td&gt;&lt;td&gt;&lt;meta charset="utf-8"&gt;Write is acknowledged after three synchronous writes.&lt;br&gt;If an AZ becomes unavailable, Azure undertakes networking updates (e.g. DNS re-pointing). Application may perceive a blip where re-try policies may help.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GRS &amp;#8211; Geo-redundant&lt;/td&gt;&lt;td&gt;LRS in primary region +&lt;br&gt;asynchronously copy your data to a single AZ in the secondary region + LRS in secondary region&lt;br&gt;&lt;/td&gt;&lt;td&gt;your data in the secondary region isn&amp;#8217;t available for read or write access unless there is a failover to the secondary region.&lt;br&gt;for read access to the secondary region, configure your storage account to use RA-GRS (read-access geo-redundant storage)&lt;br&gt;If the primary region becomes unavailable, you can choose to fail over to the secondary region. After the failover has completed, the secondary region becomes the primary region, and you can again read and write data.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GZRS &amp;#8211; Geo-zone-redundant&lt;/td&gt;&lt;td&gt;ZRS in primary region +&lt;br&gt;asynchronously copy your data to a single AZ in the secondary region + LRS in secondary region&lt;/td&gt;&lt;td&gt;&lt;meta charset="utf-8"&gt;your data in the secondary region isn&amp;#8217;t available for read or write access unless there is a failover to the secondary region.&lt;br&gt;for read access to the secondary region, configure your storage account to use RA-GZRS (read-access geo-zone-redundant storage)&lt;br&gt;If the primary region becomes unavailable, you can choose to fail over to the secondary region. After the failover has completed, the secondary region becomes the primary region, and you can again read and write data.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The disaster recovery and failover happens at storage &lt;a href="https://docs.microsoft.com/en-us/azure/storage/common/storage-disaster-recovery-guidance#understand-the-account-failover-process"&gt;account level&lt;/a&gt;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/10/logging-and-monitoring-in-kubernetes-with-plg-stack/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Logging and Monitoring in Kubernetes with PLG stack&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/11/infrastructure-deployment-in-terraform-2-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Infrastructure deployment in Terraform 2/2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Local multi-node cluster – Minikube, MicroK8s and KinD</title><link>https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/</link><pubDate>Tue, 14 Sep 2021 11:18:00 -0400</pubDate><guid>https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-multi-node-k8s.webp" alt="Featured image of post Local multi-node cluster – Minikube, MicroK8s and KinD" /&gt;&lt;p class="wp-block-paragraph"&gt;In this post we compare Minikube, MicroK8s and KinD as different approaches to build multi-node cluster locally.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="is-docker-desktop-bad"&gt;Is Docker desktop bad?&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/"&gt;previous post&lt;/a&gt; about docker desktop as a single-node Kubernetes cluster setup, I touched on the deprecation of docker-shim. Now that CRI beats OCI as the standard for container runtime, the docker runtime will no longer be supported by Kubernetes. Also deprecated is docker-shim, the temporary interface that had make Docker runtime work in Kubernetes. This was announced in December 2020, and is coming through in Kubernetes 1.23, expected Oct 2021. However, docker desktop still uses docker runtime in it&amp;#8217;s single-node Kubernetes cluster. This essentially renders itself a non-compliant Kubernetes environment. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker desktop still has great value for application developers. If your role is development, spending a lot of time coding business logics and need an easy-to-use container runtime on your laptop, Docker desktop is a good choice. The recent &lt;a href="https://www.docker.com/blog/updating-product-subscriptions/"&gt;moves&lt;/a&gt; by the company seems to suggest that this is the business they are targeting now. On the other hand, if your roles are deployment, automation, orchestration, cloud native etc and you are looking for a playground, most likely you do need a runtime compliant to Kubernetes CRI. Docker desktop is not a &lt;a href="https://www.cncf.io/certification/software-conformance/"&gt;CNCF-certified project&lt;/a&gt; anymore, and it is not your choice. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="alternatives"&gt;Alternatives&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are a number of alternatives, the most well-known ones are Minikube, MicroK8s, KinD and K3s with K3d. &lt;a href="https://www.cncf.io/wp-content/uploads/2020/08/CNCF-Webinar-Navigating-the-Sea-of-Local-Clusters-.pdf"&gt;This &lt;/a&gt;presentation from CNCF in 2020 covers a lot of details about these technologies. I&amp;#8217;ll try to add my opinion.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://rancher.com/docs/k3s/latest/en/"&gt;K3s&lt;/a&gt; is Rancher Lab&amp;#8217;s lightweight Kubernetes distribution that supports multi-node cluster as well as different runtimes (e.g. containerd). It is not straightforward to setup, and &lt;a href="https://k3d.io/"&gt;k3d&lt;/a&gt; is an command-line wrapper to make it easy to install K3s cluster. K3s was accepted as a &lt;a href="https://www.cncf.io/projects/k3s/"&gt;CNCF project &lt;/a&gt;but only at Sandbox maturity level, so it is not my choice. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The other three: Minikue, MicroK8s and KinD are all certified CNCF project. I will further discuss how to choose among them. These projects are technologies that takes different approach to address the challenges with deploying multiple nodes in local environment (e.g. my laptop). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The challenge with running a Kubernetes cluster with multiple nodes locally is how to manage these nodes. They are separate virtual resources that need to be isolated from computing perspective, and connected as a cluster. This is typically the use case of a Type II &lt;a href="https://static.digihunch.com/2020/07/overview-of-virtualization/"&gt;hypervisor&lt;/a&gt;, or alternatively, it can also be implemented with container technology. This layer of technology (referred to as drivers) makes a big difference.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="minikube"&gt;Minikube&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Minikube supports multiple drivers. Depending on your platform (Windows, Linux, or MacOS), the preferred driver is different. Refer to the document &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/"&gt;here&lt;/a&gt; for preferred driver, and this blog &lt;a href="https://kubernetes.io/blog/2019/03/28/running-kubernetes-locally-on-linux-with-minikube-now-with-kubernetes-1.14-support/"&gt;post&lt;/a&gt; for more instructions. In addition to the documents, here some notes from my personal experience:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;On MacOS, &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/"&gt;Minikube&lt;/a&gt; lists Docker as preferred driver. I disagree with that. If you have no other reason to install &lt;strong&gt;Docker&lt;/strong&gt;, then I would recommend &lt;strong&gt;hyperkit&lt;/strong&gt; as the the preferred driver. Hyperkit can be installed with a simple &lt;strong&gt;Homebrew&lt;/strong&gt; command. For two reasons I do not recommend Docker as the driver of Minikube. First, it requires a separate installation of Docker Desktop, which includes a built-in instance of &lt;strong&gt;hyperkit&lt;/strong&gt; on its own. This isn&amp;#8217;t neat. Second, I often need Metal LB add-on with Minikube for testing Kubernetes Ingress. With Minikube on Docker, the Ingress ports are not exposed to MacOS&amp;#8217;s. Therefore you cannot directly visit websites spun up on Minikube. This is a &lt;a href="https://github.com/kubernetes/minikube/issues/7332"&gt;known issue&lt;/a&gt; for a while due to &lt;a href="https://github.com/kubernetes/minikube/issues/7332#issuecomment-608133325"&gt;limitation&lt;/a&gt; on docker &lt;a href="https://github.com/kubernetes/minikube/issues/13795"&gt;bridge&lt;/a&gt; with Mac. Some reported an ugly &lt;a href="https://github.com/kubernetes/minikube/issues/7332#issuecomment-1164452857"&gt;workaround&lt;/a&gt; with &lt;a href="https://github.com/chipmk/docker-mac-net-connect"&gt;docker-mac-net-connect&lt;/a&gt; but I never got it to work.&lt;/li&gt;&#10;&lt;li&gt;On Windows native environment, the preferred driver is hyper-V. The Minikube cli command have to run from Windows PowerShell. &lt;/li&gt;&#10;&lt;li&gt;On WSL2, Minikube doesn&amp;#8217;t play well, regardless of driver. The hyperkit driver won&amp;#8217;t work (it is designed for MacOS only). The kvm2 driver would require a KVM2 hypervisor. However, WSL2 itself is a VM on top of hypervisor, as explained &lt;a href="https://static.digihunch.com/2020/06/wsl2-environment-on-windows-10/"&gt;here&lt;/a&gt;. If KVM2 driver works it would require nested virtualization so I doubt it will ever be supported. As for Docker on WSL2 as driver, Minikube has it as an &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/docker/"&gt;experimental feature&lt;/a&gt;, and requires configuring cgroup to allow setting memory. I am not confident with it.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To me, Minikube is the tool for MacOS (I have Intel processor). On MacOS, we first need to install minikube and hyperkit with home brew.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can then start a kubernetes cluster, with minikube in a single command. I noticed a process on my MacBook called dnscrypt-proxy that conflicts with hyperkit DNS server when starting minikube. I had to remove dnscrypt-proxy (part of Cisco Umbrella Roaming Client) in order to get minikube to work, as &lt;a href="https://github.com/kubernetes/minikube/issues/3036"&gt;this&lt;/a&gt; thread suggests. You can find out by running:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo lsof -i :53&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If dnscrypt-proxy is running, find out the application by PID and remove the application. Otherwise there will be issues. Check out &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/hyperkit/#local-dns-server-conflict"&gt;this&lt;/a&gt; section on the document. The commands that I use to start multi-node cluster is:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube start --driver&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hyperkit --container-runtime&lt;span style="color:#f92672"&gt;=&lt;/span&gt;containerd --memory&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12288&lt;/span&gt; --cpus&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; --disk-size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;150g --nodes &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get po -A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl describe node minikube|grep Runtime&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Node administration is simple. To enable dashboard, simply run &amp;#8220;minikube dashboard&amp;#8221;. To SSH to a node, simply do &amp;#8220;minikube ssh -n &amp;lt;node_name&amp;gt;&amp;#8221;. In order to stop the node and delete cluster, run &amp;#8220;minikube stop &amp;amp;&amp;amp; minikube delete&amp;#8221;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are some addons in minikube, for example, efk, gvisor, istio, metrics-server. To list add-ons, and enable metrics-server, for example, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube addons list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube addons enable metrics-server&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When creating cluster, instead of specifying the cluster imperatively, the configuration (e.g. driver, container runtime, cpu, memory, number of nodes, etc) can be stored as a &lt;a href="https://minikube.sigs.k8s.io/docs/commands/profile/"&gt;profile&lt;/a&gt; with -p switch. Like other Minikube configuration information, Minikube profiles are stored in ~/.minikube under the profile directory.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Minikube also has a &lt;a href="https://minikube.sigs.k8s.io/docs/benchmarks/imagebuild/minikubevsothers/"&gt;page&lt;/a&gt; that benchmarks the performance of these technologies, where it presents itself as the most performant.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://minikube.sigs.k8s.io/images/benchmarks/minikubeVsOthers/iterative.png" alt="Iterative Loads"/&gt;&lt;figcaption class="wp-element-caption"&gt;Minikube, KinD, k3d and microK8s performance&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 class="wp-block-heading" id="microk8s"&gt;MicroK8s&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MicroK8s is developed by Canonical. It can use either Multipass or LXD container as driver. Multipass can configure Ubuntu VMs using cloud-init. It supports multiple hypervisor backends as well but hyperkit is the default on MacOS, Hyper-V on Windows, and KVM on Linux.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MicroK8s supports multi-node configuration across multiple machines. That is, nodes can span across multiple physical machines. This is more powerful than Minikube where multiple nodes are on the same physical machine. It brings MicroK8s additional use cases such as edge and IoT devices.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With that capability comes the extra step to configure a MicroK8s cluster. You will need to manually join a node to a cluster because the new node is potentially located on a different machine, and you execute the command from the new machine. On the other hand, with Minikube you simply specify the number of nodes desired in a command or profile.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Snap is the native package manager to install MicroK8s, making GNU Linux (e.g. Ubuntu) the native platform. It also supports MacOS and Windows. MicroK8s does not rely on Docker (unlike KinD and Minikube with Docker as driver), and uses containerd as runtime.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://microk8s.io/docs/working-with-kubectl"&gt;MicroK8s&lt;/a&gt; comes with its own packaged version of kubectl, and you use that with &amp;#8220;microk8s kubectl&amp;#8221; command, which is not convenient. You can configure your host kubectl to point to the MicroK8s cluster, as an extra step.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Compared to the other two technologies, MicroK8s is more powerful in the sense that the cluster is build on nodes across multiple machines. However, it takes more step to configure even for a multi-node, single-machine environment. Refer to &lt;a href="https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s/#:~:text=Microk8s%20is%20the%20click%2Dand,doesn't%20require%20a%20VM."&gt;this&lt;/a&gt; post for the steps.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="kind"&gt;KinD&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;KinD is similar to Minikube with Docker as driver. It is more restricted than Minikube considering Docker is the only driver it supports. This makes it a requirement to have Docker installed locally.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although KinD uses Docker to run nodes, it does not use Docker as its container runtime. Therefore it remains as compliant environment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another advantage of KinD is it supports Docker on &lt;a href="https://kind.sigs.k8s.io/docs/user/using-wsl2/"&gt;WSL2&lt;/a&gt; very well. Simply install KinD on WSL2 and start Docker. This blog &lt;a href="https://kubernetes.io/blog/2020/05/21/wsl-docker-kubernetes-on-the-windows-desktop/"&gt;post&lt;/a&gt; contains the steps required to install KinD vs Minikube on WSL2. There is a comparison table in the conclusion section that highlights the fact that it is much easier to install KinD with WSL2 than to install Minikube.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, there are currently some &lt;a href="https://docs.docker.com/desktop/windows/networking/#known-limitations-use-cases-and-workarounds"&gt;known limitations&lt;/a&gt; with Docker desktop for Windows (including on WSL2). One is the absence of docker0 bridge. This means on Windows you cannot route traffic to the containers.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For cluster specification, KinD can configure a cluster declaratively using YAML file for example, the kind-config.yaml contains the following snippet:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Cluster&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kind.x-k8s.io/v1alpha4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;nodes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;control-plane&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;networking&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;disableDefaultCNI&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can bring up a cluster with a command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind create cluster --config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kind-config.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command will also configure the kubectl context so we can check node with kubectl command. The file is in my &lt;a href="https://github.com/digihunch/real-quicK-cluster/tree/main/kind"&gt;real-quicK-cluster&lt;/a&gt; repo.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="conclusion"&gt;Conclusion&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After reviewing the technologies that back up multi-node kubernetes cluster for my role, I find that Minikube with hyperkit is my favourite for MacOS. On WSL2, I prefer to use KinD. Since I do not use Windows native environment or Ubuntu on my laptop, I cannot make recommendations. However I would start with Minikube (with hypverv or kvm2 as driver). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Update July 2022&lt;/strong&gt;: When the test workload involves persistent storage, KinD is a better choice. When the test workload involves load balancer. Minikube is a better choice.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to storage provisioner, Minikube with storage-provisioner addon uses k8s.io/&lt;a href="https://github.com/kubernetes/minikube/tree/master/deploy/addons/storage-provisioner"&gt;minikube-hostpath&lt;/a&gt;. KinD uses &lt;a href="https://github.com/rancher/local-path-provisioner"&gt;rancher.io/local-path&lt;/a&gt;. When I have to test workload with persistent storage (e.g. PostgreSQL with &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/"&gt;Crunchy pgo&lt;/a&gt;), I realized Minikube have permission issues with persistent volume, as discussed &lt;a href="https://github.com/kubernetes/minikube/issues/12360"&gt;here&lt;/a&gt; as an issue with multiple nodes. The issue has been open since Aug 2021.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Load Balancer, Minikube has metallb as an addon and I can configure it within a &lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/minikube/restart-minikube.sh"&gt;bash script&lt;/a&gt; conveniently. With KinD, I&amp;#8217;d have to configure that in a few &lt;a href="https://kind.sigs.k8s.io/docs/user/loadbalancer/"&gt;steps&lt;/a&gt; with both kubectl and Docker CLI commands and I was not able to connect to the load balancer by IP even after following the steps. So I tend to just use Minikube to test workload requiring load balancer and service mesh. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I find myself switch between Minikube and KinD on my MacBook depending on the test workload.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/09/log-shipping-in-kubernetes-with-efk/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Log Shipping in Kubernetes with EFK stack&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/09/file-storage-vs-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;File storage vs object storage in the cloud&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>