<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>managed identity on Digi Hunch</title><link>https://static.digihunch.com/tag/managed-identity/</link><description>Recent content in managed identity on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 13:58:29 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/managed-identity/index.xml" rel="self" type="application/rss+xml"/><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>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></channel></rss>