<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>fsx ontap on Digi Hunch</title><link>https://static.digihunch.com/tag/fsx-ontap/</link><description>Recent content in fsx ontap on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Tue, 08 Apr 2025 14:44:11 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/fsx-ontap/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>FSx ONTAP – Enterprise storage on AWS</title><link>https://static.digihunch.com/2022/05/fsx-ontap-enterprise-storage-on-aws/</link><pubDate>Sat, 14 May 2022 22:28:00 -0400</pubDate><guid>https://static.digihunch.com/2022/05/fsx-ontap-enterprise-storage-on-aws/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-fsx-ontap.webp" alt="Featured image of post FSx ONTAP – Enterprise storage on AWS" /&gt;&lt;p class="wp-block-paragraph"&gt;Even though object storage has gained a lot of popularity, file storage is still prevalent. AWS has Elastic File System but the performance is insufficient for enterprise workload. The FSx product line has enterprise storage options and on Sept 2, 2021, AWS launched FSx ONTAP. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This post is my impression about FSx ONTAP. As &lt;a href="https://static.digihunch.com/2021/09/file-storage-vs-object-storage/"&gt;previously discussed&lt;/a&gt;, &lt;a href="https://aws.amazon.com/fsx/netapp-ontap/"&gt;FSx ONTAP&lt;/a&gt;&amp;nbsp;is a managed NetApp storage service by AWS. Essentially, AWS installs NetApp arrays in their data centres, so that users can provision ONTAP volumes from AWS console, or using AWS CLI.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;FSx ONTAP&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ONTAP (or Data OONTAP) has been a very successful operating system to manage storage arrays. It was so successful that NetApp uses ONTAP as brandname for their storage arrays. This is similar to Isilon, a name of BSD based operating system to manage storage and later becomes brandname of EMC&amp;#8217;s storage products. ONTAP has been in competition with other enterprise storage players such as EMC Isilon, HP 3PAR, etc and AWS landed on ONTAP as their partner for enterprise storage. It appears that NetApp still owns their ONTAP storage technology. AWS operates the data centre and provides capability via the CLI layer and console.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Sometimes, people confuses FSx ONTAP with NetApps offering &lt;a href="https://cloud.netapp.com/ontap-cloud"&gt;Cloud volumes ONTAP&lt;/a&gt;. The two are fundamentally different. Cloud volumes ONTAP works with Cloud Manager (available as self-hosted or SaaS) as the management UI. It manages volumes provisioned from cloud vendor such as AWS, Azure, etc. Clients often configure these &amp;#8220;Cloud volumes&amp;#8221; as extension to an existing on-premise ONTAP storage deployment. When their on-prem ONTAP volumes fall short of space, the Cloud Manage is aware of a remotely available, cloud backed volume to move cold data off to. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-client-tool"&gt;Client Tool&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;FSx ONTAP is essentially an ONTAP storage cluster sitting in AWS data centre. Users have the options of using AWS CLI or ONTAP CLI to manage the cluster. Users with storage administrator background are likely to prefer the latter. In my professional service experience, I have taken some iterations to come to best practice to use the right tool to interact with ONTAP resources. In a nut shell, it depends on the level of resource that we are interacting with. I categorize those resources into two classes:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Category&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Example&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Identification&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS-level resource&lt;/td&gt;&lt;td&gt;FSx ONTAP file system, Storage Virtual Machine&lt;/td&gt;&lt;td&gt;These resources are from ONTAP but they are identified as AWS resources (with ARN). They are also exposed to AWS SDK and can be managed by AWS CLI.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ONTAP-native resource&lt;/td&gt;&lt;td&gt;Volume, Snapshot policy, Schedule, Snapmirror relationship, Vserver&lt;/td&gt;&lt;td&gt;These resources come from ONTAP and can only be managed using ONTAP CLI. The AWS CLI cannot manage &lt;sub&gt;these&lt;/sub&gt; resources simply because they are not exposed to AWS SDK. Some type of resource such as Volume, may be managed by AWS CLI with very limited options. So we still prefer ONTAP CLI to manage resources.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The AWS CLI (v 2.2.37) has very limited options when creating volumes. For example, the create-volume &lt;a href="https://docs.aws.amazon.com/cli/latest/reference/fsx/create-volume.html"&gt;documentation&lt;/a&gt; states that the OntapVolumeType section of output can display types of RW, DP (data protection), or LS. However, it doesn’t allow users to create a volume other than the default RW type. When we configure SnapMirror destination, we need a volume of DP type. We had to use ONTAP CLI to achieve that.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Our practice works out to be: use AWS CLI to create a file system and storage virtual machine. Then we use ONTAP CLI to create everything else. Even though AWS CLI intend to support volume creation, we prefer ONTAP CLI for full functionality support, and alignment with the ONTAP &lt;a href="https://docs.netapp.com/ontap-9/index.jsp"&gt;literature&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-administration-tasks"&gt;Administration Tasks&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You should configure most of the administrative tasks with ONTAP CLI. We use the &lt;a href="https://docs.netapp.com/ontap-9/index.jsp"&gt;documentation&lt;/a&gt; by ONTAP as reference. For example, when a volume runs out of Inode, the AWS CLI reports that there is no space left. We need to increase the inode limit and this is, again, not something that AWS CLI can manage.&amp;nbsp; We’d have to use the volume modify command from ONTAP CLI.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-nfs-version"&gt;NFS version&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The AWS document states that FSx ONTAP supports NFSv3.0, v4.0 and v4.1. However, FSx ONTAP is currently backed by NetApp ONTAP 9.10.0, which partially supports &lt;a href="https://docs.netapp.com/ontap-9/index.jsp?topic=%2Fcom.netapp.doc.cdot-famg-nfs%2FGUID-E0A4007F-B776-4A19-A6F1-A0C0EAA7B03A.html"&gt;NFSv4.2&lt;/a&gt;, with basic protocol and Labled NFS feature. NetApp’s ONTAP &lt;a href="https://www.netapp.com/media/10720-tr-4067.pdf"&gt;Best Practices and Implementation Guide&lt;/a&gt; suggests a method for clients to mount as NFSv4.2. In the POC we mount as NFS v4.2 in all of our testings.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-clarity-of-terminology"&gt;Clarity of terminology&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The ONTAP storage system has been around for a while and many of its concepts are well known in the storage community. For example, A “Snapshot copy” is a read-only, point-in-time image of a volume. (ref: ONTAP 9 &lt;a href="https://docs.netapp.com/ontap-9/index.jsp"&gt;documentation&lt;/a&gt; -&amp;gt; ONTAP concepts -&amp;gt; Replication -&amp;gt; Snapshot copies) This concept becomes “Snapshot” in AWS &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/snapshots-ontap.html"&gt;literature&lt;/a&gt;.&amp;nbsp; It took us some research to come to realize that “Snapshot” in AWS document, essentially maps to “Snapshot copy” in ONTAP literature.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This creates confusion, because we use ONTAP documentation for operation guidance because we can’t get enough help from AWS documentation. The terminology in AWS documentation should align with ONTAP.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The other example is the difference between “&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/using-backups.html"&gt;backup&lt;/a&gt;” and “&lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/snapshots-ontap.html"&gt;snapshot&lt;/a&gt;” in AWS documentation. It is my understanding that they both use the same underlying Snapshot technology on the ONTAP side. I’m not exactly sure what their difference is.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ONTAP can create a Snapshot copy nearly instantaneously. However, when taking a snapshot using web console in AWS, it takes up to 10 minutes to update the status. This is confusing because it creates a perception that it takes 10 minutes to complete snapshot.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-cross-region-replication"&gt;Cross Region Replication&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a document &lt;a href="https://docs.aws.amazon.com/fsx/latest/ONTAPGuide/scheduled-replication.html"&gt;page&lt;/a&gt; on AWS about using SnapMirror at a very high level. It points to two documents: using NetApp Cloud Manager and ONTAP CLI.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The former is not a viable option as we started natively on FSx ONTAP and do not have NetApp Cloud Manager. As to the latter, we managed to configure cross-region replication with ONTAP CLI following the document and identified some gaps in the documentation. Specifically, it would be helpful if AWS documentation calls out that:&lt;/p&gt;&#10;&lt;ol class="wp-block-list" type="1"&gt;&#10;&lt;li&gt;inter-cluster network connectivity is a prerequisite (e.g. via VPC peering, transit gateway)&lt;/li&gt;&#10;&lt;li&gt;Port 10000, 11104-11105 must be added to &lt;a href="https://kb.netapp.com/Advice_and_Troubleshooting/Data_Protection_and_Security/SnapMirror/What_are_the_firewall_requirements_for_intercluster_SnapMirror_with_ONTAP_9"&gt;security group&lt;/a&gt; for inter cluster communication.&lt;/li&gt;&#10;&lt;li&gt;The ONTAP CLI command to validate connectivity between clusters (using the ping command from ONTAP CLI).&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With AWS CLI alone it is not possible to configure cross-region replication.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Final words&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As someone who lived with enterprise storage for more than a decade, I&amp;#8217;m glad to see that cloud vendors brings enterprise storage into their data centre, acknowledging that consumer grade file storage are just insufficient for heavy storage use cases such as medical imaging. FSx ONTAP seems to be in early maturity level. However, since AWS exposes ONTAP CLI access to users, ONTAP professionals are able to leverage its full potential.&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/04/knative-introduction/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Eventing Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/05/hosting-database-on-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Hosting database on Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>File storage vs object storage in the cloud</title><link>https://static.digihunch.com/2021/09/file-storage-vs-object-storage/</link><pubDate>Thu, 23 Sep 2021 22:54:00 -0400</pubDate><guid>https://static.digihunch.com/2021/09/file-storage-vs-object-storage/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-file-obj-storage.webp" alt="Featured image of post File storage vs object storage in the cloud" /&gt;&lt;p class="wp-block-paragraph"&gt;File storage (e.g. NFS) used to be prevalent until object storage comes in for competition.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-the-competition"&gt;The competition&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Traditionally, enterprise storage product lines are built around three capabilities, as listed in this table below:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-black-color has-white-background-color has-text-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Capability&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Typical Implementation&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Data served&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T1 &amp;#8211; &lt;br&gt;Block stroage&lt;/td&gt;&lt;td&gt;DAS (e.g. SAS cable) or SAN (Fibre Cable for FCP protocol, or Ethernet for iSCSI protocol)&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Mission critical data that are extremely sensitive to latency (e.g. database). Client has block-level access.&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T2 &amp;#8211; &lt;br&gt;File storage&lt;/td&gt;&lt;td&gt;NAS (connect via CIFS or NFS protocols). Storage arrays are typically a mix of HDD and SSD. Storage servers are usually deployed in the same location over low latency network. DR location is usually in the same region.&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Hot data. Multiple client access at file level. The size of each data request varies from small to medium (e.g. text document)&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;T3 &amp;#8211; &lt;br&gt;Object storage&lt;/td&gt;&lt;td&gt;Hardware agnostic, connect via layer-7 protocol (e.g. S3). Storage backend can be either on premise, or in the cloud, over WAN connection.&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;Warm and code data. Multiple client access at object level. Traditionally for backup but use cases are expanding. The size of each data request varies significantly, from small to very large (e.g. media content).&lt;/td&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&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;In the last couple decades, leading players for T2 have been enterprise storage vendors. They each have developed their secret sauces to tackle the challenges. For example, EMC has OneFS, a parallel distributed file system as the foundation of &lt;a href="https://static.digihunch.com/2020/07/emc-productlines/"&gt;PowerScale&lt;/a&gt; (formerly Isilon) product line. NetApp develops ONTAP, featuring proprietary techniques for storage efficiency (deduplication, compaction and compression).&amp;nbsp;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The leading players in T3 are mostly &lt;a href="https://static.digihunch.com/2020/08/cloud-storage-overview/"&gt;public cloud&lt;/a&gt; provider, such as Amazon&amp;#8217;s S3. They might work with enterprise storage vendor behind the scene. But the T3 services appear to the end users as provided by the public cloud. Originally, the use case for T3 was archive only for its virtually unlimited capacity. This is not entirely true today. With the drastic improvement in modern network infrastructure, T3 can also brings satisfactory performance to serve hot data. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A competition between T2 and T3 arises. After all, both offer storage service over Ethernet, and both support multiple clients. Today when developers architect the storage layer of their applications, they need to weigh between supporting T2 and T3. Since &lt;a href="https://static.digihunch.com/2020/07/nfs-network-file-system-and-rpc-remote-procedure-call/"&gt;NFS&lt;/a&gt; is the typical protocol for T2 storage (sorry Windows guys) and S3 is typical T3 storage. This competition essentially boils down to NFS versus S3.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For many, the fancy S3 is a no-brainer. While I have suffered from many NFS drawbacks, and there&amp;#8217;s even a whole &lt;a href="https://www.kernel.org/doc/ols/2006/ols2006v2-pages-59-72.pdf"&gt;article&lt;/a&gt; by Linux folks about why NFS sucks, is it sentenced to death today? Does it beat S3 in some cases? Do so many organizations still stick to NFS just out of inertia?&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To answer these questions, I examine four aspects to explore the differences between file storage via NFS protocol, and object storage in S3. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-data-request-size"&gt;Data request size&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Storage client can make request by byte range of a file. Therefore, data request size, instead of file size, is what ultimately matters. I pick a few data request sizes (1K, 4K, 16K, 64K, 246K, 1024K and 4096K) in my experiment, and want to see how much network traffic a write operation produces using NFS and using S3.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To emulate request size, I created files at each size (using dd command), and copy the entire file to each backend. In the mean time, I use tcpdump to write out traffic across the wire into capture files. The size of capture file gives me an idea of how much network traffic went through the network interface, which is closely related to latency. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For NFS, I mounted the target with sync option. This requires NFS client to write out to server synchronously on file copy. I&amp;#8217;ve also set the wsize to be 1M. For S3, I simply use the following CLI command to copy file:&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;aws s3 cp 1kb.img s3://digihunch5ffafe32ab0fd40f&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;On the network interface, I use tcpdump to filter traffic through specific TCP port (443 for S3, or 2049 for NFS) and record the size of the capture file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo tcpdump -s0 -pi eth0 dst port &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt; or src port &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt; -w /tmp/4096kb.cap&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The key indicator is the payload size (file size) as a percentage of the capture size. I call it payload ratio. The closer it is to 1, the better. I have the following result from my experiment:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-black-color has-white-background-color has-text-color has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Request&lt;/td&gt;&lt;td&gt;Payload&lt;/td&gt;&lt;td&gt;S3 capture size (byte)&lt;/td&gt;&lt;td&gt;NFS capture size (byte)&lt;/td&gt;&lt;td&gt;S3 payload ratio&lt;/td&gt;&lt;td&gt;NFS payload ratio&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1K&lt;/td&gt;&lt;td&gt;1024&lt;/td&gt;&lt;td&gt;9352&lt;/td&gt;&lt;td&gt;4332&lt;/td&gt;&lt;td&gt;0.11&lt;/td&gt;&lt;td&gt;0.24&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4K&lt;/td&gt;&lt;td&gt;4096&lt;/td&gt;&lt;td&gt;12640&lt;/td&gt;&lt;td&gt;7404&lt;/td&gt;&lt;td&gt;0.32&lt;/td&gt;&lt;td&gt;0.55&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;16K&lt;/td&gt;&lt;td&gt;16384&lt;/td&gt;&lt;td&gt;25969&lt;/td&gt;&lt;td&gt;20472&lt;/td&gt;&lt;td&gt;0.63&lt;/td&gt;&lt;td&gt;0.80&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;64K&lt;/td&gt;&lt;td&gt;65536&lt;/td&gt;&lt;td&gt;79183&lt;/td&gt;&lt;td&gt;69746&lt;/td&gt;&lt;td&gt;0.83&lt;/td&gt;&lt;td&gt;0.94&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;256K&lt;/td&gt;&lt;td&gt;262144&lt;/td&gt;&lt;td&gt;290035&lt;/td&gt;&lt;td&gt;271408&lt;/td&gt;&lt;td&gt;0.90&lt;/td&gt;&lt;td&gt;0.97&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;1024K&lt;/td&gt;&lt;td&gt;1048576&lt;/td&gt;&lt;td&gt;1085366&lt;/td&gt;&lt;td&gt;1074076&lt;/td&gt;&lt;td&gt;0.97&lt;/td&gt;&lt;td&gt;0.98&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;4096K&lt;/td&gt;&lt;td&gt;4194304&lt;/td&gt;&lt;td&gt;4381547&lt;/td&gt;&lt;td&gt;4286910&lt;/td&gt;&lt;td&gt;0.96&lt;/td&gt;&lt;td&gt;0.98&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This result indicates that NFS has a higher ratio in all groups. However, its advantage diminishes as the data request size grows. What it tells us is that if your applications workload issues most request in small chunks of data, such as 1K, 4K, then NFS will require much less traffic over the network, and thus less latency. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This essentially explains the use case of NFS against S3: workload with small data requests.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-client-support"&gt;Client Support&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;NFS is natively supported by Linux operating system kernel. NFS client sits below the virtual file system (VFS) layer, which sits below the system call layer. The NFS client translate system calls into RPC (remote procedure calls). Communication between client and server is completed with RPC, on top of TCP. &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="554" height="314" src="https://static.digihunch.com/wp-content/uploads/2021/10/image.png" alt="" class="wp-image-2732"/&gt;&lt;figcaption class="wp-element-caption"&gt;NFS architecture&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because of the native support, in most cases, developer can treat NFS mounts as if they were local. For performance to be sustainable as file system grows, the directory structure on NFS should follow a certain naming conventions so that files are evenly distributed across directories. The client should also use list operation as sparse as it can because that operation is expensive across the network.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From developer&amp;#8217;s perspective, NFS support is brought in by operating system and does not require much effort. On the other hand, S3 client support is not included by default in the operating system. S3 support requires special library, code changes, and integration effort to manage dependency and library version. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;NFS has an advantage on client supportability. However, as we move applications to containers, and as container storage options mature, we will need an intermediary layer (storage class, storage provisioner, CSI driver, etc), NFS, or in general file storage, does not have this advantage any more.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-client-side-cache"&gt;Client-side Cache&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The NFS support behind VFS layer also means it can leverage the I/O caching mechanism on the client side, that comes with operating system. Client operating system with sufficient memory can take advantage of this mechanism to give it a performance boost. Check out &lt;a href="https://www.ibm.com/docs/en/aix/7.2?topic=performance-nfs-tuning-client"&gt;this&lt;/a&gt; guide for NFS cache tuning.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In comparison, S3 does not have a cache mechanism by itself. Either the application needs to implement its own cache mechanism, or a cache architecture needs to be introduced, such as CloudFront. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-consistency-and-concurrency"&gt;Consistency and concurrency&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A common consistency problem is whether client can read the changes immediately after it writes the file. S3 and NFS make a tie in this round.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;S3 &lt;a href="https://aws.amazon.com/blogs/aws/amazon-s3-update-strong-read-after-write-consistency/"&gt;originally&lt;/a&gt; came with eventual consistency model for read after write since 2006. As of &lt;a href="https://aws.amazon.com/about-aws/whats-new/2020/12/amazon-s3-now-delivers-strong-read-after-write-consistency-automatically-for-all-applications/"&gt;Dec 2020&lt;/a&gt; it introduced strong read-after-write consistency. For more information, refer to the guide &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html#ConsistencyModel"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;NFS has a similar consistency guarantee called close-to-open cache coherency. Any changes made by client are flushed to the server on closing the file, and a cache revalidation occurs when you re-open it. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are more to consider in terms of consistency. For example, multiple clients tries to write the same file/object at the same time. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the S3 side, there is a locking mechanism called &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/object-lock.html"&gt;S3 object lock&lt;/a&gt; at object level (no byte-range lock). Without an object lock, when two PUT requests are simultaneously made to an object, the request with the latest timestamp wins. Refer to the section &lt;em&gt;Concurrent application&lt;/em&gt; on &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html#ConsistencyModel"&gt;this&lt;/a&gt; page.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As far as NFS goes, managing this kind of consistency problem is not in the scope of the standard. Although there are some tinkers. For example, NFS v4 includes a file locking mechanism. Client can choose to lock the entire file, or a byte range within the file. Locking can be mandatory or advisory.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-the-convergence"&gt;The convergence&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;NFS and S3 each has their respective advantage. Enterprise NAS customers have been looking for ways to expand into the cloud for lower storage cost. To combine the advantages of the two, solution providers started to converge file storage and object storage. There are two types of solutions that reflects this trend of convergence. In the first trend, enterprise NAS deployed on premise now have the ability to scale out into the cloud. In the second trend, public cloud just brought enterprise NAS into their product offerings.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-scale-out-nas"&gt;Scale-out NAS&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;NAS is traditionally expensive to scale because it requires physical storage media. The idea of scale-out NAS allows NAS to connect to object storage in the public cloud, making it a hybrid architecture. This essentially makes T3 storage as a backend of T2 and it can be implemented with a virtual storage appliance (VSA). The VSA translate file system activities into API calls for object storage operations. One example is AWS &lt;a href="https://aws.amazon.com/storagegateway/?whats-new-cards.sort-by=item.additionalFields.postDateTime&amp;amp;whats-new-cards.sort-order=desc"&gt;storage gateway&lt;/a&gt;. EMC has a similar appliance called ECS and this white paper explains how it proxies file system calls and interact with object backends. NetApp, a vested enterprise NAS provider, also has a counterpart called Cloud Volumes ONTAP (CVO). It works well with NetApp on-premise deployment, but the architecture is similar. &lt;a href="https://cloud.netapp.com/blog/aws-cvo-blg-aws-storage-gateway-vs.-cloud-volumes-ontap"&gt;Here&lt;/a&gt;&amp;#8216;s NetApp&amp;#8217;s take on how CVO is different than AWS Storage Gateway.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the scale-out NAS architecture, the public cloud acts merely as extension to on-premise storage solution, to provide capacity. The NAS on premise serves the storage workload primarily.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-cloud-hosted-nas"&gt;Cloud hosted NAS&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For applications hosted in public cloud, it makes sense for public cloud provider to operate enterprise NAS storage as a service. The underlying storage technology is provided by storage vendor. It is just installed in the data centre managed by the public cloud vendor, instead of customer&amp;#8217;s own data centre. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One example is &lt;a href="https://azure.microsoft.com/en-us/services/netapp/"&gt;Azure NetApp Files&lt;/a&gt; (ANF). ANF is fully managed services, presented to users as storage volumes. The underlying storage technology is NetApp ONTAP. Because it is offered as a fully managed service, the customers are not able to manage the fine details of the storage, as they could with an ONTAP cluster on premise. This takes a lot of flexibility away from the user.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://aws.amazon.com/fsx/netapp-ontap/"&gt;FSx ONTAP&lt;/a&gt; is a managed NetApp storage service by AWS, launched in September 2021. The NetApp arrays are installed in AWS data centre, ready for users to provision from AWS console, or using CLI. The Terraform provider support is not available as of yet. Unlike ANF, FSx ONTAP exposes the ONTAP CLI to users, allowing for advanced storage managed by storage gurus. They can use ONTAP CLI commands to configure custom policy for Snapshot, setup SnapMirror replication, and so forth.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Likewise, PowerScale landed on GCP as public cloud partner to launch &lt;a href="https://cloud.google.com/vmware-engine/docs/vmware-ecosystem/howto-cloud-dell-powerscale#:~:text=Dell%20PowerScale%20on%20Google%20Cloud,workload%20virtual%20machines%20(VMs)."&gt;Dell Cloud PowerScale for Google Cloud&lt;/a&gt; in 2020. However, it seems to require a purchase agreement before APIs are enabled.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Object storage has a great momentum and some sees that as a replacement of file storage in the long run. However file storage has its advantages for small data requests, OS-level cache support, and built-in POSIX compatibility. It will continue to be an option for customers with specific workload. Customer stickiness to file storage is so firm, that public cloud providers now install them in their data centres. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From competition to collaboration, it will be interesting to watch what happens next for enterprise storage.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Follow-up Reading&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Tom Lyon&amp;#8217;s presentation on &lt;a href="https://blocksandfiles.com/2024/06/17/eminent-sun-alumnus-says-nfs-must-die/"&gt;why NFS must die&lt;/a&gt;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Local multi-node cluster – Minikube, MicroK8s and KinD&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/10/intro-to-plg-stack-prometheus-loki-and-grafana/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to PLG stack -Prometheus, Loki and Grafana&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>