<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>azure kubernetes service on Digi Hunch</title><link>https://static.digihunch.com/tag/azure-kubernetes-service/</link><description>Recent content in azure kubernetes service on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Thu, 17 Apr 2025 14:03:57 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/azure-kubernetes-service/index.xml" rel="self" type="application/rss+xml"/><item><title>Workload Identity on Kubernetes 1 of 2 – AKS</title><link>https://static.digihunch.com/2023/12/workload-identity-on-kubernetes-1-of-2-aks/</link><pubDate>Sat, 23 Dec 2023 16:43:00 -0400</pubDate><guid>https://static.digihunch.com/2023/12/workload-identity-on-kubernetes-1-of-2-aks/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-workload-identity-aks.webp" alt="Featured image of post Workload Identity on Kubernetes 1 of 2 – AKS" /&gt;&lt;p class="wp-block-paragraph"&gt;As applications are moved to the cloud, the application workload hosted on virtual machines need to interact with cloud resources. For this, we need an IAM solution with two mechanisms:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;a (non-human) identity in the cloud service platform (CSP), to represent the application;&lt;/li&gt;&#10;&lt;li&gt;a way to grant permission to this identity, so it can manage resources&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;CSPs such as Azure and AWS have their own implementations of the two mechanism. In Azure, we have Entra workload identity (including service principal and managed identity) for the first mechanism, and Azure roles for the second. On AWS, they are the identity pool capability of Amazon Cognito and IAM role. Next, what about the workload on managed Kubernetes service? Essentially, we will need to more mechanisms:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;a native Kubernetes identity to represent the workload (Pod);&lt;/li&gt;&#10;&lt;li&gt;a way to map the Kubernetes identity to the identity in CSP&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes Service Account is designed for the first item. The second mechanism is for the CSP to address. In this post, let&amp;#8217;s examine this in Azure. Specifically, how does Azure manage workload identity with Azure Kubernetes Service (AKS). &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Node Identity and Cluster Identity in AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s define what exactly is a workload identity. In Azure we think of it as one type of non-human identity. In our context, workload identity in the broader sense contains:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;the identity that represents the control plane (or the whole cluster)&lt;/li&gt;&#10;&lt;li&gt;the identity that represents the node (or kubelet process)&lt;/li&gt;&#10;&lt;li&gt;the identity that represents the application in a Pod (workload identity in the narrow sense);&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So it is important not to confuse these identities. In this section, I&amp;#8217;ll focus on 1 and 2 since they are part of workload identity in the broad sense. In the rest of the sections, I&amp;#8217;ll discuss 3, and use the narrow sense of workload identity. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="934" height="322" src="https://static.digihunch.com/wp-content/uploads/2023/12/aks-identity.webp" alt="" class="wp-image-12970" srcset="https://static.digihunch.com/wp-content/uploads/2023/12/aks-identity.webp 934w, https://static.digihunch.com/wp-content/uploads/2023/12/aks-identity-300x103.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/12/aks-identity-768x265.webp 768w" sizes="auto, (max-width: 934px) 100vw, 934px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we create an AKS cluster, we create both a cluster control plane and a node pool. Both the control plane and the nodes need to provision cloud resources using Cloud API from Azure. For example, if we use Terraform&amp;#8217;s AzureRM provider to create an &lt;code&gt;azurerm_kubernetes_cluster&lt;/code&gt; resource, then we specify the cluster&amp;#8217;s identity using &lt;code&gt;service_principal&lt;/code&gt; or &lt;code&gt;identity&lt;/code&gt; block. We specify the nodes&amp;#8217; identity using the &lt;code&gt;kubelet_identity&lt;/code&gt; block, because &lt;code&gt;kubelet&lt;/code&gt; is the process that runs on each node. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Even though a cluster builder might be tempted to assign the same identity to both Control plane and kubelet, the security best practice is to keep them separated. It is also the responsibility of the cluster builder to distinguish activities by the control plane and by kubelet process on each node, and attache an Azure Role with minimum privilege to each of the identities.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These two types of identities (control plane and kubelet) are relatively straightforward. In order to use them, we don&amp;#8217;t have to play with Kubernetes objects. In the next section, we&amp;#8217;ll continue to discuss the identities that represents each Pod in Azure. We now refer to them as workload identities, but the first available technology was pod managed identity.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Pod Managed Identity in AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When I first worked on &lt;a href="https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/"&gt;Azure Kubernetes&lt;/a&gt;, Pod managed identity was in preview and was the recommendation. However, Microsoft renamed it (to &lt;a href="https://learn.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity"&gt;Microsoft Entra pod-managed identities&lt;/a&gt;) and then deprecated it after a couple years of preview. As of Oct 2022, the recommended mechanism becomes Microsoft Entra Workload ID. For simplicity, we refer to the deprecated mechanism as &amp;#8220;Pod Identity&amp;#8221;. We discuss pod identity only for the purpose of understanding why it is no longer recommended and what is missing in it. For new workload deployment, we should always use workload identity. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Pod Identity to work, a&lt;a href="https://learn.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity#register-the-enablepodidentitypreview-feature-flag"&gt; feature flag&lt;/a&gt; &lt;code&gt;EnablePodIdentityPreview&lt;/code&gt; must turn on. Pod Managed Identity &lt;a href="https://learn.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity#operation-mode-options"&gt;operates&lt;/a&gt; on a Kubernetes &lt;a href="https://static.digihunch.com/2022/04/kubernetes-operator/"&gt;controller&lt;/a&gt; called MIC (Managed Identity Controller) and a DaemonSet called NMI (Node Managed identity). You start with an Azure managed identity with appropriate roles. Once you installed Pod Identity, there will be two CRDs &lt;code&gt;AzureIdentity&lt;/code&gt; and &lt;code&gt;AzureIdentityBinding&lt;/code&gt;. To grant Azure permissions to a Pod, you create a CR for each CRD. The &lt;code&gt;AzureIdentity&lt;/code&gt; CR connects to your Azure managed identity. You also create an &lt;code&gt;AzureIdentityBindign&lt;/code&gt; CR. When declaring a Pod, you link to AzureIdentityBinding by using the label aadpodidbinding. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="508" src="https://static.digihunch.com/wp-content/uploads/2023/12/aks-pod-mgd-identity.webp" alt="" class="wp-image-12971" style="width:663px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2023/12/aks-pod-mgd-identity.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/12/aks-pod-mgd-identity-300x149.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/12/aks-pod-mgd-identity-768x381.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are two problems with pod managed identity. First, there is a &lt;a href="https://learn.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity#using-kubenet-network-plugin-with-microsoft-entra-pod-managed-identities"&gt;vulnerability&lt;/a&gt; when it works with kubenet as network plugin. This vulnerability requires an additional &lt;a href="https://learn.microsoft.com/en-us/azure/aks/use-azure-ad-pod-identity#mitigation"&gt;mitigation step&lt;/a&gt;. Second, it does not make use of Kubernete&amp;#8217;s Service Account. Let&amp;#8217;s discuss in the next section why it&amp;#8217;s favourable to use Kubernetes&amp;#8217; ServiceAccount. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kubernetes Service Account&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes RBAC model, &lt;a href="https://kubernetes.io/docs/concepts/security/service-accounts/"&gt;Service Account&lt;/a&gt; can bind to Roles to gain access to other Kubernetes resources. The most common use case is allowing the running application in a Pod to access other Kubernetes resources. When it comes to letting an application in Pod access cloud resources in the CSP, it makes sense to use Service Account, for a a consistent pattern.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A service account must carry a token to function. Each namespace has a default service account with the token mounted automatically. Each Pod created in a namespace uses the default service account of the namespace, unless otherwise specified. However, many security organizations do not considered this default behaviour as the best practice. For example, CIS Kubernetes &lt;a href="https://www.cisecurity.org/benchmark/kubernetes"&gt;benchmark&lt;/a&gt; 1.8 has these two recommendations:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Ensure that the default service accounts are not actively used (5.1.5)&lt;/li&gt;&#10;&lt;li&gt;Ensure that Service Account Tokens are only mounted where necessary (5.1.6)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In other words, we should create non-default service account with &lt;strong&gt;automountServiceAccountToken&lt;/strong&gt; set to false. Then when declaring a Pod, we explicitly specify the service account and where to grab the token for the service account. One way to pass ServiceAccount token is through &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#serviceaccount-token-volume-projection"&gt;volume projection&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To allow a Pod to access Azure resources, we use the combination of Kubernetes Service Account and Microsoft Entra workload identity. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Workload Identity for AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Microsoft introduced Entra Workload Identities in late 2022 to address IAM issues around machine identities. It comes with some modern features such as conditional access (e.g. location-based access, anomaly sign-in detection, etc). A &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identities-overview"&gt;workload identity&lt;/a&gt; can be:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;application: an abstract entity as the global representation of your application for use across all tenants;&lt;/li&gt;&#10;&lt;li&gt;service principal: the local representation of a global application object in a specific tenants;&lt;/li&gt;&#10;&lt;li&gt;managed identity: a special type of service principal that eliminates the need for developers to manage credentials&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In our use case for AKS workload, we also make use of Azure&amp;#8217;s Managed Identity. This part is the same as the pod identity mechanism. However, here we create a federated identity credential for managed identity. The OIDC federated identity credential is issued by the AKS cluster. Within the AKS, the service account references the identity by client_id. &lt;a href="https://learn.microsoft.com/en-us/azure/aks/workload-identity-deploy-cluster"&gt;Here&lt;/a&gt; is the documentation for the whole process.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="588" src="https://static.digihunch.com/wp-content/uploads/2023/12/aks-workload-identity.webp" alt="" class="wp-image-12972" srcset="https://static.digihunch.com/wp-content/uploads/2023/12/aks-workload-identity.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/12/aks-workload-identity-300x172.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/12/aks-workload-identity-768x441.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One of the improvements in Entra workload identity for AKS, is the use of service account, which obviates the use of CRDs. Another improvement is the use of federated identity, whose lifecycle is tied to the cluster. This pattern is not only neater, but also standard. We map a service account to a managed identity with federated credential. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On managed Kubernetes services, we need an integration mechanism to grant Kubernetes workload access to cloud resources. We discussed what&amp;#8217;s needed in this integration mechanism and looked at Azure Kubernetes as an example. In the next post, we&amp;#8217;ll discuss how this issue is addressed in Elastic Kubernetes Service on the AWS side.&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/2023/11/wordpress-security/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;WordPress Security Basics&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2024/01/workload-identity-on-kubernetes-2-of-2-eks-and-rosa-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Workload Identity on Kubernetes 2 of 2 – EKS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Storage on Azure 3 of 3 – Ceph by Rook</title><link>https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/</link><pubDate>Fri, 26 Aug 2022 19:43:00 -0400</pubDate><guid>https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-storage-3.webp" alt="Featured image of post Kubernetes Storage on Azure 3 of 3 – Ceph by Rook" /&gt;&lt;p class="wp-block-paragraph"&gt;In the last two posts, I covered the native storage options on Azure Kubernetes Service, as well as Portworx as an example of a proprietary Software Defined Storage (SDS) solution. There are also a number of open-source alternative SDS solutions. Ceph has nearly a decade of history from prior to containerization, and is the most widely adopted storage platform. In this post, we continue to explore Ceph as an open-source storage solution on Azure Kubernetes. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-ceph-by-rook"&gt;Ceph by Rook&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ceph is an open-source SDS platform for distributed storage on a cluster and provides object, block and file storage. Installation of Ceph SDS can be complex, especially on Kubernetes platform. &lt;a href="https://rook.io/"&gt;Rook&lt;/a&gt; is a graduated CNCF project to orchestrate storage platform. Rook by itself is not SDS and it supports:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/Getting-Started/intro/"&gt;Ceph&lt;/a&gt;: configure a Ceph cluster. Think of this as the equivalent of &lt;a href="https://docs.ceph.com/en/quincy/cephadm/"&gt;cephadm&lt;/a&gt; on Kubernetes platform.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/nfs/v1.7/"&gt;NFS&lt;/a&gt;: configure an NFS server. Think of this as the equivalent of nfsd daemon on Kubernetes platform.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/cassandra/v1.7/"&gt;Cassandra&lt;/a&gt;: an operator to configure a Cassandra database cluster. It is now &lt;strong&gt;deprecated&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We play with Rook Ceph. I also refer to it as Ceph by Rook. The contribution of Rook project is it simplifies the installation as a matter of declaring custom resources using CRDs. Here are some high-level CRDs to know:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/ceph-cluster-crd/"&gt;CephCluster&lt;/a&gt;: creates a Ceph storage cluster&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Block-Storage/ceph-block-pool-crd/"&gt;CephBlockPool&lt;/a&gt;: represents a block pool&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Shared-Filesystem/ceph-filesystem-crd/"&gt;CephFilesystem&lt;/a&gt;: represents a file system&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Object-Storage/ceph-object-store-crd/#example"&gt;CephObjectStore&lt;/a&gt;: represents an object store&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/ceph-nfs-crd/"&gt;CephNFS&lt;/a&gt;: spins up a NFS Ganesha server to export NFS shares of a CephFilesystem or CephObjectStore.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As with typical Kubernetes resources in controller pattern, Ceph by Rook needs an operator along with custom resources. We can use YAML manifest for both of them, and the manifests are usually very tediously long. We can also use Helm to install both of them, by providing a value file. Now we will install Ceph on AKS.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Install Ceph Operator on AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps are influenced by two relevant posts (&lt;a href="https://carlos.mendible.com/2021/10/23/aks-high-available-storage-with-rook-and-ceph/"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/evillgenius75/rook-aks"&gt;here&lt;/a&gt;). However, I&amp;#8217;ve incorporated the cluster configuration in the &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;Azure directory of the cloudkube project&lt;/a&gt;, a modular Terraform template to configure AKS cluster and facilitate storage configuration. The node group and instance sizes are selected to be just enough to run a ceph POC cluster with minimum cost. One of the node groups is tainted with storage-node, as if the following command were 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-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl taint nodes my-node-pool-node-name storage-node&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true:NoSchedule&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;You will only need to taint the nodes with the command above if you choose not to use the cloudkube template. The taint ensures that only Pods with corresponding toleration and effect can be scheduled to those nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Helm to install Rook Operator. We need a value file (e.g. rook-ceph-operator-values.yaml) with content as 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-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//github.com/rook/rook/blob/master/Documentation/Helm-Charts/operator-chart.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;crds&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:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;csi&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:#a6e22e"&gt;provisionerTolerations&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effect&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NoSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pluginTolerations&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effect&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NoSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;agent&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:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AKS&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//rook.github.io/docs/rook/v1.7/flexvolume.html#azure-aks&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;flexVolumeDirPath&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/etc/kubernetes/volumeplugins&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;Then we install the operator with Helm:&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;helm install rook-ceph-operator rook-ceph --namespace rook-ceph --create-namespace --version v1.9.6 --repo https://charts.rook.io/release/ --values rook-ceph-operator-values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n rook-ceph get po -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;rook-ceph-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After installing the operator, we check the Pod status to make sure it is running. Then we can install the actual Ceph Cluster in one of the two ways. We can declare a CephClusterCRD ourself, or we can use Helm again to declare the CRD. Helm Chart gives us a lot of useful default values and saves us from editing a large body of YAML manifest.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Install Ceph CR on AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Helm to install CephCluster CRD. We create a value file (e.g. rook-ceph-cluster-values.yaml) with content as 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-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//github.com/rook/rook/blob/master/Documentation/Helm-Charts/ceph-cluster-chart.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;operatorNamespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;toolbox&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:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;cephObjectStores&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [] &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephObjectStore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;Setting&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;disables&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;it&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cephBlockPools&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephBlockPool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;also&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cephFileSystems&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephFileSystem&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;also&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;values&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:#a6e22e"&gt;cephClusterSpec&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:#a6e22e"&gt;mon&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:#a6e22e"&gt;count&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &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; &lt;span style="color:#a6e22e"&gt;volumeClaimTemplate&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:#a6e22e"&gt;spec&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:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;managed&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;premium&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&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:#a6e22e"&gt;requests&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:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&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:#a6e22e"&gt;limits&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:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&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:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;1Gi&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:#a6e22e"&gt;requests&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:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;100m&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:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500Mi&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:#a6e22e"&gt;dashboard&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:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&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:#a6e22e"&gt;storageClassDeviceSets&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;The&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;number&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;device&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;count&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &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; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IMPORTANT&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;If&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;volumes&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;specified&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;are&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;portable&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;across&lt;/span&gt; &lt;span style="color:#a6e22e"&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:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;needs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;For&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;provisioner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;should&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;portable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Since&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;could&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;up&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;any&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;an&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effort&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;needs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;made&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;across&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;nodes&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;much&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;possible&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;Unfortunately&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pod&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;anti&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;affinity&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;breaks&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;down&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;soon&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;have&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;more&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;than&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;one&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSD&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;per&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;The&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;topology&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;constraints&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;give&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;us&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;an&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;even&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;K8s&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.18&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;or&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newer&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;placement&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:#a6e22e"&gt;topologySpreadConstraints&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxSkew&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologyKey&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hostname&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ScheduleAnyway&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labelSelector&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:#a6e22e"&gt;matchExpressions&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tolerations&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;preparePlacement&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:#a6e22e"&gt;tolerations&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;nodeAffinity&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:#a6e22e"&gt;requiredDuringSchedulingIgnoredDuringExecution&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:#a6e22e"&gt;nodeSelectorTerms&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;agentpool&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storagenp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologySpreadConstraints&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxSkew&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IMPORTANT&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;If&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;don&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;t&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;have&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;zone&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;change&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;another&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;such&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hostname&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologyKey&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;topology&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;zone&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DoNotSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labelSelector&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:#a6e22e"&gt;matchExpressions&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osd&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;prepare&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&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:#a6e22e"&gt;limits&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:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&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:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;4Gi&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:#a6e22e"&gt;requests&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:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&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:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;2Gi&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:#a6e22e"&gt;volumeClaimTemplates&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;metadata&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&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:#a6e22e"&gt;resources&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:#a6e22e"&gt;requests&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:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;managed&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;premium&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeMode&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Block&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;accessModes&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReadWriteOnce&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;During the cluster provisioning, there will be a number of preparing Pods. We want those Pods to run on nodes with label agentpool=storagenp. In real life, we need to orchestrate where to run each workload, by restricting the nodes to schedule certain types of workload.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can install the cluster using Helm:&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;helm install rook-ceph-cluster rook-ceph-cluster --namespace rook-ceph --create-namespace --version v1.9.6 --repo https://charts.rook.io/release/ --values rook-ceph-cluster-values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After running the Helm install, it may take as long as 15 minutes for all resources to settle. Watch the Pod status in rook-ceph namespace. At the end, make sure that the cluster is created successfully:&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;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get CephCluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME DATADIRHOSTPATH MONCOUNT AGE PHASE MESSAGE HEALTH EXTERNAL&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rook-ceph /var/lib/rook &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; 15m Ready Cluster created successfully HEALTH_OK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get cephBlockPools&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME PHASE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ceph-blockpool Ready&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get cephFileSystems&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME ACTIVEMDS AGE PHASE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ceph-filesystem &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 20m Ready&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In my case it took 15 minutes before the cluster comes up as created successfully. You should notice that two storage classes were also created as a part of the install. It however did not create a storage class or CRD for object storage, because we explicitly disabled it in the Helm value file by setting cephObjectStores value to null.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Dashboard&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We enabled dashboard. To configure the dashboard view properly, we would need an ingress. For a quick view here, we can play port forwarding tricks. First we fetch the admin password for use in the next step. Then expose the dashboard to the bastion host:&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;$ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.data.password}&amp;#39;&lt;/span&gt; | base64 -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n rook-ceph port-forward svc/rook-ceph-mgr-dashboard 8443:8443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Since I don&amp;#8217;t have UI on the bastion host, I use the port forwarding trick again from my own MacBook. Start a new terminal and SSH to the bastion host with port-forwarding switch:&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;$ ssh -L 8443:localhost:8443 kubeadmin@20.116.132.8&#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 above suppose the public IP of the bastion host is 20.116.132.8. Then from my MacBook I can browse to localhost:8443 (with Safari browser which gives me the option to bypass certificate error). At the web portal, provide username (admin) and password (as retrieved above):&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1795" height="1026" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-16.png" alt="" class="wp-image-6037"/&gt;&lt;figcaption class="wp-element-caption"&gt;Ceph console for Kubernetes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the dashboard, we can also use &lt;a href="https://docs.ceph.com/en/quincy/man/8/ceph/"&gt;ceph admin tool&lt;/a&gt; from a &lt;a href="https://github.com/rook/rook/blob/master/deploy/examples/toolbox.yaml"&gt;toolbox&lt;/a&gt; pod, following &lt;a href="https://rook.io/docs/rook/v1.9/ceph-toolbox.html"&gt;this&lt;/a&gt; instruction. For monitoring, Ceph by Rook can expose metrics for &lt;a href="https://www.rook.io/docs/rook/v1.9/Storage-Configuration/Monitoring/ceph-monitoring/"&gt;Prometheus&lt;/a&gt; to scrape.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Performance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With default ceph configuration on AKS, I ran quick performance test using kube-str . The result is as follows:&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;&lt;/td&gt;&lt;td&gt;read_iops&lt;/td&gt;&lt;td&gt;write_iops&lt;/td&gt;&lt;td&gt;read_bw&lt;/td&gt;&lt;td&gt;write_bw&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ceph-block&lt;/td&gt;&lt;td&gt;IOPS=464.507294 BW(KiB/s)=1874&lt;/td&gt;&lt;td&gt;IOPS=243.296143 BW(KiB/s)=989&lt;/td&gt;&lt;td&gt;IOPS=509.928162 BW(KiB/s)=65797&lt;/td&gt;&lt;td&gt;IOPS=248.530762 BW(KiB/s)=32338&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ceph-filesystem&lt;/td&gt;&lt;td&gt;IOPS=438.701324 BW(KiB/s)=1770&lt;/td&gt;&lt;td&gt;IOPS=226.270660 BW(KiB/s)=920&lt;/td&gt;&lt;td&gt;IOPS=405.936340 BW(KiB/s)=52456&lt;/td&gt;&lt;td&gt;IOPS=208.869293 BW(KiB/s)=27229&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The metrics reflects performance under default configuration. It should not be considered as the best performance that Ceph can deliver on Azure Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I discussed three storage options for Azure Kubernetes but the idea applies to other Kubernetes platform hosted on a CSP. The &lt;a href="https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;native storage&lt;/a&gt; has significant limitation. NFS has latency. Block storage does not address high availability at the storage layer. Portworx and LINSTOR fill that gap as a commercial solution. Ceph is based on Object storage.&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/2022/08/kubernetes-storage-on-azure-2-of-3-portworx/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 2 of 3 – Portworx&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/09/minio-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;MinIO for S3-compatible Object Storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Storage on Azure 2 of 3 – Portworx</title><link>https://static.digihunch.com/2022/08/kubernetes-storage-on-azure-2-of-3-portworx/</link><pubDate>Fri, 12 Aug 2022 15:23:00 -0400</pubDate><guid>https://static.digihunch.com/2022/08/kubernetes-storage-on-azure-2-of-3-portworx/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-azstorage.webp" alt="Featured image of post Kubernetes Storage on Azure 2 of 3 – Portworx" /&gt;&lt;p class="wp-block-paragraph"&gt;In the previous &lt;a href="https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;post&lt;/a&gt;, we have discussed built-in storage classes on Azure Kubernetes. Further to that, we will examine some third-party software defined storage (SDS) options that are compatible with Azure Kubernetes Service in this post. Then we take Portworx on Azure as an example. Although, these options are specific to Azure, most of the players also have solutions for other managed Kubernetes platforms. Also, the methodology to study storage options remain the same regardless of cloud service provider.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In fact, I touched on software defined storage (SDS) in the context of general non-containerized workload in a separate &lt;a href="https://static.digihunch.com/2022/07/intro-to-ceph-storage/"&gt;post&lt;/a&gt;. In short, storage providers decouple the SDS appliance from the full storage solution in order to lower the cost and increase flexibility. To the storage consumer (e.g. a process running on Linux OS), SDS can present a block disk or file system. There are also SDS solution that can host your own object storage and we will discuss that later. In fact, SDS has gained significant popularity in recent years. For example, the report &amp;#8220;&lt;a href="https://www.architecting.it/product/brksw0140-ebook/"&gt;Validating Software-Defined Storage Operating Models for the Enterprise&lt;/a&gt;&amp;#8221; by &lt;em&gt;&lt;a href="https://www.architecting.it/"&gt;archiectingit&lt;/a&gt;&lt;/em&gt; divided the evolution into four phases and cited that Gartner predicts the SDS revolution to reach 50% of the storage market by 2024, from 15% in 2020.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This post discusses SDS in the context of container storage. Then we will install Portworx on Azure Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-sds-for-kubernetes"&gt;SDS for Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Many SDS appliances also developed the capability to present storage volumes to containerized workload. I put together a list of SDS products that works on Azure with their supported access modes: &lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-cyan-bluish-gray-background-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Solution&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Licence&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Development and Support&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Access Mode&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://portworx.com/products/portworx-enterprise/features/"&gt;Portworx&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Closed source. Free Essential tier. Enterprise features on License. &lt;/td&gt;&lt;td&gt;Commercially supported by PureStorage&lt;/td&gt;&lt;td&gt;RWO, RWX&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://docs.ceph.com/en/quincy/"&gt;Ceph&lt;/a&gt; by &lt;a href="https://rook.io/"&gt;Rook&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/rook/rook"&gt;Open source&lt;/a&gt;. Rook is a graduated &lt;a href="https://www.cncf.io/projects/rook/"&gt;CNCF project&lt;/a&gt;.&lt;/td&gt;&lt;td&gt;Developed and commercially supported by &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_openshift_container_storage/4.8/html-single/red_hat_openshift_container_storage_architecture/index"&gt;Red Hat&lt;/a&gt;, &lt;a href="https://ubuntu.com/ceph"&gt;Canonical&lt;/a&gt; and &lt;a href="https://softiron.com/"&gt;SoftIron&lt;/a&gt;&lt;/td&gt;&lt;td&gt;RWO, ROX, RWX&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://openebs.io/"&gt;OpenEBS&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/openebs/openebs"&gt;Open source&lt;/a&gt;. Sandbox &lt;a href="https://www.cncf.io/projects/openebs/"&gt;CNCF project&lt;/a&gt;.&lt;/td&gt;&lt;td&gt;Developed and &lt;a href="https://openebs.io/commercial-support"&gt;commercially supported&lt;/a&gt; by &lt;a href="https://openebs.io/"&gt;MayaData&lt;/a&gt; et al.&lt;/td&gt;&lt;td&gt;RWO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://longhorn.io/"&gt;Longhorn&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/longhorn/longhorn"&gt;Open source&lt;/a&gt;. Incubating &lt;a href="https://www.cncf.io/projects/longhorn/"&gt;CNCF project&lt;/a&gt;.&lt;/td&gt;&lt;td&gt;Originally developed by &lt;a href="https://rancher.com/products/longhorn"&gt;Rancher&lt;/a&gt;, and commercially supported by &lt;a href="https://www.suse.com/products/longhorn/"&gt;SUSE&lt;/a&gt;&lt;/td&gt;&lt;td&gt;RWO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://docs.ondat.io/docs/install/microsoft-azure-aks/"&gt;StorageOS&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Closed source. License required.&lt;/td&gt;&lt;td&gt;Commercial support by &lt;a href="https://www.ondat.io/"&gt;Ondat&lt;/a&gt;.&lt;/td&gt;&lt;td&gt;RWO&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://linbit.com/kubernetes/"&gt;LINBIT&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Open-source with enterprise plans&lt;/td&gt;&lt;td&gt;Enterprise support&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;RWO is the most commonly supported mode. The report &amp;#8220;&lt;a href="https://www.architecting.it/product/performance-benchmarking-cloud-native-storage-solutions-for-kubernetes-ebook/"&gt;Performance Benchmarking Cloud Native Storage Solutions for Kubernetes&lt;/a&gt;&amp;#8221; makes a comparison of performance among some of the options in early 2021. Another potentially opinionated &lt;a href="https://linbit.com/cloud-native-sds-platform-comparsion/"&gt;comparison list&lt;/a&gt; is by LINBIT.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Portworx is a leading player with commercial SDS solution and I will test its free &lt;a href="https://portworx.com/blog/portworx-essentials-vs-portworx-enterprise/"&gt;Essential&lt;/a&gt; tier in the rest of this post. Ceph is one of the most mature leading open-source offering and I will test it in the next post.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Portworx, we can use the terraform template &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;cloudkube for Azure&lt;/a&gt;. The template assigns the kubelet&amp;#8217;s managed identity as contributor of the node resource group. The template also creates a bastion host with direct SSH access to the nodes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-portworx-operator-on-azure"&gt;Portworx Operator on Azure&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Portworx Operator to configure storage cluster. Portworx has an &lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install"&gt;instruction&lt;/a&gt; for AKS but it is not tailored to specific identity model. For simplicity, use my cloudkube &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;Terraform template&lt;/a&gt; to create the AKS cluster, and skip the &amp;#8220;&lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install/install-using-operator"&gt;Prepare Your AKS Platform&lt;/a&gt;&amp;#8221; page. Instead, follow the &amp;#8220;&lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install/azure-managed-identity-on-aks"&gt;Deploy Portworx using Azure managed identity on new AKS cluster&lt;/a&gt;&amp;#8221; page starting at step 7. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At that step, we need to create a secret with the client ID of the managed identity for node agent. The terraform template outputs the BYO identity&amp;#8217;s client ID. After cluster creation, we simply SSH to the bastion host and create the secret using the output.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1953" height="755" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-15.png" alt="" class="wp-image-5810"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To install Porworx using operator, we can follow a wizard in &lt;a href="https://central.portworx.com/"&gt;PX-central&lt;/a&gt;. If this is the first time, we need to create an account and log in to the portal. If this is not the first time and you have previously created a cluster, you need to detach that cluster by going to Profile from bottom left corner on the portal page. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Follow the guide in &amp;#8220;&lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install/install-using-operator"&gt;Install Portworx on AKS using the Operator&lt;/a&gt;&amp;#8220;. In the wizard, click on &amp;#8220;Portworx Essentials&amp;#8221; for free tier, or &amp;#8220;Portworx Enterprise&amp;#8221; for the 30-day trial. Then select operator with latest version. In the rest of the wizard steps, select options applicable to Azure environment. The last step will present two kubectl commands to install operator and install the CR. Run the command to install operator and verify result:&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;kubectl apply -f &lt;span style="color:#e6db74"&gt;&amp;#39;https://install.portworx.com/2.9?comp=pxoperator&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n kube-system get deployment portworx-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 class="wp-block-heading"&gt;Portworx Custom Resource&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To install the CR, we need to customize the given manifest in order to use our managed identity. We can download the YAML manifest (portworx_essentials.yml) and modify it in text editor. As the page &amp;#8220;&lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install/azure-managed-identity-on-aks"&gt;Deploy Portworx using Azure managed identity on new AKS cluster&lt;/a&gt;&amp;#8221; suggest at step 9: in the&amp;nbsp;env&amp;nbsp;section, remove the AZURE_CLIENT_SECRET and AZURE_TENANT_ID sections but keep the AZURE_CLIENT_ID section. My CRD declaration looks 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-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;SOURCE&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//install.portworx.com/?operator=true&amp;amp;mc=false&amp;amp;kbver=&amp;amp;oem=esse&amp;amp;user=myuserid&amp;amp;b=true&amp;amp;kd=type%3DPremium_LRS%2Csize%3D150&amp;amp;s=%22type%3DPremium_LRS%2Csize%3D150%22&amp;amp;c=my-very-long-px-cluster-id&amp;amp;aks=true&amp;amp;stork=true&amp;amp;csi=true&amp;amp;mon=true&amp;amp;tel=false&amp;amp;st=k8s&amp;amp;promop=true&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StorageCluster&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;core&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;libopenstorage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;org&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;my&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;very&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;long&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cluster&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kube&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;system&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;annotations&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:#a6e22e"&gt;portworx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;install&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;source&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;https://install.portworx.com/?operator=true&amp;amp;mc=false&amp;amp;kbver=&amp;amp;oem=esse&amp;amp;user=myuserid&amp;amp;b=true&amp;amp;kd=type%3DPremium_LRS%2Csize%3D150&amp;amp;s=%22type%3DPremium_LRS%2Csize%3D150%22&amp;amp;c=my-very-long-px-cluster-id&amp;amp;aks=true&amp;amp;stork=true&amp;amp;csi=true&amp;amp;mon=true&amp;amp;tel=false&amp;amp;st=k8s&amp;amp;promop=true&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:#a6e22e"&gt;portworx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;is&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;aks&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;true&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:#a6e22e"&gt;portworx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;misc&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;args&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;--oem esse&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:#a6e22e"&gt;spec&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:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;portworx&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;oci&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;monitor&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2.10&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;imagePullPolicy&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kvdb&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:#a6e22e"&gt;internal&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cloudStorage&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:#a6e22e"&gt;deviceSpecs&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Premium_LRS&lt;/span&gt;,&lt;span style="color:#a6e22e"&gt;size&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;150&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kvdbDeviceSpec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Premium_LRS&lt;/span&gt;,&lt;span style="color:#a6e22e"&gt;size&lt;/span&gt;&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;150&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;secretsProvider&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;k8s&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;stork&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:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;args&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:#a6e22e"&gt;webhook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;controller&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;true&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:#a6e22e"&gt;autopilot&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:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;monitoring&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:#a6e22e"&gt;prometheus&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:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;exportMetrics&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;featureGates&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:#a6e22e"&gt;CSI&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;true&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:#a6e22e"&gt;env&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:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AZURE_CLIENT_ID&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&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:#a6e22e"&gt;secretKeyRef&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;azure&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AZURE_CLIENT_ID&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;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Secret&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;essential&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kube&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;system&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;data&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:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;essen&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;user&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;id&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;aaaabbbbccccddddmyverylongpxessenuserid&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osb&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;endpoint&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ssssssyyyyyyyzzzzzzmyverylongpxosbendpoint&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;In this custom resource manifest, I specify to get the cluster to create disk from Azure, even though it has the capability of using an existing disk. Then apply the CRD manifest:&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; kubectl apply -f portworx_essentials.yml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This will take up to 10 minutes to create several related resources. There are several check points to ensure the cluster is created successfully. Here are some useful commands:&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;kubectl -n kube-system get po &lt;span style="color:#75715e"&gt;# all Pods related to portworx should be ready and running, especially the portworx-api ones&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n kube-system get storagecluster &lt;span style="color:#75715e"&gt;# the status should report online&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PX_POD&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;kubectl get pods -l name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;portworx -n kube-system -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.items[0].metadata.name}&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# get the name of one of the portworx Pod for storage cluster&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n kube-system exec -c portworx -it $PX_POD --tty --stdin -- /opt/pwx/bin/pxctl status &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# should report &amp;#34;Status: PX is operational&amp;#34; with valid license loaded&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get sc &lt;span style="color:#75715e"&gt;# portworx related storage classes are available&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 pre-built storage classes are not CSI based. However, CSI should be automatically enabled in recent operator versions. We should create our own &lt;a href="https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/csi/volumelifecycle/"&gt;CSI storage classes&lt;/a&gt; and &lt;a href="https://docs.portworx.com/portworx-install-with-kubernetes/storage-operations/create-pvcs/dynamic-provisioning/"&gt;PVCs&lt;/a&gt; using our own storage classes. Here are two examples:&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;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StorageClass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;k8s&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;csi&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;database&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;provisioner&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pxd&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;portworx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;parameters&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:#a6e22e"&gt;repl&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;2&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:#a6e22e"&gt;priority_io&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;high&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:#a6e22e"&gt;io_profile&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;db&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:#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:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;StorageClass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;k8s&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;px&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;csi&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;artifact&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;provisioner&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pxd&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;portworx&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;parameters&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:#a6e22e"&gt;repl&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;2&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:#a6e22e"&gt;priority_io&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;medium&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:#a6e22e"&gt;io_profile&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;sequential&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;That is a bare minimum Portworx installation. Check out their &lt;a href="https://docs.portworx.com/portworx-install-with-kubernetes/"&gt;documentation&lt;/a&gt; for the full features. With this minimum install we can go to the section &amp;#8220;Validate Persistent Storage&amp;#8221; from the previous post to validate the persistent volume.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For troubleshooting purpose, &lt;a href="https://docs.portworx.com/reference/cli/basics/"&gt;pxctl&lt;/a&gt; is the utility and it is available on Portworx Pods.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Performance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We care not only the functionality, but also the performance. So I ran a quick performance test using kube-str, using all default configuration. The result is as follows:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-cyan-bluish-gray-background-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;read_iops&lt;/td&gt;&lt;td&gt;write_iops&lt;/td&gt;&lt;td&gt;read_bw&lt;/td&gt;&lt;td&gt;write_bw&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;px-csi-database&lt;/td&gt;&lt;td&gt;IOPS=969.614136 BW(KiB/s)=3894&lt;/td&gt;&lt;td&gt;IOPS=729.698059 BW(KiB/s)=2935&lt;/td&gt;&lt;td&gt;IOPS=1172.772827 BW(KiB/s)=150639&lt;/td&gt;&lt;td&gt;IOPS=691.626526 BW(KiB/s)=89053&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;px-csi-artifact&lt;/td&gt;&lt;td&gt;IOPS=780.681946 BW(KiB/s)=3139&lt;/td&gt;&lt;td&gt;IOPS=682.522766 BW(KiB/s)=2746&lt;/td&gt;&lt;td&gt;IOPS=773.548584 BW(KiB/s)=99549&lt;/td&gt;&lt;td&gt;IOPS=659.015320 BW(KiB/s)=84890&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that those numbers reflect performance under default configuration, and they should not be considered as the best performance that Portworx can deliver on Azure Kubernetes. Before moving to production, it is important to establish your own test parameters that best represents the container workload, and then iterate through different parameters for the storage class based on the requirement and performance output.&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/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 1 of 3 – built-in storage and NFS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 3 of 3 – Ceph by Rook&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Storage on Azure 1 of 3 – built-in storage and NFS</title><link>https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/</link><pubDate>Sun, 31 Jul 2022 15:22:00 -0400</pubDate><guid>https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-aks-storage-1.webp" alt="Featured image of post Kubernetes Storage on Azure 1 of 3 – built-in storage and NFS" /&gt;&lt;p class="wp-block-paragraph"&gt;In the previous &lt;a href="https://static.digihunch.com/2022/05/hosting-database-on-kubernetes/"&gt;post&lt;/a&gt;, we understand that to host stateful workload, we need to manage persistent storage to the Kubernetes platform. In this post, I will explore the different storage options. These options are specific to Azure Kubernetes service. However, the principals apply to any Kubernetes platform regardless of cloud vendor.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In another old &lt;a href="https://static.digihunch.com/2021/06/kubernetes-storage-explained/"&gt;post&lt;/a&gt;, I discussed in-tree and CSI storage classes, and from a developer&amp;#8217;s perspective, how to mount volumes statically and dynamically once the storage class is available. Here in this post we are concerned with how to make storage classes available, from a platform specialist perspective.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Storage in Azure Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As soon as the Azure Kubernetes cluster is launched, a number of built-in &lt;a href="https://docs.microsoft.com/en-us/azure/aks/concepts-storage#storage-classes"&gt;storage classes&lt;/a&gt; are available. Unlike third-party storage classes, they do not require kubelet identity to be contributor for node resource group. As &lt;a href="https://static.digihunch.com/2022/05/hosting-database-on-kubernetes/"&gt;discussed&lt;/a&gt; we shall use CSI based storage classes. &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;managed-csi&lt;/li&gt;&#10;&lt;li&gt;managed-csi-premium&lt;/li&gt;&#10;&lt;li&gt;azurefile-csi&lt;/li&gt;&#10;&lt;li&gt;azurefile-csi-premium&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The main difference between them is the backing technology. However, form Kubernetes workload&amp;#8217;s perspective, the Pods as storage consumers are concerned with the &lt;a href="https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes"&gt;access mode&lt;/a&gt; instead of backing technology. Here are supported access modes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;ReadWriteOnce: read-write by a single node&lt;/li&gt;&#10;&lt;li&gt;ReadOnlyMany: read only by many nodes&lt;/li&gt;&#10;&lt;li&gt;ReadWriteMany: read-write by many nodes&lt;/li&gt;&#10;&lt;li&gt;ReadWriteOncePod: new in Kubernetes 1.22 to restrict volume access to a single Pod&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The storage classes managed-csi and managed-csi-premium support ReadWriteOnce. The storage classes azurefile-csi and azurefile-csi-premium support ReadWriteMany.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from these built-in options, Azure also suggests a few more options based on other types of Azure resources. For example, AKS can integrate with &lt;a href="https://azure.microsoft.com/en-us/services/hpc-cache/#overview"&gt;HPC cache&lt;/a&gt; and it for HPC. We can also self-manage a virtual machine &lt;a href="https://docs.microsoft.com/en-us/azure/aks/azure-nfs-volume"&gt;configured as NFS server&lt;/a&gt;, and use the &lt;a href="https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner"&gt;NFS subdir external provisioner&lt;/a&gt; to configure storage class. Despite of the overhead with managing a VM, you have more configurability. My previous client reports that they gain better performance than the built-in options. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another alternative is &lt;a href="https://docs.microsoft.com/en-us/azure/aks/azure-netapp-files"&gt;Azure NetApp Files&lt;/a&gt;. However, being a full enterprise grade solution (similar to &lt;a href="https://static.digihunch.com/2022/05/fsx-ontap-enterprise-storage-on-aws/"&gt;FSx ONTAP&lt;/a&gt;), &lt;a href="https://azure.microsoft.com/en-ca/services/netapp/"&gt;Azure NetApp Files&lt;/a&gt; costs an arm and a leg. In &lt;a href="https://www.kasten.io/kubernetes/resources/blog/aks-and-storage-performance-differences-among-kubernetes-storage-services"&gt;this comparison&lt;/a&gt;, it cost 60 to 100 times as the cost by built-in options.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another option is &lt;a href="https://docs.microsoft.com/en-us/azure/aks/use-ultra-disks"&gt;Azure Ultra Disk&lt;/a&gt;, which needs to be enabled at cluster level. You can provision performance target (DiskIOPSReadWrite and DiskMBpsReadWrite) in the storage class. Ultra Disk is a good middle ground between the pricey NetApp files and the less performant built-in options.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-terraform-template-for-aks"&gt;Terraform Template for AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To explore the storage options, I use my own terraform template to create an AKS cluster. The template is in the &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;azure&lt;/a&gt; directory of the &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; repo. The template consists a few configurations with Azure Kubernetes to facilitate storage configuration. First, it configures an SSH key pair to use between the bastion host and the node. Users can SSH to Kubernetes nodes from bastion host as soon as terraform apply is completed. Second, the third party storage options installed after the cluster creation need their Pod to instruct Azure to create Azure disks. This requires that a Kubernetes node agent have the permission to provision resources in the node resource group. This is important to understand because there are a couple of managed identities at play (refer to &lt;a href="https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/"&gt;this&lt;/a&gt; post) when building an AKS cluster. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Azure Kubernetes, it is the managed identity of kubelet, that needs to have contributor permission over the resource group for the nodes (not the one for the AKS cluster itself). A managed identity is expressed by a client ID, an object ID (aka principal ID), and the identity ID. We can find them out with an AZ CLI command as below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1899" height="207" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-14.png" alt="" class="wp-image-5653"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can also tell that the kubelet managed identity represents node agent, by connecting to a node and looking at the argument (kubernetes.azure.com/kubelet-identity-client-id) of kubelet process:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1794" height="784" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-13.png" alt="" class="wp-image-5650"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the template, I also chose to designate the same BYO identity for both the cluster and for kubelet (node agent), in order to minimize my requirement on permission. If I had left it with a system assigned identity for node agent, I would have to assign that identity as a contributor for the node resource group, either as a user, or via Terraform&amp;#8217;s identity. Either way, it is beyond what a &lt;a href="https://docs.microsoft.com/en-us/azure/role-based-access-control/role-assignments-steps"&gt;Contributor&lt;/a&gt; is allowed to do.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Benchmarking with kubestr&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I used &lt;a href="https://fio.readthedocs.io/en/latest/index.html"&gt;fio&lt;/a&gt; utility for storage benchmarking from virtual machines. However, fio utility is not for container. For &lt;a href="https://docs.portworx.com/install-with-other/operate-and-maintain/performance-and-tuning/fio/"&gt;fio testing&lt;/a&gt; on Kubernetes, I&amp;#8217;d have to use a Docker image, and test with target volume attached. Fortunately, the &lt;a href="https://www.kasten.io/"&gt;Kasten&lt;/a&gt; team shared their initiative in the open source project &lt;a href="https://kubestr.io/"&gt;Kubestr&lt;/a&gt;. The kubestr &lt;a href="https://github.com/kastenhq/kubestr/releases/tag/v0.4.31"&gt;release&lt;/a&gt; is available as an executable on common platforms. It connects to the cluster the same way as kubectl and &lt;a href="https://www.youtube.com/watch?v=j9UkQM-oa1k"&gt;here&lt;/a&gt; is a demo. To begin with, download the utility to bastion host, and run it without any argument, which prints the storage classes and &lt;a href="https://kubernetes.io/docs/concepts/storage/volume-snapshot-classes/"&gt;volume snapshot classes&lt;/a&gt;:&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;curl -L -o kubestr.tar.gz https://github.com/kastenhq/kubestr/releases/download/v0.4.31/kubestr_0.4.31_Linux_amd64.tar.gz&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tar -xvf kubestr.tar.gz &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rm kubestr.tar.gz &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; chmod +x kubestr&#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;./kubestr &lt;span style="color:#75715e"&gt;# if kubectl is configured, this command will print out the details of storage classes and volume snapshot classes&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;In addition to outputting details, it is also very simple to perform storage benchmarking with kubestr. All we need to do is giving it the storage class name and it will run four tests by default with common global options (ioengine=libaio verify=0 direct=1 gtod_reduce=1). The four tests are:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-cyan-bluish-gray-background-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;JobName&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;block_s&lt;/strong&gt;ize&lt;/td&gt;&lt;td&gt;&lt;strong&gt;filesize&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;iodepth&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;rw&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;read_iops&lt;/td&gt;&lt;td&gt;4k&lt;/td&gt;&lt;td&gt;2G&lt;/td&gt;&lt;td&gt;64&lt;/td&gt;&lt;td&gt;randread&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;write_iops&lt;/td&gt;&lt;td&gt;4k&lt;/td&gt;&lt;td&gt;2G&lt;/td&gt;&lt;td&gt;64&lt;/td&gt;&lt;td&gt;randwrite&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;read_bw&lt;/td&gt;&lt;td&gt;128k&lt;/td&gt;&lt;td&gt;2G&lt;/td&gt;&lt;td&gt;64&lt;/td&gt;&lt;td&gt;randread&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;write_bw&lt;/td&gt;&lt;td&gt;128k&lt;/td&gt;&lt;td&gt;2G&lt;/td&gt;&lt;td&gt;64&lt;/td&gt;&lt;td&gt;randwrite&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;During each test, it measures and reports IOPS and bandwidth (throughput). If your I/O profile falls out of the four jobs, you can even customize your test by supplying a fio config file. For example, you need a longer test duration, or you need a larger total size for the test. Before the test, kubestr automatically mount their test volumes using the storage class being tested.&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;./kubestr fio -s my-storage-class &lt;span style="color:#75715e"&gt;# benchmarking a storage class&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;For read_iops and write_iops, we mainly look at the IOPS. For read_bw and write_bw, we mainly look at the bandwidth. The iops and bw &lt;a href="https://fio.readthedocs.io/en/latest/fio_doc.html#interpreting-the-output"&gt;based on samples&lt;/a&gt; are reported as first line of result, followed by min, max and average.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Metrics&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With kubestr I ran a performance test amongst the native storage classes with Azure Kubernetes Service, with results as below:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-cyan-bluish-gray-background-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;read_iops&lt;/td&gt;&lt;td&gt;write_iops&lt;/td&gt;&lt;td&gt;read_bw&lt;/td&gt;&lt;td&gt;write_bw&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;managed-csi&lt;/td&gt;&lt;td&gt;IOPS=314.729797 BW(KiB/s)=1275&lt;/td&gt;&lt;td&gt;IOPS=297.071136 BW(KiB/s)=1204&lt;/td&gt;&lt;td&gt;IOPS=315.311188 BW(KiB/s)=40887&lt;/td&gt;&lt;td&gt;IOPS=261.048645 BW(KiB/s)=33941&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;managed-csi-premium&lt;/td&gt;&lt;td&gt;IOPS=493.395844 BW(KiB/s)=1990&lt;/td&gt;&lt;td&gt;IOPS=426.731812 BW(KiB/s)=1723&lt;/td&gt;&lt;td&gt;IOPS=455.950348 BW(KiB/s)=58894&lt;/td&gt;&lt;td&gt;IOPS=422.888855 BW(KiB/s)=54662&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;azurefile-csi&lt;/td&gt;&lt;td&gt;IOPS=259.333282 BW(KiB/s)=1053&lt;/td&gt;&lt;td&gt;IOPS=283.985779 BW(KiB/s)=1152&lt;/td&gt;&lt;td&gt;IOPS=240.447403 BW(KiB/s)=31298&lt;/td&gt;&lt;td&gt;IOPS=230.689804 BW(KiB/s)=30048&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;azurefile-csi-premium&lt;/td&gt;&lt;td&gt;IOPS=394.044739 BW(KiB/s)=1586&lt;/td&gt;&lt;td&gt;IOPS=371.181793 BW(KiB/s)=1494&lt;/td&gt;&lt;td&gt;IOPS=380.360535 BW(KiB/s)=49018&lt;/td&gt;&lt;td&gt;IOPS=491.313446 BW(KiB/s)=63310&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These numbers are based on default test parameters (e.g. 100Gi PVC size). The numbers indicate that block storage generally performs better in default setting. So we should use managed disk instead of azure file unless multiple pods needs to access the same volume.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Validate Persistent Storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can use mysql as a quick and dirty test of storage classes. We can deploy the following manifest:&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;PersistentVolumeClaim&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql-pvc&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;spec&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;storageClassName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;managed-csi-premium&lt;/span&gt; &lt;span style="color:#75715e"&gt;# the storage class being tested&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;accessModes&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;ReadWriteOnce&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;resources&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;requests&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;storage&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5Gi&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:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Secret&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;data&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;password&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;eHl6 &lt;/span&gt; &lt;span style="color:#75715e"&gt;# base64 code of xyz&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:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;labels&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;spec&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;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&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;selector&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;matchLabels&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;template&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;metadata&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;labels&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;spec&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;containers&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;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql:5.6&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;env&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;MYSQL_ROOT_PASSWORD&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;valueFrom&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;secretKeyRef&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;key&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;password&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;ports&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;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3306&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;volumeMounts&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql-persistent-storage&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;mountPath&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;/var/lib/mysql&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;volumes&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql-persistent-storage&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;persistentVolumeClaim&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;claimName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql-pvc&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:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql-service&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;labels&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;service&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;spec&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;selector&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;mysql&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;ports&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;tcp-mysql&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;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&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;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3306&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;targetPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;3306&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;Once the Pod has been created, then we use a throw-away Pod to connect to mysql service and build some data:&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;$ kubectl run mysql-cli --rm -i --tty --image imega/mysql-client -- /bin/sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;If you don&lt;span style="color:#e6db74"&gt;&amp;#39;t see a command prompt, try pressing enter.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;/ # mysql --host=mysql-service.default.svc.cluster.local --user=root --password=xyz&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Welcome to the MariaDB monitor. Commands end with ; or \g. Your MySQL connection id is 9 Server version: 5.6.51 MySQL Community Server (GPL) Copyright (c) 2000, 2018, Oracle, MariaDB Corporation Ab and others. Type &amp;#39;&lt;/span&gt;help;&lt;span style="color:#e6db74"&gt;&amp;#39; or &amp;#39;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\h&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39; for help. Type &amp;#39;&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;\c&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt; to clear the current input statement. MySQL &lt;span style="color:#f92672"&gt;[(&lt;/span&gt;none&lt;span style="color:#f92672"&gt;)]&lt;/span&gt;&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From the mysql shell, we create a test database with dummy data populated:&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-sql" data-lang="sql"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DATABASE&lt;/span&gt; &lt;span style="color:#f92672"&gt;`&lt;/span&gt;testdb&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;USE testdb; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;CREATE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;TABLE&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;IF&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;EXISTS&lt;/span&gt; tasks ( &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; task_id INT AUTO_INCREMENT &lt;span style="color:#66d9ef"&gt;PRIMARY&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;KEY&lt;/span&gt;, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; title VARCHAR(&lt;span style="color:#ae81ff"&gt;255&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;NOT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;NULL&lt;/span&gt;, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; description TEXT, &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; created_at &lt;span style="color:#66d9ef"&gt;TIMESTAMP&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;DEFAULT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;CURRENT_TIMESTAMP&lt;/span&gt; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;) ENGINE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;INNODB; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INTO&lt;/span&gt; tasks (title, description) &lt;span style="color:#66d9ef"&gt;VALUES&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;Job A&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;Morning Standup&amp;#39;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INTO&lt;/span&gt; tasks (title, description) &lt;span style="color:#66d9ef"&gt;VALUES&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;Job B&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;Latte with two shots of espresso&amp;#39;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INTO&lt;/span&gt; tasks (title, description) &lt;span style="color:#66d9ef"&gt;VALUES&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;Job C&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;Coding coding and coding&amp;#39;&lt;/span&gt;);&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;INSERT&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;INTO&lt;/span&gt; tasks (title, description) &lt;span style="color:#66d9ef"&gt;VALUES&lt;/span&gt; (&lt;span style="color:#e6db74"&gt;&amp;#39;Job D&amp;#39;&lt;/span&gt;,&lt;span style="color:#e6db74"&gt;&amp;#39;git commit&amp;#39;&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;Each SQL command should return with number of rows affected and then we can exit the MySQL shell and Pod shell. Once we exit out of the Pod shell, the Pod is deleted. We can re-connect to validate the data are still present:&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;$ kubectl run mysql-tester --rm -i --tty --image imega/mysql-client -- mysql --host&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mysql-service.default.svc.cluster.local --user&lt;span style="color:#f92672"&gt;=&lt;/span&gt;root --password&lt;span style="color:#f92672"&gt;=&lt;/span&gt;xyz --database testdb --execute&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;SELECT * FROM tasks;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;If you don&lt;span style="color:#e6db74"&gt;&amp;#39;t see a command prompt, try pressing enter.&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Got error: Access denied for user &amp;#39;&lt;/span&gt;root&lt;span style="color:#e6db74"&gt;&amp;#39;@&amp;#39;&lt;/span&gt;147.206.3.15&lt;span style="color:#e6db74"&gt;&amp;#39; (using password: NO)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;+---------+-------+----------------------------------+---------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;| task_id | title | description | created_at |&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;+---------+-------+----------------------------------+---------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;| 1 | Job A | Morning Standup | 2022-06-22 20:29:12 |&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;| 2 | Job B | Latte with two shots of espresso | 2022-06-22 20:29:12 |&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;| 3 | Job C | Coding coding and coding | 2022-06-22 20:29:12 |&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;| 4 | Job D | git commit | 2022-06-22 20:29:13 |&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;+---------+-------+----------------------------------+---------------------+&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;Session ended, resume using &amp;#39;&lt;/span&gt;kubectl attach mysql-tester -c mysql-tester -i -t&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt; command when the pod is running&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pod &lt;span style="color:#e6db74"&gt;&amp;#34;mysql-tester&amp;#34;&lt;/span&gt; deleted&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The output validates the persistent storage of the data. When testing a different storage class, simply start over with a different storage class specified in the PVC part of the manifest.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This post focuses on storage options for persistent volumes on Azure Kubernetes service. I use my own Terraform template with some custom configuration. I also covered kubestr as benchmarking tool and ran it against the built-in storage classes. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In some situations, the built-in options do not suit your needs. For example, you might want your application to use persistent volume in a consistent way across multiple cloud vendors. We will have to resort to third-party software defined storage layer. In the next post, I will explore a couple of SDS-based options, namely Portworx and Ceph. The custom configurations in the Terraform template will be helpful when we configure Portworx.&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/2022/07/intro-to-ceph-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Ceph storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/08/kubernetes-storage-on-azure-2-of-3-portworx/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 2 of 3 – Portworx&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Admission Control</title><link>https://static.digihunch.com/2022/01/kubernetes-admission-control/</link><pubDate>Fri, 07 Jan 2022 22:21:00 -0400</pubDate><guid>https://static.digihunch.com/2022/01/kubernetes-admission-control/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-admin-control.webp" alt="Featured image of post Kubernetes Admission Control" /&gt;&lt;p class="wp-block-paragraph"&gt;This post discusses admission control, and its implementation &amp;#8211; the OPA Gatekeeper. I also discuss Azure Policy as a different Gatekeeper implementation.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="admission-webhooks"&gt;Admission Webhooks&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Admission controller intercepts requests to the Kubernetes API server after the request has been authenticated and authorized, and prior to persistence of the object into etcd store. There are many compiled-in &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#what-does-each-admission-controller-do"&gt;controllers&lt;/a&gt;, which can be turned on and off on the node with the arguments of &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/"&gt;kube-apiserver&lt;/a&gt; process. For example, the &lt;a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/#imagepolicywebhook"&gt;ImagePolicyWebhook&lt;/a&gt; can be enabled with value ImagePolicyWebhook added to the &amp;#8211;enable-admission-plugins switch. Its configuration can be provided via the &amp;#8211;admission-control-config-file &lt;a href="https://kubernetes.io/docs/reference/command-line-tools-reference/kube-apiserver/#options"&gt;switch&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In addition to the compiled-in admission plugins (which must be configured for kube-apiserver process on the node), admission plugins can be developed as extensions and run as webhooks configured at runtime. This allows users to configure webhooks via API access, dynamically without having to restart kube-apiserver process on the Node, which is usually hard to do with managed Kubernetes platforms. They are therefore called Dynamic Admission Control.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can define two types of admission webhooks in dynamic admission control: validating admission webhook, and mutating admission webhook. Their interaction with API server can be illustrated in the diagram below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="307" src="https://static.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-1024x307.jpeg" alt="" class="wp-image-7733" srcset="https://static.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-1024x307.jpeg 1024w, https://static.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-300x90.jpeg 300w, https://static.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-768x231.jpeg 768w, https://static.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-1536x461.jpeg 1536w, https://static.digihunch.com/wp-content/uploads/2023/01/Kubernetes-Admission-controllers-01-flow-diagram-2048x615.jpeg 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The mutating admission hook takes action to change the API request, whereas the validating admission hook accepts or denies the request. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A good example of mutating webhook is Istio&amp;#8217;s sidecar injector. We can view the configuration with 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;$ kubectl get MutatingWebhookConfiguration istio-sidecar-injector -o yaml | less&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From the manifest returned, we can see that in this configuration the request is forwarded to istiod service on port 443, at path /inject for processing. We can also see some matching rules to find the target Pod creation API request.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Validating webhook can be display with the following call:&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;$ kubectl get ValidatingWebhookConfiguration&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The output of validating webhook is a yes or no decision. We usually use validating webhook in conjunction with a policy engine to decide whether the request should be accepted or denied.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="open-policy-agent"&gt;Open Policy Agent&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Open Policy Agent (OPA) is an open-source general-purpose policy engine that applies policies written in &lt;a href="https://www.openpolicyagent.org/docs/latest/policy-language/"&gt;Rego language&lt;/a&gt; to ingested JSON document and returns a result. It is usually integrated with system which requires a policy engine. For example, &lt;a href="https://kyverno.io/"&gt;Kyverno&lt;/a&gt; is a policy engine designed for Kubernetes. &lt;a href="https://blog.styra.com/blog/authorize-better-istio-traffic-policies-with-opa-styra-das"&gt;Styra&lt;/a&gt; (one of the OPA contributors) develops policy engines to integrate with Istio&amp;#8217;s authorization policy. They have &lt;a href="https://academy.styra.com/"&gt;online courses&lt;/a&gt; on &lt;a href="https://academy.styra.com/courses/opa-rego"&gt;OPA policy authoring&lt;/a&gt; and &lt;a href="https://academy.styra.com/courses/microservice"&gt;microservice authorization&lt;/a&gt; with their product.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OPA is build to be a general-purpose, unified way of solving policy and authorization problem. With microservice authorization, the activities includes decision making (determine action based on input, aka Policy Decision Point, PDP), and decision enforcement (issue 400 code or 200 code depending on decision, aka Policy Enforcement Point, PEP). OPA is introduced to decouple these two activities. OPA&amp;#8217;s input is a JSON payload and it uses Policy in Rego language to come to decision.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The team that developers Open Policy Agent also created their controller (with OPA as the core component) to run validating web hook and mutating web hook. The original version is OPA-Kubernetes that uses kube-mgmt. This original version is also dubbed Gatekeeper v1.0. When OPA starts, the kube-mgmt sidecar container will load Kubernetes Namespace and Ingress objects into OPA. You can configure the sidecar to load any kind of Kubernetes object into OPA. The sidecar establishes watches on the Kubernetes API server so that OPA has access to an eventually consistent cache of Kubernetes objects. It has gone through a couple of major version changes as summarized in &lt;a href="https://kubernetes.io/blog/2019/08/06/opa-gatekeeper-policy-and-governance-for-kubernetes/#evolution"&gt;this&lt;/a&gt; section. As of today, when we deploy Gatekeeper we should use version 3.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="gatekeeper-v3"&gt;Gatekeeper v3&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Currently, Gatekeeper v3 is the most popular choice for Kubernetes Policy Controller. The diagram bellow illustrate how Gatekeeper integrates with Kubernetes API server.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="943" height="478" src="https://static.digihunch.com/wp-content/uploads/2022/02/apiserver.png" alt="" class="wp-image-3481"/&gt;&lt;figcaption class="wp-element-caption"&gt;Gatekeeper and Kubernetes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can follow &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/install/"&gt;this&lt;/a&gt; guide to install Gatekeeper but the key step is as simple as to apply the correct version of manifest. Alternatively it can be installed &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/install#deploying-via-helm"&gt;using Helm&lt;/a&gt;. After the installation, we should see a Service named &lt;em&gt;gatekeeper-webhook-service&lt;/em&gt; in the &lt;em&gt;gatekeeper-system&lt;/em&gt; namespace. We can also inspect the newly created validationg web hook configuration&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;k get validatingwebhookconfiguration gatekeeper-validating-webhook-configuration -o yaml | less&#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 indicates that the configuration forwards incoming manifests to the &lt;em&gt;gatekeeper-webhook-service&lt;/em&gt; web service at the path /v1/admin for validation, and then at /v1/admitlabel for labelling. The configuration also stores rules as matching criteria.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can smoke test Gatekeeper v3, with the basic example in its &lt;a href="https://github.com/open-policy-agent/gatekeeper/tree/master/example"&gt;directory&lt;/a&gt;. Apply the template, constraint and then the manifests in resources. The pod creation will fail with an error like:&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;Error from server &lt;span style="color:#f92672"&gt;([&lt;/span&gt;pod-must-have-gk&lt;span style="color:#f92672"&gt;]&lt;/span&gt; you must provide labels: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;gatekeeper&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&gt;})&lt;/span&gt;: error when creating &lt;span style="color:#e6db74"&gt;&amp;#34;resources/bad_pod_namespaceselector.yaml&amp;#34;&lt;/span&gt;: admission webhook &lt;span style="color:#e6db74"&gt;&amp;#34;validation.gatekeeper.sh&amp;#34;&lt;/span&gt; denied the request: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;pod-must-have-gk&lt;span style="color:#f92672"&gt;]&lt;/span&gt; you must provide labels: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;gatekeeper&amp;#34;&lt;/span&gt;&lt;span style="color:#f92672"&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;The gatekeeper &lt;a href="https://open-policy-agent.github.io/gatekeeper/website/docs/howto"&gt;document&lt;/a&gt; also covers the details of using ConstraintTemplate and Constraints. However, Writing your own a policy in Rego still takes time and we want to piggyback on the community for commonly used policies. &lt;a href="https://github.com/open-policy-agent"&gt;OPA&lt;/a&gt;&amp;#8216;s &lt;a href="https://github.com/open-policy-agent/gatekeeper-library"&gt;gatekeeper-library&lt;/a&gt; projects keeps a handful of those in its &lt;a href="https://github.com/open-policy-agent/gatekeeper-library/tree/master/library"&gt;library&lt;/a&gt; directory. We can test the &lt;a href="https://github.com/open-policy-agent/gatekeeper-library/tree/master/library/pod-security-policy/privileged-containers"&gt;privileged container&lt;/a&gt; example:&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;$ cd gatekeeper-library/library/pod-security-policy/privileged-containers&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kustomize build . | kubectl apply -f -&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;constrainttemplate.templates.gatekeeper.sh/k8spspprivilegedcontainer created&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f samples/psp-privileged-container/example_disallowed.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pod/nginx-privileged-disallowed created&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl delete -f samples/psp-privileged-container/example_disallowed.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pod &lt;span style="color:#e6db74"&gt;&amp;#34;nginx-privileged-disallowed&amp;#34;&lt;/span&gt; deleted&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f samples/psp-privileged-container/constraint.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;k8spspprivilegedcontainer.constraints.gatekeeper.sh/psp-privileged-container created&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f samples/psp-privileged-container/example_disallowed.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Error from server &lt;span style="color:#f92672"&gt;([&lt;/span&gt;psp-privileged-container&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Privileged container is not allowed: nginx, securityContext: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;privileged&amp;#34;&lt;/span&gt;: true&lt;span style="color:#f92672"&gt;})&lt;/span&gt;: error when creating &lt;span style="color:#e6db74"&gt;&amp;#34;samples/psp-privileged-container/example_disallowed.yaml&amp;#34;&lt;/span&gt;: admission webhook &lt;span style="color:#e6db74"&gt;&amp;#34;validation.gatekeeper.sh&amp;#34;&lt;/span&gt; denied the request: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;psp-privileged-container&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Privileged container is not allowed: nginx, securityContext: &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;privileged&amp;#34;&lt;/span&gt;: true&lt;span style="color:#f92672"&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;Currently the library &lt;a href="https://github.com/open-policy-agent/gatekeeper-library/tree/master/library"&gt;directory&lt;/a&gt; contains two sub-directories, general and pod-scurity-policy. The latter is to regulate Pod creation, while the former includes more common usecases such as disable node port, enforce https, and enforce probes. This is the place I start with when building a policy.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The policy constraints take effect cluster wide. When we have multiple clusters, we would like a unified place to manage policies. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="azure-policy-with-aks"&gt;Azure Policy with AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We take Azure Policy with AKS as an example to illustrate how public cloud platform can simplify policy management. When building AKS cluster, an &lt;a href="https://docs.microsoft.com/en-us/azure/governance/policy/concepts/policy-for-kubernetes#install-azure-policy-add-on-for-aks"&gt;addon&lt;/a&gt; profile for Azure Policy can be installed. This allows Azure Policy to connect to the AKS cluster. Azure Policy contains many built-in policies definitions (as well as initiative definitions which are groups of related policies). We can simply search by Kubernetes keyword and look for the built-in policies. For example, there is a built-in policy definition &amp;#8220;Kubernetes clusters should not allow container privilege escalation. The definitions (policy or initiative) can be assigned to a resource group with enforcement action set to denied, and with excluded namespaces, as shown in the screenshot below&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1566" height="1056" src="https://static.digihunch.com/wp-content/uploads/2022/02/image-2.png" alt="" class="wp-image-3497"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The assignment can take as long as 10 minutes to push down to the cluster. Then we should be able to confirm by checking the constraint CRDs. We can see this This setup brings a centralized policy management system that can be easily hooked up to multiple clusters.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://ahmedkhamessi.com/img/azurepolicy/azurepolicy.png" alt="Azure Policy and OPA Gatekeeper underlay for AKS"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Other benefits of this architecture includes the ability to report compliance. As per CIS report for Azure AKS recommendation 4.3:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure Policy extends Gatekeeper v3, an admission controller webhook for Open Policy Agent (OPA), to apply at-scale enforcements and safeguards on your clusters in a centralized, consistent manner. It covers many &lt;a href="https://static.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/"&gt;basic resource&lt;/a&gt; &lt;a href="https://static.digihunch.com/2021/02/basic-resource-object-in-kubernetes-2-of-2/"&gt;types&lt;/a&gt; but does not cover any well-known CRDs. Azure Policy makes it possible to manage and report on the compliance state of your Kubernetes clusters from one place.&amp;nbsp;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Checks with Azure Policy service for policy assignments to the cluster.&lt;/li&gt;&#10;&lt;li&gt;Deploys policy definitions into the cluster as constraint template and constraint custom resources.&lt;/li&gt;&#10;&lt;li&gt;Reports auditing and compliance details back to Azure Policy service.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As of February 2022, AWS EKS doesn&amp;#8217;t seem to have the equivalent of this capability to integrate with a policy management. The only option would be to install Gatekeeper v3 &lt;a href="https://aws.amazon.com/blogs/opensource/using-open-policy-agent-on-amazon-eks/"&gt;yourself&lt;/a&gt; on the cluster, or host it separately. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="bottom-line"&gt;Bottom line&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Admission control should be a standard setup in Kubernetes deployment. When building gatekeeper system on your own, it can be set up separately on a different cluster. When Kubernetes is provided as a platform, it is very helpful for platform operator to manage their tenants. If the tenant is application development team, it also makes sense for them to develop their own policies for the developers in their team.&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/12/from-ingress-to-gateway-why-you-need-istio-gateways-on-kubernetes-platforms/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;From Ingress to CRD: why my solution needs Istio Gateways on Kubernetes platforms&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;FluxCD: Continuous Deployment with GitOps&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>AKS Lessons Learned 2 of 2</title><link>https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/</link><pubDate>Sat, 18 Dec 2021 01:18:00 -0400</pubDate><guid>https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-aks-lession-2.webp" alt="Featured image of post AKS Lessons Learned 2 of 2" /&gt;&lt;p class="wp-block-paragraph"&gt;Even though Azure Kubernetes Service (AKS) is a managed service, building a cluster is not trivial. For help resources, I would start with the webinar &amp;#8220;Configure Your AKS cluster with Confidence&amp;#8221; from April 2021, which focuses on a set of working best practices (convention over configuration) but obviously not every recommendation suits every use case. For a deeper technical tour, the &lt;a href="https://www.youtube.com/channel/UCpIn7ox7j7bH_OFj7tYouOQ"&gt;John Savill&amp;#8217;s Technical Training channel&lt;/a&gt; has good videos (from 2020) on AKS &lt;a href="https://www.youtube.com/watch?v=c4nTKMU6fBU"&gt;overview&lt;/a&gt;, &lt;a href="https://www.youtube.com/watch?v=gksFrKiZjMc"&gt;high availability&lt;/a&gt; and &lt;a href="https://www.youtube.com/watch?v=6TZsd4toIbg"&gt;networking&lt;/a&gt;. Lastly, there is also an &lt;a href="https://www.the-aks-checklist.com/"&gt;AKS checklist&lt;/a&gt; to remind you of the implementation details to consider.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All the references aside, I need to write down some gotchas from my implementation experience in the last two month.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="identity-and-access-management"&gt;Identity and Access Management&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AKS is a special type of Azure resource in the sense that it manages other Azure services on user&amp;#8217;s behalf. Therefore the access management needs to consider several aspects:&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;&lt;strong&gt;Access Type&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Mechanisms involved&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;User access Kubernetes API&lt;/td&gt;&lt;td&gt;Azure AD, Azure RBAC and Kubernetes RBAC. &lt;br&gt;&amp;#8211; Azure AD is for authentication&lt;br&gt;&amp;#8211; Azure RBAC for Kubernetes&lt;br&gt;&amp;#8211; Kubernetes RBAC&lt;/td&gt;&lt;td&gt;A user connects to Kube-API server using kubectl&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AKS access other Azure resource&lt;/td&gt;&lt;td&gt;There are several identities that represents different components of AKS. For example, the AKS cluster, the node agent pool, and each add-on.&lt;br&gt;The AKS cluster can be represented as a service principal, or managed identity (system assigned or user assigned). &lt;br&gt;The node agent pool can be represented as a managed identity&lt;/td&gt;&lt;td&gt;AKS cluster connects to a VNet in a different resource group. (requiring cluster&amp;#8217;s identity to have network contributor role on the target network resource group)&lt;br&gt;AKS node agent pulls images from ACR (requiring the node agent pool&amp;#8217;s identity to have ArcPull role on the target ACR)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pod access other Azure resource&lt;/td&gt;&lt;td&gt;AAD-Pod Managed Identity&lt;/td&gt;&lt;td&gt;Business workload connects to managed database service such as PostgreSQL on Azure.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Pod access Kubernetes API&lt;/td&gt;&lt;td&gt;Access Kubernetes API using Service Account. This issue is solved completely by Kubernetes native mechanisms. Roles and ClusterRoles defines permissions. RoleBindings and ClusterRoleBindings associates Service Accounts with permissions&lt;/td&gt;&lt;td&gt;Workload access ConfigMap, Secret etc.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the first access type, for RBAC with user to access Kubernetes API, there is an overlap between Azure RBAC and Kubnernetes RBAC. Azure RBAC has four &lt;a href="https://docs.microsoft.com/en-us/azure/aks/manage-azure-rbac#create-role-assignments-for-users-to-access-cluster"&gt;built-in roles&lt;/a&gt; and three of them (reader, writer, admin) are namespaced. When you use Azure CLI to manage to assign one of those roles, the rolebinding and cluster rolebinding record stored in etcd will be updated accordingly. &lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-black-color has-cyan-bluish-gray-background-color has-text-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;RBAC mechansim&lt;/td&gt;&lt;td&gt;Use case&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Azure RBAC for Kubernetes&lt;/td&gt;&lt;td&gt;Manage RBAC programmatically using Azure CLI, or infrastructure as code&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Kubernetes RBAC&lt;/td&gt;&lt;td&gt;Manage RBAC declaratively with more granularity for all types of Kubernetes resources including CRD&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For ease of operation it is advised to use Kubernetes RBAC whenever possible. Azure RBAC is still used for RBAC at the level of Azure resource but not at the level of Kubernetes resource.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the the second access type, AKS cluster may use managed identity or service principal. Azure&amp;#8217;s recommendation is managed identity over service principal. Managed Identity is a wrapper around Service Principal with less overhead. Managed Identity can be system assigned (created at the time of cluster creation), or user assigned (can be created ahead of time by Azure administrator and imported to the cluster&amp;#8217;s context).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The second access type can be further broken down because there are several components in AKS that uses their own identities. I list the &lt;a href="https://docs.microsoft.com/en-us/azure/aks/use-managed-identity"&gt;related managed identities&lt;/a&gt; as below:&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;Name&lt;/td&gt;&lt;td&gt;Purpose&lt;/td&gt;&lt;td&gt;BYO identity with Terraform&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;cluster identity&lt;/td&gt;&lt;td&gt;This identity represents the cluster&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#identity"&gt;identity&lt;/a&gt; block in kubernetes_cluster resource&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;agent pool identity&lt;/td&gt;&lt;td&gt;This identity represents kubelet running in the agent pool&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#kubelet_identity"&gt;kubelet_identity&lt;/a&gt; block. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: azurepolicy&lt;/td&gt;&lt;td&gt;This identity represents azure policy addon to access the policy&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: omsagent&lt;/td&gt;&lt;td&gt;This identity represents OMS agent to access monitoring etc&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#oms_agent_identity"&gt;oms_agent_identity&lt;/a&gt; block &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: secret&lt;/td&gt;&lt;td&gt;This identity represents to the secret addon, to access AKV&lt;/td&gt;&lt;td&gt;Specify in &lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#secret_identity"&gt;secret_identity&lt;/a&gt; block &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;addon: ingress gateway &lt;/td&gt;&lt;td&gt;This identity represents the ingress application gateway&lt;/td&gt;&lt;td&gt;&lt;a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/kubernetes_cluster#ingress_application_gateway_identity"&gt;ingress_application_gateway_identity&lt;/a&gt; block&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, the system creates a new managed identity for each of the required identity above. For simplicity with identity management, we may create a managed identity and use it for all the occasions where an identity is needed and user assigned (BYO) identity is supported.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &amp;#8220;az aks show&amp;#8221; command return (a JSON document), the &lt;em&gt;identity&lt;/em&gt; section (root level) reports the cluster identity, the &lt;em&gt;identityProfile&lt;/em&gt; section (root level) reports the agent pool (kubelet) identity. Other identities such as omsagent, are reported in their own child document.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="node-networking"&gt;Node Networking &lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Azure, a subnet can span across multiple availability zones. Therefore an AKS cluster can put its nodes on a single subnet with nodes evenly distributed across three AZs for high availability. The AZ of each node is indicated in the node label, and can be &lt;a href="https://docs.microsoft.com/en-us/azure/aks/availability-zones#verify-node-distribution-across-zones"&gt;displayed&lt;/a&gt; with kubectl command.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Within a single AZ, a good practice to minimize latency between nodes is to place the nodes in a proximity placement group (PPG). However, only a &lt;a href="https://docs.microsoft.com/en-us/azure/aks/reduce-latency-ppg#node-pools-and-proximity-placement-groups"&gt;single PPG&lt;/a&gt; can be associated with a node group. You can&amp;#8217;t have three PPGs, one in each AZ, for a single subnet. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="pod-networking"&gt;Pod Networking&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The default Pod networking model is kubenet, which involves overlay network. Pod-to-Pod traffic across nodes requires Network Address Translation (NAT). To overcome this performance tax, Azure introduces Azure CNI which gives each Pod an routable IP address from the VNet&amp;#8217;s CIDR. This requires advanced IP planning to prevent IP exhaustion. A risk introduced in Azure CNI is that all Pods are exposed on the V-net, which needs to be protected by Network Security Group and/or outbound firewall.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="dns"&gt;DNS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the DNS side, when AKS cluster integrate with an external node network, it may create weird issues that are hard to troubleshoot. Another example is with DNS. If the V-Net uses an external DNS server (which is common for enterprises with hybrid network to use an on-premise DNS server), then the cluster creation failed with time-out with misleading error messages (for example, this&amp;nbsp;&lt;a href="https://github.com/Azure/AKS/issues/1972#issuecomment-868478020"&gt;comment&lt;/a&gt;). This is because the DNS name of the newly created cluster is not resolvable within the V-NET, which points to the on-prem DNS server. The fix to that is:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Use a BYO DNS zone (in Azure) for AKS cluster creation;&lt;/li&gt;&#10;&lt;li&gt;The AKS cluster will publish the A-record to the zone. To allow this to happen, the AKS cluster’s managed identity needs to have DNS contributor permission for the zone;&lt;/li&gt;&#10;&lt;li&gt;Configure the on-prem DNS for conditional forwarding to the DNS zone&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This fix will allow AKS to resolve its name and therefore confirm its own creation. Here is a good blog about the DNS zone&amp;nbsp;&lt;a href="https://blog.baeke.info/2021/07/01/dns-options-for-private-azure-kubernetes-service/"&gt;option&lt;/a&gt;s.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another potential issue introduced with the use of on-prem DNS server, is the resolution of single-label hostname of the nodes. This is not just an issue in the context of AKS. It is a generic issue with VMs running on a V-Net pointing to on-prem DNS, as explained in detail&amp;nbsp;&lt;a href="https://docs.microsoft.com/en-us/azure/virtual-network/virtual-networks-name-resolution-for-vms-and-role-instances#name-resolution-that-uses-your-own-dns-server"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this situation, we should use the fully qualified hostname instead of single-label hostname. The fully qualified hostname with DNS suffix can help the on-prem server to configure conditional forwarding. For example, when the DNS suffix is *.internal.cloudapp.net, then forward it to Azure’s virtual internal DNS server 168.63.129.16 which can resolve the hostname.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If only the Pods need to resolve those FQDNs, then we can configure Core-DNS with&amp;nbsp;&lt;a href="https://docs.microsoft.com/en-us/azure/aks/coredns-custom"&gt;conditional forwarding&lt;/a&gt;, which will take effect only at the cluster level without the need for changing the on-prem DNS. The Core-DNS configuration looks 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-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ConfigMap&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&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:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;coredns&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;custom&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;is&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;configmap&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;can&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;overwrite&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;your&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;changes&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kube&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;system&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;data&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:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;override&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;|&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;may&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;select&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;any&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;here&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;but&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;it&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;must&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; .&lt;span style="color:#a6e22e"&gt;override&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;file&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;extension&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;rewrite&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;continue&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;regex&lt;/span&gt; &lt;span style="color:#f92672"&gt;^&lt;/span&gt;(.&lt;span style="color:#f92672"&gt;*&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;]{&lt;span style="color:#ae81ff"&gt;7&lt;/span&gt;}&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;vmss&lt;/span&gt;[&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;9&lt;/span&gt;]{&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;})&lt;span style="color:#a6e22e"&gt;$&lt;/span&gt; {&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;}.&lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;answer&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt; &lt;span style="color:#f92672"&gt;^&lt;/span&gt;(.&lt;span style="color:#f92672"&gt;*&lt;/span&gt;)&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;\&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net$&lt;/span&gt; {&lt;span style="color:#ae81ff"&gt;1&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:#a6e22e"&gt;forward&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;internal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;168.63&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;129.16&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;server&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&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:#a6e22e"&gt;internal&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;cloudapp&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;net&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;53&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;errors&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;log&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cache&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;forward&lt;/span&gt; . &lt;span style="color:#ae81ff"&gt;168.63&lt;/span&gt;.&lt;span style="color:#ae81ff"&gt;129.16&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;Alternatively, use Pod&amp;nbsp;&lt;a href="https://kubernetes.io/docs/concepts/services-networking/dns-pod-service/#pod-s-dns-policy"&gt;DNS policy&lt;/a&gt;&amp;nbsp;so that the Pod can use an external DNS server.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="initial-service-account"&gt;Initial Service Account&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When a cluster is created, an Azure AD user or group can be assigned as cluster administrator. For a CI/CD pipeline to interact with the newly created cluster, a service account in Kubernetes is needed. Suppose we use &lt;a href="https://static.digihunch.com/2021/11/infrastructure-deployment-in-terraform-2-2/"&gt;Terraform&lt;/a&gt; to create the AKS cluster, we can create such service account automatically with the Kubernetes provider. This requires that the Terraform execution environment to have network access to the cluster. If the AKS cluster is located in a private network, then the agent where Terraform CLI runs should also be on the network. Alternatively, use Terraform Enterprise hosted in an environment with access to the cluster&amp;#8217;s network.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="integration-with-azure-keyvault"&gt;Integration with Azure KeyVault&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Azure Key Vault can store several types of secrets, key value pair, X509 keys and certificate. When AKV is &lt;a href="https://docs.microsoft.com/en-us/azure/aks/csi-secrets-store-identity-access"&gt;integrated&lt;/a&gt; with an AKS cluster, the Kubernetes workload can access the secrets as mounted volumes, using CRD named SecretProviderClass. Further, they can be presented as Kubernetes Secret, using a Pod to &lt;a href="https://docs.microsoft.com/en-us/azure/aks/csi-secrets-store-driver#sync-mounted-content-with-a-kubernetes-secret"&gt;sync&lt;/a&gt; between mounted content and Secret. AKV has three types of entries: key, certificate and secret (key-value). The certificate entry requires both key and certificate are stored, with optional certificate chain. In my opinion this is an over design. Unless we need Azure to manage the certificate (e.g. rotation) I would simply use secret to store my own X509 key and certificate.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&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/12/aks-troubleshooting-lessons-learned/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AKS Lessons Learned 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/12/from-ingress-to-gateway-why-you-need-istio-gateways-on-kubernetes-platforms/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;From Ingress to CRD: why my solution needs Istio Gateways on Kubernetes platforms&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>AKS Lessons Learned 1 of 2</title><link>https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/</link><pubDate>Sat, 04 Dec 2021 02:11:06 -0400</pubDate><guid>https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-aks-lesson-1.webp" alt="Featured image of post AKS Lessons Learned 1 of 2" /&gt;&lt;p class="wp-block-paragraph"&gt;In general, troubleshooting Kubernetes is tricky. That is because one has to get in and out of pods. I took two days to troubleshoot some networking issues with private AKS cluster. For the amount of of tricks I had to employ, I need to take some notes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="the-issue"&gt;The issue&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After writing the Terraform code, I used the following dummy service to test the private AKS cluster:&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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;spec&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;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&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;selector&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;matchLabels&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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;template&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;metadata&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;labels&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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;spec&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;containers&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;neilpeterson/aks-helloworld:v1&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;ports&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;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&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;env&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TITLE&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;value&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Welcome to Azure Kubernetes Service (AKS)&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:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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;spec&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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;LoadBalancer&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;ports&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;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&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;selector&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;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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 expected behaviour, is that the service object will tell cloud API to provision a load balancer, with public IP listing at port 80. I should be able to curl to the IP address and connect to the site in the Pod. However, I was not able to. On the bastion host, I was able to curl to nodePort of the node address. But anything on public IP does not work, no matter where I ran curl from. This feels like a basic issue, but is is quite annoying because the native troubleshooting tool for Azure Load Balancer is horrible. In and out of a bunch of components named &amp;#8220;insights&amp;#8221;, &amp;#8220;diagnostic log&amp;#8221;, or &amp;#8220;Metrics&amp;#8221;, I can&amp;#8217;t simply find a way to trace whether it received an HTTP request. Most of the information I was able to see was irrelevant or useless.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="the-approach"&gt;The approach&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The hard way to troubleshooting infrastructure as code, is configuration comparison approach: revert to a baseline configuration, and see if the expected function works. Then from the baseline, change one configuration at a time and see where it starts to break. This approach is very time consuming, and AKS cluster as a relatively large resource, with numerous attributes, takes this effort to extreme. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The baseline configuration I started with 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;az aks create -g AutomationTest -n orthCluster --generate-ssh-keys --node-count &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --tags Owner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;MyOwner&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With this baseline, I simply use kubectl to apply the YAML file above. Then I can tell that the port is working. With a good start point, I started to apply one change at a time and repeat the test. I ran into a snug when I&amp;#8217;m using the following configuration:&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 aks create -g AutomationTest -n orthCluster --generate-ssh-keys --node-count &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --tags Owner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;MyOwner --enable-private-cluster --network-plugin azure --network-policy calico&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With the cluster created from the command above, the variable introduced is &amp;#8211;enable-private-cluster. This puts the cluster on a private network. I cannot connect to the cluster via a public endpoint anymore, and thus have to figure out some tricks to run the kubectl commands. I had to play with the Command Run feature of AKS cluster because I don&amp;#8217;t have a bastion host when using AZ CLI command. The Command Run feature would not allow me to use any file from bastion host. So i had to create my test objects, the Deploy and the Service objects all by imperative commands. The equivalent commands I worked out 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;kubectl create deployment aks-helloworld-one --image&lt;span style="color:#f92672"&gt;=&lt;/span&gt;neilpeterson/aks-helloworld:v1 --replicas&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl expose deploy aks-helloworld-one --port &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt; --target-port &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt; --type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;LoadBalancer&amp;#39;&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;Then I realized a limitation with Command Run feature: it only supports basic command switches and doesn&amp;#8217;t like switches such as &amp;#8211;replicas. So I used the following commands:&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 aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl get no&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl create deployment aks-helloworld-one --image=neilpeterson/aks-helloworld:v1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl get deploy&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl expose deploy aks-helloworld-one --port 80 --target-port 80 --type=LoadBalancer&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl get svc&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 trick allows me to continue with the testing eliminate Azure CNI and Calico policy as the cause. Testing after each cluster creation is painful because the cluster creation can take 10 minutes.I had to temporarily minimize the size of the cluster to speed up provisioning. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I finally came to the point that I can reproduce the issue using TF template. I realized that when I set the vnet_subnet_id attribute of azurerm_kubernetes_cluster&amp;#8217;s default_node_pool, the problem came back. That&amp;#8217;s the smoking gun that the node subnet is the issue. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="the-network-security-group-on-node-subnet"&gt;The Network Security Group on Node Subnet&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The node subnet has an associated network security group. I discovered that once I add an allow rule for port 80 to the security group, the curl test will work. I also noticed the security group rule change will take 60 sec to come to effect and load balancer will also take 60 sec to warm up.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This confuses me because port 80 is only listened by the load balancer and not by any of the nodes. It&amp;#8217;s most likely when public load balancer is used the load balancer is placed on the node subnet. According to &lt;a href="https://docs.microsoft.com/en-us/azure/aks/load-balancer-standard"&gt;this&lt;/a&gt; note: &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Inbound, external traffic flows from the load balancer to the virtual network for your AKS cluster. The virtual network has a Network Security Group (NSG) which allows all inbound traffic from the load balancer. This NSG uses a service tag of type LoadBalancer to allow traffic from the load balancer.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The packet coming from external source can travel up to the VNet, but it was blocked at the NSG of node subnet.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="lessons-learned"&gt;Lessons Learned&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We always need to have some dummy service ready to test what we need. We can use nginx dummy service like:&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Deployment&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;spec&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;selector&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;matchLabels&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;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&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;template&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;metadata&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;labels&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;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;spec&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;containers&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&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;ports&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;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&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:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&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;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&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;metadata&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;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;labels&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;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;spec&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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;LoadBalancer&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;ports&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;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&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;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&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;selector&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;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;As discussed above, it&amp;#8217;s also important to have a Bastion host able to access the control plane when the AKS cluster is private. Azure touts about CloudShell (and its ability to run in specified V-Net) but it&amp;#8217;s pretty useless in troubleshooting. CloudShell sessions run inside of Kubernetes cluster and lacks common network troubleshooting tool such as nc. Azure has a managed service for Bastion but it requires a subnet with the exact name of AzureBastionSubnet.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will explore more issues in the &lt;a href="https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/"&gt;next&lt;/a&gt; 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/11/from-microservice-to-service-mesh/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;From Microservice to Service Mesh&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AKS Lessons Learned 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>