<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>stateful sets on Digi Hunch</title><link>https://www.digihunch.com/tag/stateful-sets/</link><description>Recent content in stateful sets 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://www.digihunch.com/tag/stateful-sets/index.xml" rel="self" type="application/rss+xml"/><item><title>Hosting database on Kubernetes</title><link>https://www.digihunch.com/2022/05/hosting-database-on-kubernetes/</link><pubDate>Sun, 29 May 2022 11:01:00 -0400</pubDate><guid>https://www.digihunch.com/2022/05/hosting-database-on-kubernetes/</guid><description>&lt;img src="https://www.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://www.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://www.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://www.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://www.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://www.digihunch.com/wp-content/uploads/2025/04/aks-data-1024x726.webp" alt="" class="wp-image-13110" srcset="https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-1024x726.webp 1024w, https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-300x213.webp 300w, https://www.digihunch.com/wp-content/uploads/2025/04/aks-data-768x545.webp 768w, https://www.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://www.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://www.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://www.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://www.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://www.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>Basic Resource Object in Kubernetes 1 of 2</title><link>https://www.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/</link><pubDate>Sat, 16 Jan 2021 22:13:00 -0400</pubDate><guid>https://www.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;For someone from a system administration background, it would be amazing to discover that Kubernetes provides a solution to every pain point in the traditional software deployment landscape. On the contrary, it also brings about a lot of complexity due to the types of resource objects introduced. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/pod-128.png" alt=""/&gt;&lt;figcaption&gt;Pod&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Pod is a shared execution environment for one or more containers. The containers running in a Pod share resources such as memory, volumes, network namespace (e.g. IP address, port range, hostname, routing table), UTS namespace (e.g. hostname) and IPC namespace (Unix domain sockets). Every Pod has its own IP address that is routable on the Pod network. All Pods connect to the same flat network called the Pod network.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A pod most commonly only contains a single container, which is considered a good practice, unless there is good reasons to put two containers in a single pod (sharing resource). One such good reason is to co-schedule tightly-coupled workloads (such as logging, sharing volume, etc). Within the Pod, the containers communicate with each other via localhost interface of the Pod. In service mesh model, there is also a proxy container in each application Pod. The proxy container handles all network traffic entering and leaving the Pod. Also, within the Pod, to avoid competing for resources, individual containers can have their own cgroup limits, which actively police resource usage.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Pods are mortal (composable). They come and go (with dynamic IPs), so application should not store state in Pods. Deploying a Pod is an atomic (all or nothing) operation. When a Pod is scheduled to a node, it enters the pending state while the container runtime on the node downloads images and starts any containers. Once&amp;#8217;s everything is ready, the Pod enters the running state.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We typically deploy Pods via higher-level controllers such as Deployments (to offer scalability and rolling updates), DaemonSets (to run one instance of a service on every node in the cluster), StatefulSets (for stateful application components), and CronJobs (for short-lived tasks that need to run at set times just like a Linux &lt;a href="https://www.digihunch.com/2018/05/cron-and-logrotate-in-centos/"&gt;cronjob&lt;/a&gt;).&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/deploy-128.png" alt=""/&gt;&lt;figcaption&gt;Deployments&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Deployment manages multiple replicas of the same Pod (via ReplicaSets). To follow best practice, you interact with Deployments instead of ReplicaSets, and use YAML file (declarative model). You can perform rolling update or rollback.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/rs-128.png" alt=""/&gt;&lt;figcaption&gt;ReplicaSets&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ReplicaSets provide self-healing and scaling capabilities to Pods. If a Pod fails, it will be replaced. If load increases, then the ReplicaSets creates new Pod. This is all implemented with a background reconciliation loop that is constantly checking whether the right number of Pod replicas are present on the cluster. If not, Kubernetes declares a red-alert condition, orders the control plan to bring up more replicas. The best practice however, is that you should not manage ReplicaSets directly. Instead, you should perform all actions against the Deployment object and leave the Deployment to manage ReplicaSets.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://storage.googleapis.com/cdn.thenewstack.io/media/2017/11/07751442-deployment.png" alt=""/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/svc-128.png" alt=""/&gt;&lt;figcaption&gt;Service&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Pods themselves are mortal (IP churn) so it&amp;#8217;s a bad idea to talk directly to individual Pods. Service object provides stable and reliable networking for a set of dynamic Pods. Service gets its own stable IP address, stable port and stable DNS name. It can also load-balance request across the Pods.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Services are loosely coupled with Pods via labels and label selectors. You specify label selector for Service and labels on Pods when creating them. All the labels in label selector are used to select target Pods. Service acts as front-end, consisting of stable IP, DNS name and port, with Pods acting as backend, consisting of constantly changing Pods. Labels are simple yet extremely powerful. During blue-green update, you may use version label as a technique to control what backend pool is used behind Service object. For example, start with version=1, deploy version 2, remove version from label selector, and eventually add version=2 back to label selector, before phasing out the old Deployment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Services learn Pod status via Endpoint object, more details to follow.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are several types of Service, the default being &lt;strong&gt;ClusterIP&lt;/strong&gt;. A ClusterIP Service has a stable IP address and port that is only accessible from inside the cluster. The ClusterIP gets registered against the name of the Service on the cluster&amp;#8217;s internal DNS service (implemented via coreDNS with Control plane Pods). This means that the ClusterIP only works within the cluster, not outside. The other type of Service is called a &lt;strong&gt;NodePort&lt;/strong&gt;, which is built on top of ClusterIP, but also enables access from outside of the cluster. The Service object has a reliable NodePort mapped to every node in the cluster. The NodePort value is the same on every cluster. Traffic from outside of the cluster can hit any node in the cluster on the NodePort and get through the the Pods.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Other types of Services include LoadBalancer and ExternalName. LoadBalancer Services integrate with load-balancers from cloud provider. They build on top of NodePort Services and allow clients on the internet to reach your Pods via the load balancer of cloud vendor. ExternalName Services route traffic to systems outside of your K8s cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For service discovery within the cluster, Kubelet program every container with the knowledge of the internal DNS (/etc/resolv.conf). The internal DNS service watches constantly the API server for new Services and automatically register them in the DNS. The other means of service discovery is through environment variables. However, in this method the Pods have no way of learning about new Services added to the cluster after the Pod itself is created.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/ep-128.png" alt=""/&gt;&lt;figcaption&gt;Endpoints&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Endpoints object is a dynamic list of all the healthy Pods on the cluster that match the Service&amp;#8217;s label selector. Each Service gets its own Endpoints objects for an up-to-date list of matching Pods. Kubernetes is constantly evaluating the Service&amp;#8217;s label selector against the currently list of healthy Pods on the cluster. Any new Pods that match the selector get added to the Endpoints object, and any Pods that disappear get removed.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When sending traffic to Pods, via a Service, an application will query the cluster&amp;#8217;s internal DNS for the IP address of a Service, then sends the traffic to this stable IP address. Service then forwards it on to a Pod. Kubernetes-native application however, has the ability to query the Endpoints API directly, bypassing the DNS lookup and use of the Service&amp;#8217;s IP.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It requires a thorough understanding of Services, Endpoints and the service discovery mechanism to perform effective troubleshooting in Kubernetes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The aforementioned internal DNS service (we usually call it the &amp;#8220;cluster DNS&amp;#8221;) is implemented in the kube-system Namespace as a set of Pods managed by a Deployment called coredns. These Pods are fronted by a Service called kube-dns. The cluster DNS is constantly looking for new Services and automatically register their details (metadata.name). We might need to check the logs for each of the coredns Pods during troubleshooting. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The kubelet process on every node is watching the API Server for new Endpoints objects, when it sees them, it creates local networking rules that redirect ClusterIP traffic to Pod IPs, using &lt;a href="https://www.digihunch.com/2020/11/ipvs-iptables-and-kube-proxy/" class="rank-math-link"&gt;IPVS technology&lt;/a&gt; on Linux to manage these rules.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/ds-128.png" alt=""/&gt;&lt;figcaption&gt;DaemonSet&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A DaemonSet ensures that all (or some) Nodes run a copy of a Pod. As nodes are added to the cluster, Pods are added to them. As nodes are removed from the cluster, those Pods are garbage collected. Deleting a DaemonSet will clean up the Pods it created.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some typical uses of a DaemonSet are: cluster storage daemon on every node, logs collection daemon on every node, a node monitoring daemon on every node.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/hpa-128.png" alt=""/&gt;&lt;figcaption&gt;Horizontal Pod Autoscaler&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Horizontal Pod Autoscaler automatically scales the number of Pods in a replication controller, deployment, replica set or stateful set based on observed CPU utilization (or, with custom metrics support, on some other application-provided metrics). Note that Horizontal Pod Autoscaling does not apply to objects that can&amp;#8217;t be scaled, for example, DaemonSets.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Horizontal Pod Autoscaler is implemented as a Kubernetes API resource and a controller. The resource determines the behaviour of the controller. The controller periodically adjusts the number of replicas in a replication controller or deployment to match the observed average CPU utilization to the target specified by user.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are more details about HPA &lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/" class="rank-math-link"&gt;here&lt;/a&gt; and &lt;a href="https://cloud.google.com/kubernetes-engine/docs/concepts/horizontalpodautoscaler" class="rank-math-link"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/blob/master/icons/png/resources/labeled/sts-128.png?raw=true" alt="sts-128.png"/&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;StatefulSets are designed for stateful application, which creates and saves valuable data. The three properties that form the state of a Pod are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Pod names (&amp;lt;StatefulSetName&amp;gt;-&amp;lt;Integer&amp;gt;)&lt;/li&gt;&lt;li&gt;DNS hostnames&lt;/li&gt;&lt;li&gt;volume bindings&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;They are sometimes referred to as the Pods &lt;em&gt;sticky ID&lt;/em&gt;. StatefulSets ensures that these are all predictable and persistent. For example, failed Pods managed by a StatefulSet will be replaced by new Pods with the exact same Pod name, the exact same DNS hostname, and the exact same volumes, even if the replacement Pod is started on a different cluster Node.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that StatefulSets create one Pod at a time, and always wait for previous Pods to be &lt;em&gt;running and ready&lt;/em&gt; before creating the next. Scaling operations are also governed by the same ordered startup rules. This is different from Deployments that use a ReplicaSet controller to start all Pods at the same time, causing potential race conditions. The way StatefulSet controllers do their own self-healing and scaling is architecturally different to Deployments which use a separate ReplicaSet controller for these operations. The reason it is a game changer to know the order in which Pods will be scaled down, as well as that Pods will not be terminated in parallel, is because clustered apps that store data are usually at high risk of losing data if multiple replicas go down at the same time.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Deleting a StatefulSet does not terminate Pods in order. So you may want to scale a StatefulSet to 0 replicas before deleting it. You might also set 10 seconds grace period before terminating to allow applications a chance to flush local buffers and safely commit any writes still in flight.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, Volumes are decoupled from Pods via PersistentVolumes and PersistentVolumeClaims. So volumes have separate lifecycles to Pods and can survive Pod failures and termination operations. When a StatefulSet Pod is created, any volumes it needs are created at the same time and named in a way to connect them to the right Pod. Any time a StatefulSet Pod fails or is terminated, the associated volumes are unaffected. This allows replacement Pods to attach to the same storage as the Pods they&amp;#8217;re replacing, even if the replacement Pod is scheduled to a different cluster Node. Similarly, if a StatefulSet Pod is detected as part of a scale-down operation, subsequent scale-up operations will attach new Pods to the existing volumes that match their names.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since each StatefulSet Pod needs its own unique storage, hence its own PVC, this can be done by volumeClaimTemplate, which dynamically creates a PVC each time a new Pod replica is dynamically created. This eliminates the hassle to have to pre-create a unique PVC for every potential StatefulSet Pod.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/ns-128.png" alt=""/&gt;&lt;figcaption&gt;Namespaces&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Namespaces allows you to partition resource objects. For example, you may create a Namespace called prod and dev. Object names must be unique within Namespaces but not across Namespaces.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/12/ansible-tower-lab-environment-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AWS CDK example in Typescript – provision an AWX server&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2021/01/blockchain-and-di-fi/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Blockchain and DeFi&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>