<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Data Replication on Digi Hunch</title><link>https://static.digihunch.com/tag/data-replication/</link><description>Recent content in Data Replication on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Tue, 08 Apr 2025 14:28:12 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/data-replication/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes Storage on Azure 3 of 3 – Ceph by Rook</title><link>https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/</link><pubDate>Fri, 26 Aug 2022 19:43:00 -0400</pubDate><guid>https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-storage-3.webp" alt="Featured image of post Kubernetes Storage on Azure 3 of 3 – Ceph by Rook" /&gt;&lt;p class="wp-block-paragraph"&gt;In the last two posts, I covered the native storage options on Azure Kubernetes Service, as well as Portworx as an example of a proprietary Software Defined Storage (SDS) solution. There are also a number of open-source alternative SDS solutions. Ceph has nearly a decade of history from prior to containerization, and is the most widely adopted storage platform. In this post, we continue to explore Ceph as an open-source storage solution on Azure Kubernetes. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-ceph-by-rook"&gt;Ceph by Rook&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ceph is an open-source SDS platform for distributed storage on a cluster and provides object, block and file storage. Installation of Ceph SDS can be complex, especially on Kubernetes platform. &lt;a href="https://rook.io/"&gt;Rook&lt;/a&gt; is a graduated CNCF project to orchestrate storage platform. Rook by itself is not SDS and it supports:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/Getting-Started/intro/"&gt;Ceph&lt;/a&gt;: configure a Ceph cluster. Think of this as the equivalent of &lt;a href="https://docs.ceph.com/en/quincy/cephadm/"&gt;cephadm&lt;/a&gt; on Kubernetes platform.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/nfs/v1.7/"&gt;NFS&lt;/a&gt;: configure an NFS server. Think of this as the equivalent of nfsd daemon on Kubernetes platform.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/cassandra/v1.7/"&gt;Cassandra&lt;/a&gt;: an operator to configure a Cassandra database cluster. It is now &lt;strong&gt;deprecated&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We play with Rook Ceph. I also refer to it as Ceph by Rook. The contribution of Rook project is it simplifies the installation as a matter of declaring custom resources using CRDs. Here are some high-level CRDs to know:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/ceph-cluster-crd/"&gt;CephCluster&lt;/a&gt;: creates a Ceph storage cluster&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Block-Storage/ceph-block-pool-crd/"&gt;CephBlockPool&lt;/a&gt;: represents a block pool&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Shared-Filesystem/ceph-filesystem-crd/"&gt;CephFilesystem&lt;/a&gt;: represents a file system&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Object-Storage/ceph-object-store-crd/#example"&gt;CephObjectStore&lt;/a&gt;: represents an object store&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/ceph-nfs-crd/"&gt;CephNFS&lt;/a&gt;: spins up a NFS Ganesha server to export NFS shares of a CephFilesystem or CephObjectStore.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As with typical Kubernetes resources in controller pattern, Ceph by Rook needs an operator along with custom resources. We can use YAML manifest for both of them, and the manifests are usually very tediously long. We can also use Helm to install both of them, by providing a value file. Now we will install Ceph on AKS.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Install Ceph Operator on AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps are influenced by two relevant posts (&lt;a href="https://carlos.mendible.com/2021/10/23/aks-high-available-storage-with-rook-and-ceph/"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/evillgenius75/rook-aks"&gt;here&lt;/a&gt;). However, I&amp;#8217;ve incorporated the cluster configuration in the &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;Azure directory of the cloudkube project&lt;/a&gt;, a modular Terraform template to configure AKS cluster and facilitate storage configuration. The node group and instance sizes are selected to be just enough to run a ceph POC cluster with minimum cost. One of the node groups is tainted with storage-node, as if the following command were run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl taint nodes my-node-pool-node-name storage-node&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true:NoSchedule&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;You will only need to taint the nodes with the command above if you choose not to use the cloudkube template. The taint ensures that only Pods with corresponding toleration and effect can be scheduled to those nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Helm to install Rook Operator. We need a value file (e.g. rook-ceph-operator-values.yaml) with content as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//github.com/rook/rook/blob/master/Documentation/Helm-Charts/operator-chart.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;crds&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;csi&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;provisionerTolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effect&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NoSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pluginTolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effect&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NoSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;agent&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AKS&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//rook.github.io/docs/rook/v1.7/flexvolume.html#azure-aks&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;flexVolumeDirPath&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/etc/kubernetes/volumeplugins&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we install the operator with Helm:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install rook-ceph-operator rook-ceph --namespace rook-ceph --create-namespace --version v1.9.6 --repo https://charts.rook.io/release/ --values rook-ceph-operator-values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n rook-ceph get po -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;rook-ceph-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After installing the operator, we check the Pod status to make sure it is running. Then we can install the actual Ceph Cluster in one of the two ways. We can declare a CephClusterCRD ourself, or we can use Helm again to declare the CRD. Helm Chart gives us a lot of useful default values and saves us from editing a large body of YAML manifest.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Install Ceph CR on AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Helm to install CephCluster CRD. We create a value file (e.g. rook-ceph-cluster-values.yaml) with content as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//github.com/rook/rook/blob/master/Documentation/Helm-Charts/ceph-cluster-chart.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;operatorNamespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;toolbox&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;cephObjectStores&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [] &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephObjectStore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;Setting&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;disables&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;it&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cephBlockPools&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephBlockPool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;also&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cephFileSystems&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephFileSystem&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;also&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;cephClusterSpec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;mon&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;count&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeClaimTemplate&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;managed&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;premium&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;limits&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;1Gi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;100m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500Mi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;dashboard&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassDeviceSets&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;The&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;number&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;device&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;count&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IMPORTANT&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;If&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;volumes&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;specified&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;are&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;portable&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;across&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;nodes&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;needs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;For&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;provisioner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;should&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;portable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Since&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;could&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;up&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;any&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;an&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effort&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;needs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;made&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;across&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;nodes&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;much&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;possible&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;Unfortunately&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pod&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;anti&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;affinity&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;breaks&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;down&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;soon&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;have&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;more&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;than&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;one&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSD&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;per&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;The&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;topology&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;constraints&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;give&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;us&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;an&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;even&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;K8s&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.18&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;or&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newer&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;placement&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxSkew&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologyKey&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hostname&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ScheduleAnyway&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labelSelector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;preparePlacement&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;nodeAffinity&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;nodeSelectorTerms&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;agentpool&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storagenp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxSkew&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IMPORTANT&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;If&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;don&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;t&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;have&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;zone&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;change&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;another&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;such&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hostname&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologyKey&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;topology&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;zone&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DoNotSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labelSelector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osd&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;prepare&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;limits&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;4Gi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;2Gi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;managed&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;premium&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeMode&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Block&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;accessModes&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReadWriteOnce&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;During the cluster provisioning, there will be a number of preparing Pods. We want those Pods to run on nodes with label agentpool=storagenp. In real life, we need to orchestrate where to run each workload, by restricting the nodes to schedule certain types of workload.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can install the cluster using Helm:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install rook-ceph-cluster rook-ceph-cluster --namespace rook-ceph --create-namespace --version v1.9.6 --repo https://charts.rook.io/release/ --values rook-ceph-cluster-values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After running the Helm install, it may take as long as 15 minutes for all resources to settle. Watch the Pod status in rook-ceph namespace. At the end, make sure that the cluster is created successfully:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get CephCluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME DATADIRHOSTPATH MONCOUNT AGE PHASE MESSAGE HEALTH EXTERNAL&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rook-ceph /var/lib/rook &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; 15m Ready Cluster created successfully HEALTH_OK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get cephBlockPools&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME PHASE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ceph-blockpool Ready&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get cephFileSystems&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME ACTIVEMDS AGE PHASE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ceph-filesystem &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 20m Ready&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In my case it took 15 minutes before the cluster comes up as created successfully. You should notice that two storage classes were also created as a part of the install. It however did not create a storage class or CRD for object storage, because we explicitly disabled it in the Helm value file by setting cephObjectStores value to null.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Dashboard&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We enabled dashboard. To configure the dashboard view properly, we would need an ingress. For a quick view here, we can play port forwarding tricks. First we fetch the admin password for use in the next step. Then expose the dashboard to the bastion host:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.data.password}&amp;#39;&lt;/span&gt; | base64 -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n rook-ceph port-forward svc/rook-ceph-mgr-dashboard 8443:8443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Since I don&amp;#8217;t have UI on the bastion host, I use the port forwarding trick again from my own MacBook. Start a new terminal and SSH to the bastion host with port-forwarding switch:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh -L 8443:localhost:8443 kubeadmin@20.116.132.8&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above suppose the public IP of the bastion host is 20.116.132.8. Then from my MacBook I can browse to localhost:8443 (with Safari browser which gives me the option to bypass certificate error). At the web portal, provide username (admin) and password (as retrieved above):&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1795" height="1026" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-16.png" alt="" class="wp-image-6037"/&gt;&lt;figcaption class="wp-element-caption"&gt;Ceph console for Kubernetes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the dashboard, we can also use &lt;a href="https://docs.ceph.com/en/quincy/man/8/ceph/"&gt;ceph admin tool&lt;/a&gt; from a &lt;a href="https://github.com/rook/rook/blob/master/deploy/examples/toolbox.yaml"&gt;toolbox&lt;/a&gt; pod, following &lt;a href="https://rook.io/docs/rook/v1.9/ceph-toolbox.html"&gt;this&lt;/a&gt; instruction. For monitoring, Ceph by Rook can expose metrics for &lt;a href="https://www.rook.io/docs/rook/v1.9/Storage-Configuration/Monitoring/ceph-monitoring/"&gt;Prometheus&lt;/a&gt; to scrape.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Performance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With default ceph configuration on AKS, I ran quick performance test using kube-str . The result is as follows:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;read_iops&lt;/td&gt;&lt;td&gt;write_iops&lt;/td&gt;&lt;td&gt;read_bw&lt;/td&gt;&lt;td&gt;write_bw&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ceph-block&lt;/td&gt;&lt;td&gt;IOPS=464.507294 BW(KiB/s)=1874&lt;/td&gt;&lt;td&gt;IOPS=243.296143 BW(KiB/s)=989&lt;/td&gt;&lt;td&gt;IOPS=509.928162 BW(KiB/s)=65797&lt;/td&gt;&lt;td&gt;IOPS=248.530762 BW(KiB/s)=32338&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ceph-filesystem&lt;/td&gt;&lt;td&gt;IOPS=438.701324 BW(KiB/s)=1770&lt;/td&gt;&lt;td&gt;IOPS=226.270660 BW(KiB/s)=920&lt;/td&gt;&lt;td&gt;IOPS=405.936340 BW(KiB/s)=52456&lt;/td&gt;&lt;td&gt;IOPS=208.869293 BW(KiB/s)=27229&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The metrics reflects performance under default configuration. It should not be considered as the best performance that Ceph can deliver on Azure Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I discussed three storage options for Azure Kubernetes but the idea applies to other Kubernetes platform hosted on a CSP. The &lt;a href="https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;native storage&lt;/a&gt; has significant limitation. NFS has latency. Block storage does not address high availability at the storage layer. Portworx and LINSTOR fill that gap as a commercial solution. Ceph is based on Object storage.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/08/kubernetes-storage-on-azure-2-of-3-portworx/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 2 of 3 – Portworx&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/09/minio-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;MinIO for S3-compatible Object Storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Intro to Ceph storage</title><link>https://static.digihunch.com/2022/07/intro-to-ceph-storage/</link><pubDate>Thu, 21 Jul 2022 18:55:00 -0400</pubDate><guid>https://static.digihunch.com/2022/07/intro-to-ceph-storage/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-ceph.webp" alt="Featured image of post Intro to Ceph storage" /&gt;&lt;p class="wp-block-paragraph"&gt;Ceph is a unified, distributed storage system designed for excellent performance, reliability and scalability. In this post, I will introduce Ceph and explain how it stands out from traditional enterprise storage technology. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-software-defined-storage"&gt;Software defined storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the realm of enterprise storage, I discussed &lt;a href="https://static.digihunch.com/2020/07/emc-productlines/"&gt;PowerScale (Isilon)&lt;/a&gt; from Dell EMC, and touched on &lt;a href="https://static.digihunch.com/2021/09/file-storage-vs-object-storage/"&gt;ONTAP&lt;/a&gt; by NetApp as an alternative. These solutions usually include both enterprise grade hardware, and the software layer that manages those expensive hardware. As the competition with cloud storage arises, those vendors start to decouple the software layer from the hardware to sell them separately. As a result, clients have the options to use commodity hardware. On the other hand, the software layer is built to be more accommodative to different hardware options. Eventually, the software layer evolves into Software Defined Storage (SDS) with the purpose of supporting cheaper storage hardware.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This table shows the full solution offering and SDS offering from NetApp and Dell EMC:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-black-color has-cyan-bluish-gray-background-color has-text-color has-background has-fixed-layout"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Full solution offering&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;SDS offering&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;NetApp&lt;/td&gt;&lt;td&gt;&lt;a href="https://docs.netapp.com/ontap-9/index.jsp"&gt;ONTAP&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://docs.netapp.com/us-en/ontap-select/index.html"&gt;ONTAP Select&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EMC&lt;/td&gt;&lt;td&gt;PowerScale&lt;/td&gt;&lt;td&gt;PowerFlex&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is not easy to make a proprietary SDS appliance support commodity hardware. For example, PowerFlex currently supports (and bundles with) DELL&amp;#8217;s commodity hardware only. It is most likely an involuntary move. Then, why would these commercial providers even be motivated to support a broader range of hardware by moving to SDS? It is because they face fierce competition from open-source SDS technologies, which were born to support commodity hardware. In this family of technologies, Ceph is a rising star. This family also includes other technologies such as Gluster and HDFS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the performance of a storage based on SDS still has to do with the underlying hardware. Therefore, comparing Ceph storage with PowerScale is apple to orange, without identical storage hardware. Now that we decoupled SDS and hardware, let&amp;#8217;s take a look at two important aspects of SDS: the distributed technology to manage hardware, and the interface it provides to storage clients.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Distributed storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The reason to use an SDS layer to manage hardware in a distributed architecture is for better scalability and high availability. The soul of this SDS layer is the ability to manage distributed system. However, a distributed storage introduces problems of its own, such as coordinating consistency. Different storage technologies have their own way to tackle these problems. For example, with PowerScale, OneFS has its own Group Management Protocol. Ceph uses CRUSH for data distribution. GlusterFS uses DHT(Distributed Hash Table) Translator. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Storage architects usually do not need to know these technologies in detail. It is not the intention of this post to cover the details of any distributed technology in any of the storage options above. However, storage architects needs to know supported API very well.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Access API&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The supported access API of a storage system determines its compatibility with client systems. One good example is NFS for file storage, which defines the protocol for file share without defining the underlying implementation. Most GNU/Linux distributions come with nfsd (NFS server) which exports directories on XFS or ext4 FS as a file share with NFS protocol. In order to transfer data over network, NFS uses RPC, a request-response protocol. With object storage, S3 is a widespread protocol. Below is a list of storage implementations and their supported access API:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Ceph supports librados, S3, Swift and FUSE&lt;/li&gt;&#10;&lt;li&gt;GlusterFS supports SMB, NFS, FUSE,&lt;/li&gt;&#10;&lt;li&gt;PowerScale supports NFS, SMB/CIFS, HDFS, Object, POSIX&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;CephFS is distributed file system built on top of Ceph RADOS. It is also a client-server architecture. A Ceph Client, via &lt;a href="https://docs.ceph.com/en/latest/rados/api/librados/"&gt;librados&lt;/a&gt;, interacts directly with OSDs to store and retrieve data. In order to interact with OSDs, the client app must invoke librados and connect to a Ceph Monitor. For compatibility, CephFS namespaces can be export over NFS protocol using &lt;a href="https://docs.ceph.com/en/latest/rados/api/librados-intro/"&gt;NFS-Ganesha&lt;/a&gt; NFS server.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Ceph Architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ceph is a high-performance, distributed storage platform. It provides object storage, block storage and distributed file system, all backed by a single, reliable storage cluster running on commodity server hardware. A Ceph Storage Cluster consists of Ceph Nodes on a network. A Ceph Storage cluster requires at least one Ceph monitor (ceph-mon), Ceph Manager (ceph-mgr) and Ceph OSDs (ceph-osd). For file system clients, it also requires Ceph Metadata Server (MDS, ceph-mds) to allow user to execute basic commands on POSIX file system (e.g. ls, find)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Under the hood, Ceph stores data as objects within logical storage pools. Using the&amp;nbsp;&lt;a href="https://docs.ceph.com/en/latest/glossary/#term-CRUSH"&gt;CRUSH&lt;/a&gt;&amp;nbsp;algorithm, Ceph calculates which placement group (PG) should contain the object, and which OSD should store the placement group. The CRUSH algorithm enables the Ceph Storage Cluster to scale, rebalance, and recover dynamically.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="689" height="483" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-11.png" alt="" class="wp-image-5585"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ceph is based on RADOS (reliable autonomic distributed object store), a self-healing system that distributes and replicates data across nodes. It then layers CephFS (a distributed file system), block storage service (RADOS Block Device or RBD), and s3-compatible object storage (RADOS Gateway or RGW) on top of RADOS. For a better description, refer to &lt;a href="https://ubuntu.com/ceph/what-is-ceph"&gt;this&lt;/a&gt; page. The chart above shows how Ceph interacts with different kinds of client. For CephFS, the client can interact with the file system via metadata daemon, as illustrated below. This diagram looks similar to the diagram for &lt;a href="https://static.digihunch.com/2020/07/nfs-network-file-system-and-rpc-remote-procedure-call/"&gt;NFS&lt;/a&gt;.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="883" height="706" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-12.png" alt="" class="wp-image-5586"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a RADOS cluster, each server runs some daemons (i.e. OSD, MON or MDS). &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="736" height="317" src="https://static.digihunch.com/wp-content/uploads/2022/09/ceph.png" alt="" class="wp-image-7275"/&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When an I/O request occurs, it needs to be mapped to the specific OSD that keeps the storage units. Here is an illustration of the mapping:&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="603" height="338" src="https://static.digihunch.com/wp-content/uploads/2022/09/io-path.png" alt="" class="wp-image-7277"/&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As typically observed in distributed system, there is quite some communication overhead to serve a file.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Ceph Cluster Installation&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Installing a VM-based Ceph cluster is no trivial effort and there are several methods. The recommended method is &lt;a href="https://docs.ceph.com/en/latest/cephadm/#cephadm"&gt;Cephadm&lt;/a&gt;. &lt;a href="https://kifarunix.com/install-and-setup-ceph-storage-cluster-on-ubuntu/"&gt;Here&lt;/a&gt; is a good instruction, where you will notice a lot of steps on each nodes, such as configuring NTP, installing docker, configuring hostname, Linux user and SSH, etc. You may also check &lt;a href="https://www.youtube.com/watch?v=LxDQyFWDNHI"&gt;this&lt;/a&gt; video for how involving it is. Red Hat adopts &lt;a href="https://www.redhat.com/en/technologies/storage/ceph"&gt;Ceph&lt;/a&gt; project as a product and has an &lt;a href="https://access.redhat.com/documentation/en-us/red_hat_ceph_storage/5/html/installation_guide/red-hat-ceph-storage_install"&gt;installation guide&lt;/a&gt; on its documentation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Previously, there was a legacy tool &lt;a href="https://docs.ceph.com/projects/ceph-ansible/en/latest/"&gt;ceph-ansible&lt;/a&gt; to help administrators with server configuration. It is similar to the way kubespray helps administrators configure Kubernetes cluster. However, the &lt;a href="https://docs.ceph.com/en/quincy/install/#other-methods"&gt;document&lt;/a&gt; suggests that ceph-ansible is not integrated with new orchestrator APIs and therefore is not a viable option anymore. Also I did not find a way to install a single-node ceph cluster just for a quick demo. It involves tweaking the &lt;a href="https://docs.ceph.com/en/quincy/rados/operations/crush-map/"&gt;CRUSH&lt;/a&gt; map configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we deploy Ceph on Kubernetes for Kubernetes workload, we use &lt;a href="https://rook.io/docs/rook/v1.9/Getting-Started/intro/"&gt;Rook&lt;/a&gt;, an orchestrator running on Kubernetes, to integrate storage to a cluster.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Cloud Native Storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Moving to cloud native storage, instead of presenting storage to operating system, we need to configure storage classes for Pods to use persistent volumes dynamically, using storage provisioners. Ceph also shows good presence in cloud native storage ecosystem. In a self-managed Kubernetes cluster, Ceph gives us the capability to configure storage classes to access connected storage. In public cloud, Ceph allows us to configure storage classes connecting to disks attached to the Nodes, an alternative to the cloud vendor provided native storage classes with high availability across availability zones. This layer enables the organization to normalize how their application connects to persistent volumes, a capability particularly helpful in the multi-cloud strategy of the cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Rook is a CNCF project to orchestrate storage system on Kubernetes. It automates storage administrative tasks such as deployment, bootstrapping, configuring, provisioning and monitoring, using declarative templates. It supports Ceph and a number of other storage backends such as Cassandra, NFS, MinIO. &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/kick-the-tires-on-argocd/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kick the tires on ArgoCD&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 1 of 3 – built-in storage and NFS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Etcd – the key-value store for Kubernetes</title><link>https://static.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/</link><pubDate>Tue, 14 Jun 2022 00:10:00 -0400</pubDate><guid>https://static.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-etcd.webp" alt="Featured image of post Etcd – the key-value store for Kubernetes" /&gt;&lt;h2 class="wp-block-heading"&gt;Etcd in Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes &lt;a href="https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/"&gt;architecture&lt;/a&gt;, &lt;a href="https://etcd.io/"&gt;etcd&lt;/a&gt; is the data store. It stores the desired state of Kubernetes object. API server is the only client that connects to etcd (via &lt;a href="https://grpc.io/"&gt;gRPC&lt;/a&gt; protocol). Cluster builder specifies the endpoint of etcd as a parameter to the kube-api-server process. Other Kubernetes components, whether in the control plane or from the nodes, connect to API server. API server translates their request into etcd query, and then translates etcd query result into what its clients ask for. For this reason, communication with etcd accounts for a lot of network traffic in a Kubernetes cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The etcd store is a CNCF project for &amp;#8220;a distributed, reliable key-value store for critical data in a distributed system&amp;#8221;, developed by CoreOS team. So it is essentially a distributed key-value store for any distributed application. If an application runs on Kubernetes, it can leverage etcd store, by keeping their configurations in ConfigMap and Secret objects. One key feature is to watch for specific keys or directories for changes, and react to the changes. Voila! This is the underlying mechanism for &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/"&gt;controller&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Kubernetes cluster may have stacked etcd deployment or connect to an external etcd store.&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="848" height="560" src="https://static.digihunch.com/wp-content/uploads/2022/05/stacked.png" alt="" class="wp-image-5250"/&gt;&lt;figcaption class="wp-element-caption"&gt;stacked etcd architecture&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="856" height="601" src="https://static.digihunch.com/wp-content/uploads/2022/05/external.png" alt="" class="wp-image-5251"/&gt;&lt;figcaption class="wp-element-caption"&gt;external etcd architecture&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In managed Kubernetes services such as EKS in AWS and AKS in Azure, users usually do not directly access etcd store. However, it is still a very important component to understand. Its use case includes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Configuration sharing&lt;/li&gt;&#10;&lt;li&gt;Service discovery&lt;/li&gt;&#10;&lt;li&gt;Consistency&lt;/li&gt;&#10;&lt;li&gt;Watching mechanism&lt;/li&gt;&#10;&lt;li&gt;Expiry and extension of key &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The consistency use case is based on Raft protocol for distributed consensus.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Raft protocol&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I am not an expert in distributed consensus protocols and nor do I intent to cover it in depth. At a high level, I have heard of three of them so far:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Etcd uses Raft protocol&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://static.digihunch.com/2020/08/zookeeper/"&gt;Zookeeper&lt;/a&gt; uses ZAB protocol&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://static.digihunch.com/2018/03/cassandra-architecture-summary/"&gt;Cassandra&lt;/a&gt; uses paxos protocol&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.alibabacloud.com/blog/a-brief-analysis-of-consensus-protocol-from-logical-clock-to-raft_594675"&gt;Here&lt;/a&gt; is a good intro to the three protocols. Instead of getting into the fine details, I would like to discuss why we need such a consensus protocol (or consensus mechanism) in distributed systems, which are also decentralized systems.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="686" src="https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-1024x686.webp" alt="" class="wp-image-13111" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-1024x686.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-300x201.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-768x514.webp 768w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-410x275.webp 410w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology.webp 1138w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Centralized, Decentralized, Distributed systems&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The reason a distributed system needs consensus protocol, is that a distributed system lacks a single source of truth as centralized systems do. Different parts of the distributed system may receive different signals but they must come to agreement of a single plan to act. Lamport studies this with an analogy of &lt;a href="https://en.wikipedia.org/wiki/Byzantine_fault"&gt;Byzantine Generals&lt;/a&gt; problem, and first proposed Paxos protocol. &lt;a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)"&gt;Paxos&lt;/a&gt; has been an important foundation to modern distributed systems. In Paxos, consensus is achieved in &lt;a href="https://martinfowler.com/articles/patterns-of-distributed-systems/paxos.html"&gt;two phases&lt;/a&gt;, which creates the problem of livelocks. Raft is an alternative to Paxos, and is widely adopted today. &lt;a href="http://thesecretlivesofdata.com/raft/"&gt;Here&lt;/a&gt; is a link to an animated illustration for Raft protocol. The Raft protocol is also used in Redis. It has three roles: Leader, Candidate, and follower. ZAB protocol is similar to Raft, where it needs to select a leader.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Etcd Lab&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In troubleshooting, if we suspect that the response from API server is inconsistent with etcd store, we want to directly connect to it.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Managed Kubernetes services do not expose their etcd store. We can use KinD or Minikube. There are two types of jump box to access etcd store: using etcd Pod, or SSH to a Node. To connect to etcd, we also need the X509 key, certificate and CA&amp;#8217;s certificate, in addition to the endpoint, usually an IP with port 2389. When I connect to Pod shell, I find the command shell not easy to use. They might miss basic command such as ls, or do not support auto completion.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take KinD for example, we first create a secret, then we can connect to the node with docker CLI command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create ns myns&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n myns create secret generic mysecret --from-literal key1&lt;span style="color:#f92672"&gt;=&lt;/span&gt;value1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n myns get secret mysecret -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.data.key1}&amp;#39;&lt;/span&gt; | base64 -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it control /bin/bash&#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 node, &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;apt update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt install etcd-client&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nc -vz localhost &lt;span style="color:#ae81ff"&gt;2379&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep etcd&#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;export ETCDCTL_API&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_CERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/pki/apiserver-etcd-client.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/pki/apiserver-etcd-client.key&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_CACERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/pki/etcd/ca.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_ENDPOINTS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;https://127.0.0.1:2379&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl member list write out&lt;span style="color:#f92672"&gt;=&lt;/span&gt;table&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can see the secret object directly with etcd store:&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;etcdctl get /registry/secrets/myns/mysecret&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With get query, when using &amp;#8211;prefix, we can use &amp;#8211;keys-only switch to list keys without values:&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;etcdctl get --prefix /registry/api --keys-only&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get --prefix /registry/namespace -wjson&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can write key-value with put command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl put myloc &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get myloc -wjson&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, all the key names start with / which makes the key looks like a POSIX path. Every Kubernetes object is stored in etcd with a unique key following a self-explanatory naming pattern. To display the path, we can also use debug log that records the call to API server:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get ns myns -v9&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Look for curl command such as:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;I0523 22:51:43.517728 32347 round_trippers.go:466] curl -v -XGET -H &amp;#34;Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json&amp;#34; -H &amp;#34;User-Agent: kubectl/v1.23.6 (darwin/amd64) kubernetes/ad33385&amp;#34; &amp;#39;https://127.0.0.1:64081/api/v1/namespaces/myns&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From there we can see the etcd query as the URI is namespaces/myns, which we use in etcdctl query path:&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;etcdctl get /registry/namespaces/myns&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Every type of Kubernetes object has a storage.go file in their implementation that defines how api server should write object. &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/pkg/registry/core/pod/storage/storage.go"&gt;Here&lt;/a&gt; is an example for Pod object.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Etcd also supports watch command to watch for changes. For example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl watch --prefix /registry/namespace &lt;span style="color:#75715e"&gt;# watch output k create ns newns&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;Now we create a namespace with kubectl:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create ns myns&#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 from etcdctl will reflect the change. The communication between etcdctl and etcd is gRPC protocol. The output is based on stream, as we can see from the watch result.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-etcd-maintenance"&gt;Etcd Maintenance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Like any distributed store, etcd needs &lt;a href="https://etcd.io/docs/v3.5/op-guide/maintenance/"&gt;maintenance&lt;/a&gt; and operation work. For example, we can check endpoint status with endpoint command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl endpoint status&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can also backup and restore etcd store with etcdctl command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl snapshot save /tmp/backup.db&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This was an question in &lt;a href="https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/"&gt;CKA exam&lt;/a&gt;. In real life, when the workload scales up, the etcd store may come across many pitfalls, such as degraded performance, unresponsiveness, some etcd member going down, network partition on etcd store causing split brain. It is important to ensure efficient communication between API server and etcd store. The etcdctl provides defrag and compact commands for common maintenance activities.&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/05/hosting-database-on-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Hosting database on Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/06/chaos-mesh-cloud-native-chaos-engineering/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Chaos Mesh – Cloud Native Chaos Engineering&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></channel></rss>