<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>backup on Digi Hunch</title><link>https://static.digihunch.com/tag/backup/</link><description>Recent content in backup on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Thu, 17 Apr 2025 14:03:52 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/backup/index.xml" rel="self" type="application/rss+xml"/><item><title>Hosting database on Kubernetes</title><link>https://static.digihunch.com/2022/05/hosting-database-on-kubernetes/</link><pubDate>Sun, 29 May 2022 11:01:00 -0400</pubDate><guid>https://static.digihunch.com/2022/05/hosting-database-on-kubernetes/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-database.webp" alt="Featured image of post Hosting database on Kubernetes" /&gt;&lt;h2 class="wp-block-heading"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&amp;#8220;We want to host Postgres database on Kubernetes. Can you help us?&amp;#8221;. The client appears assertive and reluctant to resort to managed services. So I did some homework and went through &lt;a href="https://www.youtube.com/watch?v=3TFXztwat_s"&gt;this&lt;/a&gt; tutorial. My thought: it&amp;#8217;s doable, but don&amp;#8217;t do it unless operating database as a service is your main business.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I believed that was the client&amp;#8217;s best interest, until I came across this the blog post &lt;a href="https://thenewstack.io/a-case-for-databases-on-kubernetes-from-a-former-skeptic/"&gt;A Case for Databases on Kubernetes from a Former Skeptic&lt;/a&gt;. The author explained his journey from being a skeptic, to grudging acceptance, and eventually to an evangelist on running database on Kubernetes. The same voice came from the author of the upcoming book &lt;a href="https://www.oreilly.com/library/view/managing-cloud-native/9781098111380/"&gt;Managing Cloud Native Data on Kubernetes&lt;/a&gt;, who also advocates hosting database on Kubernetes. While the points in the chapters are valid, the book also includes a good amount of technical details which might lead reader to believe the opposite view.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Just a few years back, Kubernetes was not mature to host database. This is changing in 2022. Nowadays, for clients with their own Kubernetes platform, technological maturity is no longer the main reason that keeps them from hosting database on Kubernetes, it is the operational cost. The operational cost has to do with whether the client has in-house expertise in database and Kubernetes. If they do, the hard path makes economical sense.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post, we discuss what we need to be aware of in order to host database on Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-benefit-with-kubernetes"&gt;Benefit with Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first few versions of Kubernetes only supported stateless workload (reference &lt;a href="https://www.youtube.com/watch?v=BE77h7dmoQU"&gt;documentary&lt;/a&gt;). That is what Kubernetes was born to solve. Built-in objects such as replicaSet, deployment, horizontalPodAutoscaler are abstractions of operations particular to stateless workload. Pods for stateless workload are ephemeral: they crash and get replaced at any time. Because they don&amp;#8217;t carry persistent data themselves, they are expendable. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes&amp;#8217; orchestration capability are driven by controllers. As &lt;a href="https://static.digihunch.com/2022/04/kubernetes-operator/"&gt;discussed&lt;/a&gt;, the &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/#controller-pattern"&gt;controller pattern&lt;/a&gt; is adopted in all controller implementations. They are the engines of the platform that works tirelessly in a control loop to ensure desired states matches their declared states. This is a key feature of Kubernetes as container platform. Let&amp;#8217;s examine a web service that requires 5 instances behind load balancer. With traditional hosting model on Linux servers, you&amp;#8217;d have it installed on all five VMs. If the process on one of the VMs dies, the VM has to be removed from the load balancer&amp;#8217;s target pool. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One may wrap the process with process monitor and control utility such as &lt;a href="http://supervisord.org/"&gt;supervisord&lt;/a&gt;, and re-install the application using automation utility (e.g. &lt;a href="https://www.ansible.com/products/controller"&gt;Ansible&lt;/a&gt;). However, each server is unaware of the status of its peer. Without a central &amp;#8220;Control Plane&amp;#8221;, there is no coordination between the activities of each VMs. Kubernetes controller solved all these operational problems. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://k21academy.com/wp-content/uploads/2021/05/Actual_DesiredState.png" alt="Kubernetes Control Loop"/&gt;&lt;figcaption class="wp-element-caption"&gt;Control Loop&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes comes with a set of build-in &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/"&gt;controllers&lt;/a&gt; that run inside the kube-controller-manager. Here is a good page about how &lt;a href="https://www.containiq.com/post/kubernetes-controllers"&gt;controllers&lt;/a&gt; work. Controller is what is missing in many automation tools other that Kubernetes. Even though Red Hat now brands Ansible as Automation &lt;em&gt;Controller&lt;/em&gt;, it does not involve a control loop or controller pattern. If there&amp;#8217;s one thing that sets Kubernetes apart from other hosting platforms and automation platforms, it is the implementation of controller pattern. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Stateful workload&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Does the controller pattern also benefit stateful workload? Yes. How to orchestrate Pods for stateful workload is usually more tricky. CRD can define a custom object type for controller to consume. In this case, an operator is an implementation of the controller pattern. This pattern is also known as the &lt;a href="https://static.digihunch.com/2022/04/kubernetes-operator/"&gt;operator pattern&lt;/a&gt;. In a replicaSet, Pod names have extensions of randomly generated numbers. A statefulSet names its the Pods by sequential numbers. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Postgres database, Bitnami built a good &lt;a href="https://artifacthub.io/packages/helm/bitnami/postgresql-ha"&gt;Helm Chart&lt;/a&gt; to install the database automatically. However, it does not have a control loop. If someone changes the workload after initial installation, the change is not monitored or controlled by any controller. This is a disadvantage of Helm chart as compared with operators. For PostgreSQL, there are a &lt;a href="https://blog.flant.com/comparing-kubernetes-operators-for-postgresql/"&gt;number of operators&lt;/a&gt;, the most notable being PGO (&lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/"&gt;Postgres Operator&lt;/a&gt;) from &lt;a href="https://www.crunchydata.com/"&gt;Crunchy Data&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To install an instance of PostgreSQL database, we need to install the operator, and then declare a Custom Resource using the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/references/crd/"&gt;PostgresCluster&lt;/a&gt; CRD. The operator will set up the cluster according to the declaration made in the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/tutorial/create-cluster/"&gt;PostgresCluster&lt;/a&gt; CR. I used the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/quickstart/"&gt;quick start guide&lt;/a&gt; to bring Postgres up real quick on an Azure Kubernetes cluster. The &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/"&gt;operator&lt;/a&gt; (v5) supports common cloud Kubernetes platforms (GKE, EKS, AKS), VMware Tanzu, Openshift, Rancher, Kubernetes. It does not explicitly indicate whether PGO supports Minikube or kind.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So far, I&amp;#8217;ve discussed the pros of running PostgreSQL on Kubernetes using Postgres Operator. We can describe the database deployment in a CR and the controller (operator) will monitor the resource incessantly to ensure the actual state matches the state defined in the CR. Not only is it doable to host database in Kubernetes, it makes our lives even easier. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Persistent storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Database is not only a stateful workload, it also has special requirement on storage. It needs to persist data, support ACID transaction, and make optimal use of disks. When we operate everything on premise, we use fibre cable with a &lt;a href="https://static.digihunch.com/2019/05/storage-nitty-gritty-2-5/"&gt;SAN&lt;/a&gt; as the storage media for database file. The operating system allows the database process to interact with blocks on the storage volume via device mapper.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, we need to give Pods persistent volumes. There are a few APIs: Storage Class, Volume Storage Class, Persistent Volume and Persistent Volume Claims. Storage Class represents how Pod can connect to a storage. Pods will need PVCs in order to read and write on PVs. However, since Pods are ephemeral &amp;#8211; a Pod may crash any time, even if it is in the middle of writing to a PV, during an ACID transaction. The scheduler may reschedule the crashed Pod to a different node. Then it will need to pick up the PV from where it left off, on the new Node. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take Azure Kubernetes Service for example, a few storage classes are available by default, backed by Azure managed disk (managed-csi) or Azure file storage (azurefile-csi):&lt;/p&gt;&#10;&lt;table id="tablepress-19" class="tablepress tablepress-id-19 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;td class="column-1"&gt;&lt;/td&gt;&lt;th class="column-2"&gt;StorageClass&lt;/th&gt;&lt;th class="column-3"&gt;Azure storage service&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td rowspan="4" class="column-1"&gt;in-tree&lt;/td&gt;&lt;td class="column-2"&gt;default&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure StandardSSD&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-2"&gt;managed-premium&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Standard Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile-premium&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td rowspan="4" class="column-1"&gt;csi&lt;/td&gt;&lt;td class="column-2"&gt;managed-csi&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure StandardSSD&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-7"&gt;&#10;&#9;&lt;td class="column-2"&gt;managed-csi-premium&lt;/td&gt;&lt;td class="column-3"&gt;Managed Disk using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-8"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile-csi&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Standard Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-9"&gt;&#10;&#9;&lt;td class="column-2"&gt;azurefile-csi-premium&lt;/td&gt;&lt;td class="column-3"&gt;Azure File Share using Azure Premium Storage&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-19 from cache --&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we use storage class based on Azure disks to create a PV, only one Pod can use the PV. If we use storage class based on Azure files to create a PV, then the storage is mounted as NFS (Linux) or SMB (Windows) share. File storage is not a valid &lt;a href="https://static.digihunch.com/2020/08/cloud-storage-overview/"&gt;use case&lt;/a&gt; for database workload and it can significantly degrade database performance. When I tried to use a file-storage based CSI with PGO, the Pod reports an &lt;a href="https://github.com/CrunchyData/postgres-operator/issues/2870"&gt;error&lt;/a&gt; and will not start properly. We should use Azure disk based CSI storage classes. That leaves us with two options: managed-csi and managed-csi-premium. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;High Availability&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Even with these to options left, we still have to investigate how database Pods interact with persistent volume for high availability, in order to determine whether any of the options are suitable. The two storage classes differ by disk performance but both have its own &lt;a href="https://docs.microsoft.com/en-us/azure/aks/availability-zones#azure-disk-availability-zone-support"&gt;limitation&lt;/a&gt; with multi-AZ support on Azure managed disks. When the cluster operates across zones, the Kubernetes scheduler may reschedule a Pod crashed in one zone to a Node in a different availability zone (a different data centre). Even though the managed disks, when attached to VMs, can be configured as zone-redundant, when they are used as Kubernetes volume, they are NOT zone-redundant. So the node in a different zone will not be able to attach PV to the new Pod. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are SDS (software-defined storage) solution such as &lt;a href="https://portworx.com/wp-content/uploads/2020/06/portworx-microsoft-aks-reference-architecture.pdf"&gt;Portworx&lt;/a&gt; that solves the limitation of Azure disk for cross-region storage volume. The SDS layer brings managed disks from multiple availability zones into a pool. This storage pool acts as a highly available, cross-zone storage tier presented to AKS as persistent volumes. We can install Portworx as the SDS layer using Portworx operator. To do so, we first have to configure &lt;a href="https://docs.portworx.com/portworx-enterprise/platform/kubernetes/azure-aks/install/azure-managed-identity-on-aks"&gt;grant the cluster the permission &lt;/a&gt;to provision resources in Azure, because the Portworx operator will use node&amp;#8217;s identity (kubelet identity) to provision Azure resources on behalf of the nodes. Portworx will provision Azure disks and acts as the intermediary layer.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="726" src="https://static.digihunch.com/wp-content/uploads/2025/04/aks-data-1024x726.webp" alt="" class="wp-image-13110" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/aks-data-1024x726.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/aks-data-300x213.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/aks-data-768x545.webp 768w, https://static.digihunch.com/wp-content/uploads/2025/04/aks-data.webp 1430w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from cross-zone high availability enabled by PX-Store, Portworx can also help with cross-region replication of persistent volumes. The PX-DR component can perform asynchronous replication across Azure regions. The destination region needs to have its own cluster because a single AKS cluster cannot span across regions.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Storage Class&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Once we have portworx installed, the following storage classes are available by default:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;px-db&lt;/li&gt;&#10;&lt;li&gt;px-db-cloud-snapshot&lt;/li&gt;&#10;&lt;li&gt;px-db-cloud-snapshot-encrypted&lt;/li&gt;&#10;&lt;li&gt;px-db-encrypted&lt;/li&gt;&#10;&lt;li&gt;px-db-local-snapshot&lt;/li&gt;&#10;&lt;li&gt;px-db-local-snapshot-encrypted&lt;/li&gt;&#10;&lt;li&gt;px-replicated&lt;/li&gt;&#10;&lt;li&gt;px-replicated-encrypted&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps for installing porworx on AKS are documented &lt;a href="https://web.archive.org/web/20230204230139/https://docs.portworx.com/install-portworx/cloud/azure/"&gt;here&lt;/a&gt;. This blog post has more details in the &lt;a href="https://portworx.com/blog/portworx-enterprise-2-8-installation-on-oracle-kubernetes-engine-oke/"&gt;installation&lt;/a&gt; process on a different platform. We can also built CSI based storage classes with different IO priority and replication factors.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In summary, Kubernetes operator pattern makes it easier to manage stateful workload. However, database performance depends largely on storage. To host database on Kubernetes, one will have to also manage the storage volumes on their own. There has not been a study on the impact to performance by moving database to Kubernetes platform. However, I only expect a degraded performance due to the layers introduced.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Example&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this section we configure a (minimally viable) PostgreSQL cluster using Crunchy Data pgo to demonstrate the idea. The steps are based on its &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/5.1.1/tutorial/"&gt;tutorial&lt;/a&gt; but it works on a local KinD cluster. As discussed in a &lt;a href="https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/"&gt;previous post&lt;/a&gt;, I use KinD for testing workload requiring persistent storage because Minikube has this open &lt;a href="https://github.com/kubernetes/minikube/issues/12360"&gt;issue&lt;/a&gt; with permissions on PVs with multiple nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To prepare the cluster, we can use &lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/kind/kind-config.yaml"&gt;kind-config.yaml&lt;/a&gt; file from my &lt;a href="https://github.com/digihunch/real-quicK-cluster"&gt;real-quicK-cluster&lt;/a&gt; repo:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind create cluster --config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kind-config.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# to delete cluster after testing: kind delete cluster --name kind&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We use Helm to install the operator. Since the Helm chart is not hosted in a public repo, we&amp;#8217;d have to download the directory of the Helm Chart.&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;git clone https://github.com/CrunchyData/postgres-operator-examples&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cd postgres-operator-examples&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install -n postgres-operator --create-namespace crunchy-pgo helm/install&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n postgres-operator get po --watch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl explain postgresclusters&#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 create a YAML file for the Custom Resource and let&amp;#8217;s call it test-cluster.yaml with the following content:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1beta1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;PostgresCluster&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hippo&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;operator&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;backups&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;pgbackrest&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;developers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchy&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pgbackrest&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ubi8&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2.38&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;repos&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;repo1&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;volume&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;volumeClaimSpec&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;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;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;1&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:#e6db74"&gt;&amp;#34;standard&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;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;registry&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;developers&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;com&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchydata&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;crunchy&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ubi8&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;14.3&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&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; &lt;span style="color:#a6e22e"&gt;instances&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;dataVolumeClaimSpec&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;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;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;1&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:#e6db74"&gt;&amp;#34;standard&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;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;instance1&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;replicas&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;minAvailable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;postgresVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;14&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In the manifest, we specified a cluster, using storageclass named &amp;#8220;standard&amp;#8221;, with 3 replicas and requiring 2 available. We assume a storage class named &amp;#8220;standard&amp;#8221; already exists and optimized for database workload. In the manifest, we also configured a backup job. We can apply the manifest and watch for the Pods to come up in a few minutes.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f test-cluster.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n postgres-operator get po --watch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n postgres-operator describe postgresclusters hippo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The Pods in the postgres-operator namespace should report something like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY STATUS RESTARTS AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-backup-mwpm-ps8wk 0/1 Completed &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 21s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-instance1-6mls-0 4/4 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-instance1-hjp6-0 4/4 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-instance1-k4qf-0 4/4 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hippo-repo-host-0 2/2 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m35s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pgo-548d5f48bc-9w4z4 1/1 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 8m41s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pgo-upgrade-566b9cc98f-d7gkr 1/1 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 8m41s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Three Pods for PostgreSQL are all up. The first backup run has completed already. We can connect to the cluster using psql following the &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/5.1.1/quickstart/"&gt;quick start guide&lt;/a&gt;. We can also configure an application. A good example application that uses PostgreSQL database is &lt;a href="https://www.keycloak.org/"&gt;KeyCloak&lt;/a&gt;. We briefly mentioned it in OIDC &lt;a href="https://static.digihunch.com/2022/02/istio-external-authorization/"&gt;discussion&lt;/a&gt;. Currently the keycloak example on Crunchy pgo&amp;#8217;s &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/5.1.1/quickstart/"&gt;quick start guide&lt;/a&gt; is outdated. Instead, use the following content as keycloak.yaml:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;apps&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Deployment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;postgres&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;operator&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;labels&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;app&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;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&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;selector&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;matchLabels&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;app&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;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&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;template&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;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;labels&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;app&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;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;keycloak&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;containers&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;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;quay&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;keycloak&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;keycloak&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;latest&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;keycloak&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;args&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [&lt;span style="color:#e6db74"&gt;&amp;#34;start-dev&amp;#34;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;env&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DB_VENDOR&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;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;postgres&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &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;DB_ADDR&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&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;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&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;host&lt;/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;DB_PORT&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&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;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&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;port&lt;/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;DB_DATABASE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&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;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&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;dbname&lt;/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;DB_USER&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&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;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&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;user&lt;/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;DB_PASSWORD&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;valueFrom&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; { &lt;span style="color:#a6e22e"&gt;secretKeyRef&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;hippo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;pguser&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hippo&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;password&lt;/span&gt; } }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&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;KEYCLOAK_USER&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;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &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;KEYCLOAK_PASSWORD&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;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;admin&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &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;PROXY_ADDRESS_FORWARDING&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;value&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;true&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ports&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;http&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;containerPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/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;https&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;containerPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8443&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;readinessProbe&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;httpGet&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;path&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;/realms/master # https://stackoverflow.com/questions/70577004/keycloak-could-not-find-resource-for-full-path&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;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&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;initialDelaySeconds&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;30&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;restartPolicy&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Always&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once we apply keycloak.yaml, in a minute we should see and be able to port-forward web traffic:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f keycloak.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n postgres-operator get po -l app.kubernetes.io/name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;keycloak&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY STATUS RESTARTS AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;keycloak-7995d78d7c-zjp4d 1/1 Running &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 4m29s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl port-forward deploy/keycloak -n postgres-operator 8080:8080&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After using the port-forward command, we can browse to web portal on my MacBook by http://localhost:8080 and configure an initial password, as shown here:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1086" height="937" src="https://static.digihunch.com/wp-content/uploads/2022/07/image.png" alt="" class="wp-image-6112"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In real life system we would need a proper Ingress. After testing, delete the cluster with kind command and specify the cluster name (kind).&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operation Cost&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Operation cost is an important consideration. Troubleshooting on Kubernetes platform is in general more complicated than just on a Unix system. Hosting database on Kubernetes requires skills not only on the Kubernetes platform, but also on database. There used to be database administrator positions where someone has to maintain the upgrade, the storage, the replication, the multi-tenancy and the performance optimization of database. With a database hosted on Kubernetes, the database administrator will have to perform all these activities on a containerized platform. This is not an easy undertaking, and in many occasions warrants a full-time position on its own. Therefore, don&amp;#8217;t host your database on Kubernetes, unless that is your main business. It is not the technology that shots down this option. It is the operation cost, such as complexity of configuration, and staff skillset, that makes this option not worth it.&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/fsx-ontap-enterprise-storage-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;FSx ONTAP – Enterprise storage on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Etcd – the key-value store for Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Operator</title><link>https://static.digihunch.com/2022/04/kubernetes-operator/</link><pubDate>Thu, 07 Apr 2022 09:39:00 -0400</pubDate><guid>https://static.digihunch.com/2022/04/kubernetes-operator/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-operator.webp" alt="Featured image of post Kubernetes Operator" /&gt;&lt;p class="wp-block-paragraph"&gt;Kubernetes has a number of tools to automate the deployment of a single workload. In previous posts, we had covered &lt;a href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;Helm&lt;/a&gt; and &lt;a href="https://static.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;Kustomize&lt;/a&gt;. What are left unresolved is how to maintain the status of workload after deployment is completed. In this post, I will give an introduction to Kubernetes Operator. Compared with Helm (templating approach) and Kustomize (patching approach), Kubernetes Operator follows the &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/"&gt;operator pattern&lt;/a&gt;. Operators are usually provided by the developer of the application.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-operator-pattern"&gt;Operator Pattern&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, we know that a controller takes care of routine tasks to ensure that desired state expressed by Kubernetes resource types matches the current state. One example is that the Deployment controller ensures the number of pods running matches the amount specified in the replica field. Controller is the key to ensure that resources can be managed by declarative manifests for Kubernetes resources. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes makes use of controller pattern throughout its own design. One of its key component, Controller Manager, is a collection of many controllers. Each controller is in charge of a control loop, responsible for listening the object it manages. Another component, Kube-scheduler, is also a special type of Controller. The kube-scheduler monitors unscheduled Pod and health of nodes and determines the best Node to schedule the new Pod to. Then it writes the decision to etcd store for kubelet to execute.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This controller pattern is fairly successful in what it does and we can extend the use of it. Beyond the built-in resource types, we can create our own custom resource definitions (CRDs), and create controllers that watches for the manifest that declares custom resources (CRs). The controller ensures that the resource status matches their specifications. This is also known as reconciliation, which is implemented as a control loop. Operator pattern can be illustrated in the diagram below:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/cncf/tag-app-delivery/raw/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/img/02_1_operator_pattern.png" alt="Operator Design Pattern"/&gt;&lt;figcaption class="wp-element-caption"&gt;Operator Pattern&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Technically, there is no difference between a controller and an operator. What makes an Operator (used to install workload) different than a native Kubernetes controller, are two things. First, an Operator usually needs CRDs because the built-in resource types are insufficient. Second, the operator reflects the domain knowledge to keep the target workload running. For example, stateful workloads such as database needs their operational steps executed in certain orders.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On &lt;a href="https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md"&gt;Operator Pattern&lt;/a&gt;, CNCF published a &lt;a href="https://www.cncf.io/wp-content/uploads/2021/07/CNCF_Operator_WhitePaper.pdf"&gt;whitepaper&lt;/a&gt; with a deeper review. This white paper is the best reference for a good understanding of the Operator Pattern.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Custom Resource Definition&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The built-in controllers work with built-in objects (pre-defined APIs). Custom operators usually need their own APIs to function. To extend Kubernetes API, we define the schema of these APIs in the form of CRDs (&lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules"&gt;custom resource definitions&lt;/a&gt;) using &lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation"&gt;OpenAPIv3&lt;/a&gt; standard. Then, we can declare Custom Resources (CRs) in compliance with the schema. The OpenAPIv3 schema in the CRD resource tells validating web hook (&lt;a href="https://static.digihunch.com/2022/01/kubernetes-admission-control/"&gt;admission control&lt;/a&gt;) how to validate the schema when we send an CR in to API server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we work with third-party operators, they usually provide CRDs along with the operator implementation. For example, in my &lt;a href="https://github.com/digihunch/wordpress-operator"&gt;operator example&lt;/a&gt; project, we have a minimalist CRD &lt;a href="https://github.com/digihunch/wordpress-operator/blob/main/config/crd/bases/wordpress.digihunch.com_wordpresses.yaml"&gt;WordPress&lt;/a&gt; with one property: sqlRootPassword and we can declare a CR as in &lt;a href="https://github.com/digihunch/wordpress-operator/blob/main/config/samples/wordpress_v1_wordpress.yaml"&gt;this&lt;/a&gt; example. For a more realistic use case, we can take a look at &lt;a href="https://github.com/kiali/kiali-operator/blob/master/crd-docs/crd/kiali.io_kialis.yaml"&gt;Kiali CRD&lt;/a&gt;. In the next section, we&amp;#8217;ll use it along with Kiali operator to install Kiali. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operator Usage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Like &lt;a href="https://artifacthub.io/"&gt;Artifact Hub&lt;/a&gt; to Helm, &lt;a href="https://operatorhub.io/"&gt;OperatorHub&lt;/a&gt; is a public registry of most used Kubernetes Operators. In this section, we will take an example of using Operators. We will install Kiali as an add-on to Istio using Kiali CR and operator, which also depends on Prometheus to be installed using Prometheus Operator first. Note that the Kiali installation outlined in this section is not the the &lt;a href="https://istio.io/latest/docs/ops/integrations/kiali/#option-1-quick-start"&gt;quick-start&lt;/a&gt; install manifests from Istio&amp;#8217;s &lt;a href="https://github.com/istio/istio/tree/master/samples/addons"&gt;sample&lt;/a&gt; directory. For Kiali on production system we have to customize the &lt;a href="https://kiali.io/docs/installation/installation-guide/"&gt;installation&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose we have installed Istio, we can then install Prometheus operator using Helm. The Prometheus operator will install Prometheus. Then we use Helm again to install Kiali operator. The Kiali operator will watch for creation of Kiali CRD, to deploy services:&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 -f prometheus-values.yaml --namespace istio-system --repo https://prometheus-community.github.io/helm-charts --version 13.6.0 istio-prometheus prometheus --insecure-skip-tls-verify&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ helm install -f kiali-operator-values.yaml --namespace kiali-operator --repo https://kiali.org/helm-charts --version 1.45.0 kiali-op kiali-operator --create-namespace&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f kiali-cr.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;I include example content for each file in the commands above on Github gist (&lt;a href="https://gist.github.com/digihunch/448180c019310a5dadb700c1bcdb0772"&gt;prometheus-values.yalm&lt;/a&gt;, &lt;a href="https://gist.github.com/digihunch/5574aba4aa9fc1aa15257bd6e811bf5b"&gt;kiali-operator-values.yaml&lt;/a&gt; and &lt;a href="https://gist.github.com/digihunch/2fd0884f5999416c8baf4197ee5790f3"&gt;kiali-cr.yaml&lt;/a&gt;). For more options for installing Kiali, refer to &lt;a href="https://kiali.io/docs/installation/installation-guide/install-with-helm/"&gt;their&lt;/a&gt; documentation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use this example to install Kiali and it includes two Operators, the Prometheus Operator and the Kiali Operator. The Prometheus Operator is one of the first ever written Kubernetes Operator. As soon as the operator is deployed, it starts to deploy the operator service. For the Kiali operator, we need to deploy Kiali CR after the Kiali Operator has been deployed. Both are valid patterns.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operator Development&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Operator is powerful. However, authoring an Operator is not a trivial effort. One usually start with a framework. A framework creates a body of boiler plate code that has the pattern implemented and allows developers to enrich the functions following the pattern. The white paper introduced three frameworks:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;CNCF &lt;a href="https://operatorframework.io/"&gt;Operator Framework&lt;/a&gt; &amp;#8211; aims at Operator Developers with an SDK, a scaffolding tool and a test harness. It currently supports three project types: Golang, Helm and Ansible. CNCF Operator framework consists of SDK and OLM. &lt;/li&gt;&#10;&lt;li&gt;Kopf (Kubernetes Operator Pythonic Framework) &amp;#8211; an easy-to-use framework in Python that abstracts away most of the low-level Kubernetes API communications hassle.&lt;/li&gt;&#10;&lt;li&gt;kubebuilder &amp;#8211; helps build a Manager similar to the native kube-controller-manager. For difference with OperatorSDK, read &lt;a href="https://sdk.operatorframework.io/docs/faqs/#what-are-the-the-differences-between-kubebuilder-and-operator-sdk"&gt;here&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Metacontroller: lightweight Kubernetes Controller as a Service&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://www.cncf.io/projects/operator-framework/"&gt;CNCF&lt;/a&gt; Operator Framework, the Operator SDK supports development using &lt;a href="https://sdk.operatorframework.io/docs/building-operators/ansible/"&gt;Ansible&lt;/a&gt;, &lt;a href="https://sdk.operatorframework.io/docs/building-operators/helm/"&gt;Helm&lt;/a&gt; and &lt;a href="https://sdk.operatorframework.io/docs/building-operators/"&gt;Golang&lt;/a&gt;. The author of &lt;a href="https://www.velotio.com/engineering-blog/getting-started-with-kubernetes-operators-helm-based-part-1"&gt;this&lt;/a&gt; post makes a general comparison as follows:&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;thead&gt;&lt;tr&gt;&lt;th&gt;Type &lt;/th&gt;&lt;th&gt;Best use case&lt;/th&gt;&lt;th&gt;Underlying technology&lt;/th&gt;&lt;th&gt;Amt of Effort&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Helm&lt;/td&gt;&lt;td&gt;Stateless workload&lt;/td&gt;&lt;td&gt;Helm Charts&lt;/td&gt;&lt;td&gt;Med&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ansible&lt;/td&gt;&lt;td&gt;Stateless workload&lt;/td&gt;&lt;td&gt;Ansible Roles and Playbooks&lt;/td&gt;&lt;td&gt;Med&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Golang&lt;/td&gt;&lt;td&gt;Stateful workload&lt;/td&gt;&lt;td&gt;Code developed in Golang&lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The aforementioned &lt;a href="https://github.com/kiali/kiali-operator"&gt;Kiali operator&lt;/a&gt; is an example of Operator developed in Ansible. The &lt;a href="https://github.com/prometheus-operator/prometheus-operator"&gt;prometheus operator&lt;/a&gt;, is developed in Golang as the workload can be stateful depending on configuration. One needs to know how to develop operator in Golang in order to tackle the most complicated situations. This is requires some serious development effort. The documentation with a quick start section is available &lt;a href="https://sdk.operatorframework.io/docs/building-operators/golang/quickstart/"&gt;here&lt;/a&gt;. Even that is not very straightforward. RedHat, the maintainer of the CNCF &lt;a href="https://cloud.redhat.com/learn/topics/operators"&gt;Operator&lt;/a&gt; framework has a good blog &lt;a href="https://developers.redhat.com/articles/2021/08/04/managing-stateful-applications-kubernetes-operators-golang#"&gt;post&lt;/a&gt; on how to develop an Operator in Golang. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The example requires some development knowledge to go through. On my MacOS (Intel) I have to configure the following prerequisites:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Install gcc, using command: xcode-select &amp;#8211;install&lt;/li&gt;&#10;&lt;li&gt;Install the right version of golang. You can find the version &lt;a href="https://sdk.operatorframework.io/docs/contribution-guidelines/developer-guide/#prerequisites"&gt;here&lt;/a&gt;. The MacOS has a version of golang installed already so I had to install version 1.17 and link to it: brew install go@1.17 &amp;amp;&amp;amp; brew link &amp;#8211;force go@1.17&lt;/li&gt;&#10;&lt;li&gt;Install operator-sdk with home brew: brew install operator-sdk&lt;/li&gt;&#10;&lt;li&gt;When you run &amp;#8220;operator-sdk version&amp;#8221;, ensure the result shows a golang version that matches your installation.&lt;/li&gt;&#10;&lt;li&gt;If you need to push docker image, also connect to docker registry by running: docker login&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can create our working directory, initialize the repository and create boilerplate code (scaffolding) with these commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ mkdir wordpress-operator &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cd wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ operator-sdk init --domain digihunch.com --repo github.com/digihunch/wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ operator-sdk create api --group wordpress --version v1 --kind WordPress --resource --controller&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With the repo initialized, we can go to the section &amp;#8220;Defining the API&amp;#8221; and &amp;#8220;Implementing the Controller&amp;#8221;. The blog post does not cover every code editing needed to bring up wordpress. You are supposed to go to the author&amp;#8217;s &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest"&gt;repository&lt;/a&gt; to fit the changes into your own repo. The author&amp;#8217;s repo has a few more &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/tree/master/controllers"&gt;controllers&lt;/a&gt; such as &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/blob/master/controllers/common.go"&gt;common.go&lt;/a&gt; and &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/blob/master/controllers/mysql.go"&gt;mysql.go&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At the end of the lab, you should be able to run the controller and bring up wordpress. I used my own &lt;a href="https://github.com/digihunch/wordpress-operator"&gt;repository&lt;/a&gt; for this lab and have made the code changes for this lap in a couple &lt;a href="https://github.com/digihunch/wordpress-operator/commit/5540d7e045bf4da1ea1d140f1b9fd189fd9f2cc9"&gt;commits&lt;/a&gt;. To test locally with the code:&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;$ git clone git@github.com:digihunch/wordpress-operator.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cd wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ make install run&#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 can validate wordpress install from a new terminal as the instruction shows:&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 -f config/samples/wordpress_v1_wordpress.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ minikube service wordpress --url&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For Developers that requires more details, RedHat has an &lt;a href="https://www.redhat.com/cms/managed-files/cl-oreilly-kubernetes-operators-ebook-f21452-202001-en_2.pdf?extIdCarryOver=true&amp;amp;sc_cid=701f2000001Css5AAC"&gt;eBook&lt;/a&gt; for Kubernetes Operators, in supplement to the &lt;a href="https://cloud.redhat.com/learn/topics/operators"&gt;documentation&lt;/a&gt;. As DevOps professional, I&amp;#8217;m mainly concerned with understanding how Operator works and using Operators correctly.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Too many Tools?&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we seem to have too many choice of tools when it comes to deploying workload on Kubernetes. Kustomize and Helm can deploy simple workloads. Operator can deploy stateful workloads, as well as keep the workload status in check. Further, we have FluxCD and ArgoCD based on GitOps workflow.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When assessing a tool, we should think about the complexity of the workload deployed. If it is a single stateless workload, Kustomize or Helm should be sufficient. If it is not very simple but still stateless, we can consider using Helm charts developed by the community. For multiple workloads, we can build our own top-level chart to combine existing sub-charts created by the community.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm is essentially a package manager. It does not follow controller pattern and therefore will not monitor the current status of deployment. Helm has other limitations compared to Operator. For example, as a templating scheme, it reaches limitation when dealing with complex logic, even with the help of its helper functions. It is also hard to reason through the template code when we have to troubleshoot a deployment. Refer to &lt;a href="https://thenewstack.io/we-pushed-helm-to-the-limit-then-built-a-kubernetes-operator/"&gt;this&lt;/a&gt; blog post for the author&amp;#8217;s experience with Helm.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we want our deployment to be fully declarative and continuous, then we will follow the Operator pattern by using a Kubernetes Operator. When we have many workloads of different levels of complexity, we can combine them with GitOps tool. Operator is one of the underlying technologies behind GitOps.&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background has-fixed-layout"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Workload profile&lt;/th&gt;&lt;th&gt;Just Installation&lt;/th&gt;&lt;th&gt;Installation and Maintain Status&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Single stateless workload&lt;/td&gt;&lt;td&gt;Helm or Kustomize&lt;/td&gt;&lt;td&gt;Operator (using Ansible or Helm)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Single stateful workload&lt;/td&gt;&lt;td&gt;Helm or Kustomize&lt;/td&gt;&lt;td&gt;Operator (using Golang)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multiple workloads&lt;/td&gt;&lt;td&gt;Helm (e.g. build parent chart)&lt;/td&gt;&lt;td&gt;GitOps in combination with Operator, Helm and Kustomize&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The table above helps refine deployment requirement. It&amp;#8217;s not a recommendation, but rather a model of analyzing deployment requirement.&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/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Autoscaling on Kubernetes Platform&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/04/knative-introduction-serving/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Serving Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Storage Nitty-Gritty 4 of 5 – Backup and Archive Solutions</title><link>https://static.digihunch.com/2019/10/storage-nitty-gritty-4-of-5-backup-and-archive-solutions/</link><pubDate>Mon, 14 Oct 2019 19:42:00 -0400</pubDate><guid>https://static.digihunch.com/2019/10/storage-nitty-gritty-4-of-5-backup-and-archive-solutions/</guid><description>&lt;h4 class="wp-block-heading"&gt;Business Continuity&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Information Availability IA&lt;/strong&gt; = MTBF/(MTBF+MTTR), where&lt;br&gt;* MTBF (Mean Time Between Failure) &amp;#8211; average time available for a system or component to perform its normal operations between failures.&lt;br&gt;* MTTR (Mean Time to Repair) &amp;#8211; the average time required to repair a failed component.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Disaster Recovery&lt;/strong&gt; &amp;#8211; the coordinated process of restoring systems, data, and the infrastructure required to support ongoing business operations after a disaster occurs. It is the process of restoring a previous copy of the data and applying logs or other necessary processes to that copy to bring it to a known point of consistency.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Recovery-Point Objective (RPO)&lt;/strong&gt; &amp;#8211; the point in time to which systems&lt;br&gt; and data must be recovered after an outage. It defi nes the amount&lt;br&gt; of data loss that a business can endure. A large RPO signifi es high tolerance&lt;br&gt; to information loss in a business.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Recovery-Time Ojbective (RTO)&lt;/strong&gt; &amp;#8211; The time within which systems and applications must be recovered after an outage. It defi nes the amount of downtime that a business can endure and survive. Businesses can optimize disaster recovery plans after defi ning the RTO for a given system.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="557" height="191" src="https://static.digihunch.com/wp-content/uploads/2019/11/image-55.png" alt="" class="wp-image-401"/&gt;&lt;figcaption&gt;Strategies to meet RTO and RPO&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Data Vault&lt;/strong&gt;: a repository at a remote site where data can be periodically or continuously copied so a copy is always available in that site.&lt;br&gt;&lt;strong&gt;Hot site&lt;/strong&gt;: A backup site running all the time.&lt;br&gt;&lt;strong&gt;Cold site&lt;/strong&gt;: A backup site with minimum infrastructure, to be activated for operation in the event of disaster.&lt;br&gt;&lt;strong&gt;Server Clustering&lt;/strong&gt;: a group of servers and relevant resources coupcled to operate as a single syste. Clusters can ensure high availability and load balancing.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Single Point of Failure&lt;/strong&gt; &amp;#8211; failure of a component that can terminate the availability of the entire system or IT service. To mitigate single point of failure, systems are designed with redundancy. This includes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;redundant HBA on server &lt;/li&gt;&lt;li&gt;NIC teaming&lt;/li&gt;&lt;li&gt;redundant switch&lt;/li&gt;&lt;li&gt;multiple storage array ports&lt;/li&gt;&lt;li&gt;RAID and hot spare configuration&lt;/li&gt;&lt;li&gt;Redundant storage array&lt;/li&gt;&lt;li&gt;server clustering (e.g. clustered servers exchange heartbeat to inform each other about their health. If one of the servers fails, other server can take up the workload.&lt;/li&gt;&lt;li&gt;VM Fault Tolerance&lt;/li&gt;&lt;li&gt;Multipathing software: If one path fails, I/O does not reroute unless the system recognizes that it has an alternative path. Multipathing software provides the functionality to recognize and utilize alternative I/O paths to data. Multipathing software also managees the load balancing by distributing I/Os to all available, active paths.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Backup &lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Backup &lt;/strong&gt;is an additional copy of production data created and retained for the sole purpose of recovering lost or corrupted data. Backup are typically performed for the following purposes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Disaster recovery&lt;/strong&gt;. e.g. the backup copies are used for restoring data at an alternate site, when the primary site is incapacitated due to disaster.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Operational recovery&lt;/strong&gt;. e.g. accidental deletion, file corruption&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Archival&lt;/strong&gt;. e.g. data is not changed or accessed any more.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Common considerations for backup includes: time interval between two backups (to meet RPO), retention period, media type (to meet RTO), granularity, compression and deduplication&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Backup Granularity&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Full backup&lt;/strong&gt;: backup of the complete data on the production volumes.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Incremental backup&lt;/strong&gt;: copies the data that has changed since the last full or incremental backup, whichever occurred more recently.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Cumulative backup&lt;/strong&gt;: copies the data that has changed since the last full backup.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Backup Methods&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;hot backup/online backup&lt;/strong&gt;: backup is completed while application is up and running;&lt;/li&gt;&lt;li&gt;&lt;strong&gt;cold backup/offline backup&lt;/strong&gt;: backup is completed while the application is shutdown for the backup window.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The hot backup of online production data is challenging because data is actively used and changed. If a file is open, it is normally not backed up during the backup process. In such situations, an open file agent is required to back up the open file. These agents interact directly with the operating system or application and enable the creation of consistent copies of open files. In database environments, To ensure a consistent database backup, all files need to be backed up in the same state. That does not necessarily mean that all files need to be backed up at the same time, but they all must be synchronized so that the database can be restored with consistency. The disadvantage associated with a hot backup is that the agents usually affect the overall application performance. If this is not acceptable, PIT (point-in-time) copy method can be utilized to create a PIT copy from the production volume and use it as the source for the backup. PIT copy method can reduce impact on production volume.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Typical Backup Architecture &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://static.digihunch.com/wp-content/uploads/2019/11/image-58.png" alt="" class="wp-image-404" width="353" height="302"/&gt;&lt;figcaption&gt;Typical Backup Architecture&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="555" height="300" src="https://static.digihunch.com/wp-content/uploads/2019/11/image-59.png" alt="" class="wp-image-405"/&gt;&lt;figcaption&gt;Typical Backup steps&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="554" height="296" src="https://static.digihunch.com/wp-content/uploads/2019/11/image-60.png" alt="" class="wp-image-406"/&gt;&lt;figcaption&gt;Typical Restore steps&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Backup topologies&lt;/strong&gt;&lt;br&gt;&lt;strong&gt;Direct-attached backup:&lt;/strong&gt; the storage node is configured on a backup client, and the backup device is attached directly to the client;&lt;br&gt;&lt;strong&gt;LAN-based backup&lt;/strong&gt;: the clients, backup server, storage node, and backup device are connected to the LAN;&lt;br&gt;&lt;strong&gt;SAN-based backup (LAN-free):&lt;/strong&gt; The SAN-based backup topology is the most appropriate solution when a backup device needs to be shared among clients;&lt;br&gt;&lt;strong&gt;Mixed topology:&lt;/strong&gt; mix of LAN-based and SAN-based topologies;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;NDMP protocol &lt;/strong&gt;is for backup in NAS environment&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Backup media&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;Tape&lt;/strong&gt;: for long-term offsite storage due to low cost. data access is sequential which implies slowness for both backup and restore. Tapes are susceptible to wear and tear.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Disk&lt;/strong&gt;: fast backup and retrieve to improve RPT and RTO. No offsite capability.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;Virtual Tape&lt;/strong&gt;: virtual taps are disk drives emulated and presented as tapes to the backup software. VTL (virtual tape library) has the same components as that of a physical tape library.&lt;br&gt; Fig 10-18&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Data deduplication&lt;/strong&gt; &amp;#8211; identify and eliminate redundant data to reduce backup window and size. Common data deduplication methods:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;file-level deduplication&lt;/strong&gt; (aka. single-instance storage) detects and removes redundant copies of identical files. It enables storing only one copy of the file; the subsequent copies are replaced with a pointer that points to the original file.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;subfile deduplication&lt;/strong&gt; breaks file into smaller chunks and then uses a specialized althorithm to detect redundant data within and across the file. This eliminates duplicate data across files. This has two forms:&lt;br&gt; * &lt;strong&gt;fixed-length block deduplication&lt;/strong&gt; &amp;#8211; divides the files into fi xed length blocks and uses a hash algorithm to fi nd the duplicate data. &lt;br&gt; *&lt;strong&gt; variable-length segment deduplication&lt;/strong&gt; &amp;#8211; if there is a change in the segment, the boundary for only that segment is adjusted, leaving the remaining segments unchanged.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Data deduplication implementation&lt;/strong&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;strong&gt;source-based data deduplication&lt;/strong&gt; &amp;#8211; eliminates redundant data at the source before it&lt;br&gt; transmits to the backup device. This requires less bandwidth and shortens backup window. It increases the overhead on the backup client and could impact the performance of the backup and application running on the client.&lt;/li&gt;&lt;li&gt;&lt;strong&gt;target-based data deduplication&lt;/strong&gt; &amp;#8211; deduplication occurs at the backup device, which offloads the backup client from the deduplication process. This takes two forms:&lt;/li&gt;&lt;li&gt;&lt;strong&gt;inline deduplication&lt;/strong&gt; &amp;#8211; performs deduplication on the backup data before it is stored on the backup device. this reduces storage need, but introduces time overhead to identify and remove duplication. best for large backup window&lt;/li&gt;&lt;li&gt;&lt;strong&gt;post-process deduplication&lt;/strong&gt; &amp;#8211; enables backup data to be stored on backup device first, and then deduplicate later. This is suitable for tighter backup windows, but requires more storage.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In virtualized environments, backup agent can be installed on the hypervisor, where the VMs appear as a set of files to the agent. VM files can be backed up by performing a file system backup from a hypervisor. For example, Image-based backup operates at hypervisor level and essentially takes a snapshot of the VM. It creates a copy of the guest OS and all the data associated with it (snapshot of VM disk files), including the VM state and application configurations. The backup is saved as a single file (an image) and mounted on a separate server as proxy, which acts as backup client. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="536" height="294" src="https://static.digihunch.com/wp-content/uploads/2019/11/image-62.png" alt="" class="wp-image-409"/&gt;&lt;figcaption&gt;Image Based Backup&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Data archive&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Archive &lt;/strong&gt;&amp;#8211; a repository where fixed content is stored. Fixed content can be data that were changed but will not be changed anymore.&lt;br&gt;&lt;strong&gt;Online archive&lt;/strong&gt;: A storage device directly connected to a host that makes&lt;br&gt; the data immediately accessible.&lt;br&gt;&lt;strong&gt;Nearline archive&lt;/strong&gt;: A storage device connected to a host, but the device where the data is stored must be mounted or loaded to access the data.&lt;br&gt;&lt;strong&gt;Offline archive&lt;/strong&gt;: A storage device not ready to use. Manual intervention is required to connect, mount or load the storage device before data can be accessed.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An &lt;strong&gt;archiving agent&lt;/strong&gt; is software installed on application server. The agent is responsible for identify data that can be archvied based on policy. After the data is identified for archiving, the agent sends the data to the archiving server. Then the original data on the application server is replaced with a stub file, which contains the address of the archived data. &lt;br&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://static.digihunch.com/wp-content/uploads/2019/11/image-63.png" alt="" class="wp-image-410" width="373" height="313"/&gt;&lt;figcaption&gt;Archiving Solution Architecture&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;br&gt;An &lt;strong&gt;archiving serve&lt;/strong&gt;r is software installed on a host that enables administrators to configure the policies for archiving data. An archiving storage device stores fixed content.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Related Postings&lt;/h4&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;&lt;a href="https://static.digihunch.com/2019/03/storage-nitty-gritty-1-5/"&gt;Disk and RAID&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://static.digihunch.com/2019/05/storage-nitty-gritty-2-5/"&gt;SAN&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://static.digihunch.com/2019/07/storage-nitty-gritty-3-of-5-nas-and-object-storage/"&gt;NAS and Object Storage&lt;/a&gt;&lt;/li&gt;&lt;li&gt;&lt;a href="https://static.digihunch.com/2019/11/storage-nitty-gritty-5-of-5-replication/"&gt;Replication&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2019/10/personal-vim-cheatsheet/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Personal Vim cheatsheet&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2019/10/clean-up-your-git-repository/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Clean up Git repository&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>