<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Nodes on Digi Hunch</title><link>https://static.digihunch.com/tag/nodes/</link><description>Recent content in Nodes on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Thu, 17 Apr 2025 14:04:59 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/nodes/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes Platform as a Service and Red Hat OpenShift</title><link>https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/</link><pubDate>Sun, 25 Jun 2023 11:10:15 -0400</pubDate><guid>https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-rosa.webp" alt="Featured image of post Kubernetes Platform as a Service and Red Hat OpenShift" /&gt;&lt;h2 class="wp-block-heading"&gt;The Three-layer model&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes is so complex that it becomes a buzz word itself. I categorize the related work into three layers: a cluster layer, a platform layer and an application layer, by their purposes. The three layers are illustrated as below:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="471px" viewBox="-0.5 -0.5 471 161" style="max-width:100%;max-height:161px;"&gt;&lt;defs&gt;&lt;style type="text/css"&gt;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;&lt;/style&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="470" height="160" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;rect x="210" y="60" width="130" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 80px; margin-left: 211px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Kubernetes Platform&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="84" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Kubernetes Platform&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="210" y="100" width="130" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 120px; margin-left: 211px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Kubernetes Cluster&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="124" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Kubernetes Cluster&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="210" y="20" width="130" height="40" fill="#f5f5f5" stroke="#666666" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 40px; margin-left: 211px;"&gt;&lt;div data-drawio-colors="color: #333333; " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(51, 51, 51); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Application&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="44" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Application&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 380 60 L 375 60 Q 370 60 370 70 L 370 90 Q 370 100 365 100 L 362.5 100 Q 360 100 365 100 L 367.5 100 Q 370 100 370 110 L 370 130 Q 370 140 375 140 L 380 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" transform="translate(370,0)scale(-1,1)translate(-370,0)" pointer-events="all"/&gt;&lt;path d="M 190 100 L 185 100 Q 180 100 180 110 L 180 115 Q 180 120 175 120 L 172.5 120 Q 170 120 175 120 L 177.5 120 Q 180 120 180 130 L 180 135 Q 180 140 185 140 L 190 140" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="0" y="105" width="170" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 120px; margin-left: 1px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;AKS, EKS, self-built cluster&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="124" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;AKS, EKS, self-built cluster&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="375" y="85" width="85" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 83px; height: 1px; padding-top: 100px; margin-left: 376px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;ROSA, ARO&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="418" y="104" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;ROSA, ARO&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 190 60 L 185 60 Q 180 60 180 70 L 180 75 Q 180 80 175 80 L 172.5 80 Q 170 80 175 80 L 177.5 80 Q 180 80 180 90 L 180 95 Q 180 100 185 100 L 190 100" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="10" y="65" width="160" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 80px; margin-left: 11px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;OpenShift Container Platform&lt;br /&gt;Self-managed platform&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="90" y="84" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;OpenShift Container Platfo&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Text is not SVG &amp;#8211; cannot display&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s examine each layer in this model and where the Kubernetes Platform as a Service fits in.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-the-kubernetes-cluster-layer"&gt;The Kubernetes Cluster Layer&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At the bottom, the Kubernetes Cluster layer is the foundational layer. It focus on using self-hosted VMs or cloud resources to build a functional Kubernetes cluster and worker node groups. A functional cluster includes a highly available control plane, as well as scalable node groups that all communicate with the control plane. Cloud Service Providers like AWS and Azure provides managed Kubernetes service, which takes away the complexity (and flexibility as well) of managing control plane components such as etcd store and API server. The managed services also automatically provisions computing nodes and join them into the cluster. The cluster layer may also involve integration with of CNI and CSI, to ensure Pod-to-Pod communication and available storage classes. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Professionals working at this layer are infrastructure experts who understand networking, storage, as well as how to manage cloud resources or VMs, infrastructure as code. On a daily basis, they deal with VPCs/V-Nets, subnets, EBS/Azure Disk, File storage, EC2/Azure VMs, etc. When the team is doing a bad job at this layer, you might see symptoms like unresponsive cluster API, orphaned worker nodes, or kubectl failing to connect to cluster endpoint.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tenants (applications) of the Kubernetes platform does not directly interact with this layer. If you decide to switch CSP vendor, this layer requires 100% re-engineering because the managed Kubernetes service by each CSP is different.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;The Kubernetes Platform Layer&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Platform layer sits in the middle. When organization decides to adopt Kubernetes, they often underestimate the efforts required in this layer. This layer works on a functional cluster, without directly interacting with the underlying cloud resources. This layer involves any Kubernetes abstractions that do not creates tangible business value. Rather, this layer is an enabler. It allows the applications to deploy smoothly, evolve quickly, and more importantly, focus on the business.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Teams working on this layer needs to be Kubernetes experts. On a daily basis, they play with common CNCF toolings, such as Prometheus, ArgoCD, Istio, Cilium, Tekton, Open Policy Agent, etc. They are comfortable with Operators, Helm Charts, Ingress, etc. Inside of the Kubernetes cluster, they also manage the foundational services such as Event streaming (e.g. Kafka), PostgreSQL database (e.g. PostgreSQL), software-defined storage (e.g. Ceph), service mesh (e.g. Istio), Authentication (e.g. Keykloak) , etc. These services act as the infrastructure layer to the business workload. If the team is doing a bad job, you would see data loss with database, observability service not populating data, ingress does not process request, etc. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tenants (application) share services in this layer. If you decide to switch CSP vendor. I estimate 80% of the work at this layer is portable, and 20% requires re-engineering. That is because each CSP offers different external resources, therefor the low level Kubernetes objects in this layer, such as storage classes, load balancers, supported CNIs are different. High level objects such as Kafka remains portable across platforms.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;The Application Layer&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The next layer at the top is application layer. Workloads in this layer are directly linked to the business value. The applications are very diverse. Most of the time, the release team is the main player at this layer. If the organization develops its own application, the software development team also work at this layer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In terms of knowledge, the members of development team are experts in software engineering, and Software Development Life Cycle (SDLC), etc. On a daily basis, they deal with programming languages, product development, build and release. If they screw up their work, expect business errors, such as orders sent to wrong client, incorrect balance sheet, etc. This team has high visibility in the organization due to its direct link to business value.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This layer of work involves multiple tenants. Each tenant is isolated within their own namespace. When you switch CSP vendor, this layer should be readily portable with minimal effort.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is also worth noting that, with solid platform and cluster layers, the team working at this layer do not write bespoke code for networking, observability, authentication and authorization, encryption and many other aspects not relevant to the core business. Once deployed, the application services are resilient, scale to demands, and cost efficient. This layer reaps the benefits of Kubernetes. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kubernetes Platform as a Service&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As the Kubernetes dust is still settling, a builder&amp;#8217;s title may not always reflect which layer she or he focuses on. Today it is pretty common for infrastructure engineers to expand their role into the platform layer, or likewise, a software engineer to drill down to the platform layer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The boundary between platform layer and cluster layer is clear. The cluster layer deals with underlying infrastructure, either in the cloud or on premise. They abstract away the complex infrastructure world from those working with the platform layer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The boundary between platform layer and application layer is a little tricky to articulate. The application layer focuses on implementing the business logics. The platform layer takes care of the functions that are not part of business logic but essential to the business application. Take an HTTP request for example, application developer should not have to write code to terminate TLS (not part of business logic). They should only write the code to process the HTTP request (business logic). TLS termination is delegated to an Ingress, to be configured by platform builders. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The folks working at the Platform layer needs to interface with both sides. They provide Platform as a Service to the Application teams. However, their work appears mostly invisible in an organization. Their effort is oftentimes underestimated. There are several reasons for that. First, the platform layer does not directly create tangible business value. They are just someone else&amp;#8217;s enabler. Second, their building blocks involve a lot of abstractions by Kubernetes API. Third, the idea of platform engineering is newly emerged. There hasn&amp;#8217;t been a populous recognition of its value.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Red Hat OpenShift Container Platform&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The platform team builds the platform with their choice of open-source tools. For &lt;a href="https://static.digihunch.com/2022/09/build-a-kubernetes-cluster/"&gt;clusters&lt;/a&gt; using OpenShift Kubernetes &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/kubernetes-engine"&gt;Engine&lt;/a&gt;, Red Hat introduces Open Shift container platform consisting of Red Hat&amp;#8217;s opinionated (but validated) choice of toolings, for example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;OpenShift Service Mesh: Istio&lt;/li&gt;&#10;&lt;li&gt;OpenShift Streams: Apache Kafka&lt;/li&gt;&#10;&lt;li&gt;OpenShift GitOps: ArgoCD&lt;/li&gt;&#10;&lt;li&gt;OpenShift Container Platform Pipelines: Tekton&lt;/li&gt;&#10;&lt;li&gt;OpenShift Serverless: Knative&lt;/li&gt;&#10;&lt;li&gt;OpenShift Data Foundation: Ceph&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Clients building their clusters with OpenShift Kubernetes Engine may build their own platform with the toolings in the &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift"&gt;OpenShift enterprise Kubernetes container platform&lt;/a&gt;. For more services, check out the &lt;a href="https://docs.openshift.com/container-platform/4.13/welcome/index.html"&gt;documentation&lt;/a&gt; for OpenShift Container Platform. For customers with OpenShift Kubernetes &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/kubernetes-engine"&gt;Engine&lt;/a&gt;, their options to DIY platform are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Entry-Level: Red Hat OpenShift Kubernetes Engine: Enterprise Kubernetes distribution on RHEL CoreOS&lt;/li&gt;&#10;&lt;li&gt;Mid-Level: Red Hat OpenShift Container Platform (RHOCP):&lt;/li&gt;&#10;&lt;li&gt;Plus-Level: Red Hat OpenShift Platform Plus: RHOCP + advanced cluster management, security, data management essentials, enterprise container registry&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;OpenShift runs the business model of Kubernetes PaaS.This is a unique business model that I do not find a matching competitor. Even if you choose to DIY your own platform, the Red Hat&amp;#8217;s choices are still a great reference. The OpenShift enterprise Kubernetes container platform maps perfectly to the platform layer of the three-layer model, aiming to simplify the work in the platform layer.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Managed RedHat OpenShift&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At first, the OpenShift container platform started as a value add-on to the Kubernetes Engine. Now it&amp;#8217;s a separate product line in their business model. In the mean time, OpenShift partners with major CSPs, to develop the cloud service editions, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Red Hat OpenShift on AWS (ROSA)&lt;/li&gt;&#10;&lt;li&gt;Microsoft Azure Red Hat OpenShift (ARO)&lt;/li&gt;&#10;&lt;li&gt;Red Hat OpenShift Dedicated &amp;#8211; on AWS and GCP&lt;/li&gt;&#10;&lt;li&gt;Red Hat OpenShift on IBM Cloud&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These offerings are managed Kubernetes Platform as a Service in the cloud. Since RedHat is the only player in this model, we can refer to them as managed OpenShift services. In addition to an already-confusing world of Kubernetes platform portfolios, these offerings gives consumers even &lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/openshift-cloud-services"&gt;more options&lt;/a&gt;. On AWS for example, users have the following options:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Managed Platform: OpenShift Dedicated, managed by Red Hat&lt;/li&gt;&#10;&lt;li&gt;Managed Platform: Red Hat OpenShift Service on AWS (ROSA), managed by Red Hat and AWS&lt;/li&gt;&#10;&lt;li&gt;Self-built cluster: OpenShift Container Platform&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://pages.awscloud.com/apn-tv-491.html"&gt;This&lt;/a&gt; video discussed more details about these options, such as support model. It is also worth noting that these options tend to be much pricier than managed clusters such as EKS and AKS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since a Managed RedHat Platform makes it easy to deploy, let&amp;#8217;s take ROSA as an example and create a cluster. To enable ROSA in AWS &lt;a href="https://console.aws.amazon.com/rosa/home"&gt;console&lt;/a&gt;, click on &amp;#8220;Getting Started&amp;#8221;. The next page ensures ROSA is enabled and checks other prerequisite such as meeting service quotas and creating ELB service-linked role, as show below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="912" height="1024" src="https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui.webp" alt="" class="wp-image-12926" srcset="https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui.webp 912w, https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui-267x300.webp 267w, https://static.digihunch.com/wp-content/uploads/2023/06/rosa-ui-768x862.webp 768w" sizes="auto, (max-width: 912px) 100vw, 912px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now, with an AWS account (and ROSA enabled), a RedHat account, and the &lt;a href="https://docs.redhat.com/en/documentation/red_hat_openshift_service_on_aws/4/html/rosa_cli/rosa-get-started-cli"&gt;rosa-cli&lt;/a&gt; utility, we can create a cluster with just a few commands. As a note, be wary of the cost and do not forget to delete the cluster afterwards.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Create a ROSA cluster&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With the following set of commands, we can kick off cluster creation, using STS. We can bring our own VPC, so long as it meets certain &lt;a href="https://docs.openshift.com/rosa/rosa_planning/rosa-sts-aws-prereqs.html#rosa-vpc_rosa-sts-aws-prereqs"&gt;prerequisites&lt;/a&gt;. I use the Terraform template in the &lt;a href="https://github.com/digihunch/vpc-base"&gt;vpc-base&lt;/a&gt; project, to create the underlying VPC. We&amp;#8217;ll need the followings from this template:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The CIDR range of the VPC: as &lt;a href="https://github.com/digihunch/cloudkube/blob/9d8667c81fc0989e6e48fba9ed5a87ab761d4044/aws_vpc/variables.tf#L3"&gt;input&lt;/a&gt; with a default&lt;/li&gt;&#10;&lt;li&gt;The subnet Ids of the private subnet to place, printed in the &lt;a href="https://github.com/digihunch/cloudkube/blob/9d8667c81fc0989e6e48fba9ed5a87ab761d4044/aws_vpc/output.tf#L18"&gt;output&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The subnets are private subnets, because we want to provision the cluster with private node and private endpoint. When we use rosa CLI, we provide the CIDR and subnet IDs.&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;&lt;span style="color:#75715e"&gt;# start with AWS cli configured to the correct profile&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa login &lt;span style="color:#75715e"&gt;# with redhat account and past token&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa create account-roles --mode auto -y &lt;span style="color:#75715e"&gt;# this command creates the IAM roles ManagedOpenShift-*-Role, with RedHat account as trust entity&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa verify permissions &lt;span style="color:#75715e"&gt;# optional&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa verify quota &lt;span style="color:#75715e"&gt;# optional&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ROSA_CLUSTER_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;dhc&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;OPENSHIFT_VERSION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;4.13.4 &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AWS_ACCOUNT_ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;aws sts get-caller-identity --query Account --output text&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AWS_DEFAULT_REGION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;us-east-1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa create cluster --sts --private &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --cluster-name $ROSA_CLUSTER_NAME &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --multi-az &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --private-link &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --region $AWS_DEFAULT_REGION &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --version $OPENSHIFT_VERSION &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --enable-autoscaling &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --min-replicas &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --max-replicas &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --compute-machine-type m5.xlarge &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --machine-cidr 147.206.0.0/16 &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; --subnet-ids subnet-052852a1fb4d7d2ad,subnet-06d8d40ae39d55c47,subnet-0f67ce08bc588012c&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The CLI will pick up the correct VPC by CIDR, and prompt you to confirm creation of private cluster. After the command kicks off, it will wait for OIDC provider creation, and role creation. Then it uses a Terraform template to create the related resources including VPC. Use this command to check status:&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;rosa list clusters&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa describe cluster -c dhc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When the second command displays the state of waiting (Waiting for OIDC configuration), we can create OIDC provider:&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;rosa create operator-roles -c $ROSA_CLUSTER_NAME --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa create oidc-provider -c $ROSA_CLUSTER_NAME --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Throughout the process, we can monitor the install log (terraform output) with:&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;rosa logs install -c dhc --watch&#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 log, you might see errors with terminals connecting to the terraform backend, which doesn’t necessarily indicate a cluster creation error. Always check the cluster state until it reports success. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kick the tires &lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Eventually the describe cluster command will show ready state. We can now create an admin user:&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;rosa create admin -c $ROSA_CLUSTER_NAME&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above prints an &lt;code&gt;oc&lt;/code&gt; command (&lt;a href="https://access.redhat.com/documentation/en-us/openshift_container_platform/4.2/html/cli_tools/openshift-cli-oc"&gt;OpenShift CLI&lt;/a&gt;, equivalent to kubectl) with password to log in. Let&amp;#8217;s examine the cluster with oc. Because it is a private cluster, the endpoint is not available publicly. However, it is accessible from the Bastion host. Use the SSM Session Manager technique from my &lt;a href="https://static.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;previous post&lt;/a&gt; to SSH to the Bastion Host, which should have &lt;code&gt;oc&lt;/code&gt; installed. To install &lt;code&gt;oc&lt;/code&gt; yourself, use HomeBrew on Mac. On Linux or Windows, log on to &lt;a href="https://console.redhat.com/openshift/downloads"&gt;OpenShift console&lt;/a&gt;, go to Downloads on the left pannel and find it out under CLI tools.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;code&gt;oc&lt;/code&gt; command may report insecure TLS on the login URL. Wait for a few minutes for the certificate to come off as safe. Once you run the &lt;code&gt;oc&lt;/code&gt; command with password, it should return &amp;#8220;Login successful&amp;#8221; and then we can connect to the cluster:&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;$ oc get node &lt;span style="color:#75715e"&gt;# or kubectl get node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME STATUS ROLES AGE VERSION&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-135-41.ec2.internal Ready,SchedulingDisabled infra,worker 3m5s v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-155-141.ec2.internal Ready control-plane,master 25m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-156-81.ec2.internal Ready worker 19m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-164-21.ec2.internal Ready infra,worker 3m3s v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-179-90.ec2.internal Ready worker 19m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-191-118.ec2.internal Ready,SchedulingDisabled control-plane,master 26m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-192-232.ec2.internal Ready worker 19m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-193-198.ec2.internal Ready infra,worker 3m20s v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ip-147-206-218-114.ec2.internal Ready control-plane,master 26m v1.26.5+7d22122&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;You can use oc the same way you&amp;#8217;d use kubectl. Both works through &lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/socks5-proxy-access-api/"&gt;SOCK5 proxy.&lt;/a&gt; In the meantime, log in to the &lt;a href="https://console.redhat.com/openshift"&gt;RedHat console&lt;/a&gt; with your Red Hat credential, you can see the cluster in Ready state as well:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="330" src="https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui.webp" alt="" class="wp-image-12927" srcset="https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui-300x97.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/06/openshift-ui-768x248.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;code&gt;rosa create admin&lt;/code&gt; command creates a &lt;code&gt;htpasswd&lt;/code&gt; type (username-password) of identity provider (IdP) with a user named cluster-admin and a preset password. In real life however, we often configure third party IdP with OIDC integration. I&amp;#8217;ll have to leave this to the &lt;a href="https://static.digihunch.com/2023/07/authenticate-kube-apiserver-via-oidc/"&gt;next blog post&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We shall see the nodes as EC2 instances from AWS console as well. Note that there are three roles: control-plane, worker and infra. The &lt;a href="https://docs.openshift.com/container-platform/4.13/nodes/nodes/nodes-nodes-creating-infrastructure-nodes.html"&gt;infra nodes&lt;/a&gt; are for infrastructure services. These services (Ingress Controller, GitOps, Pipeliens) are the ones in the platform player as we discussed above. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are many &lt;a href="https://access.redhat.com/solutions/6347302"&gt;customizations&lt;/a&gt; you can make in this installation process and I&amp;#8217;d have to defer to the &lt;a href="https://docs.openshift.com/rosa/rosa_planning/rosa-sts-aws-prereqs.html"&gt;ROSA documentation&lt;/a&gt;. To clean up, use the following ROSA command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa remove cluster -c dhc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The output also gives you the command to delete operator roles and OIDC provider, for example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa delete operator-roles -c 23o4u3j98tqmlbtjo612opb7a4bbim5f --mode auto --yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rosa delete oidc-provider -c 23o4u3j98tqmlbtjo612opb7a4bbim5f --mode auto --yes&#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 destroy the VPCs using terraform.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;ROSA with HCP&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Update Oct 2023:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The deployment above provisioned a few nodes for control plane, which add to the overall time to provision a cluster. In Aug 2023, there is a new option Hosted Control Plane (HCP) that came to allow users to provision a hosted control plane. This results in cost savings and shorter time to provision a cluster. &lt;a href="https://docs.aws.amazon.com/ROSA/latest/userguide/rosa-deployment-options.html"&gt;Here&lt;/a&gt; is a table of comparison between the ROSA with HCP and ROSA classic.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Final words&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post, I discussed the three-layer model and pointed out that platform layer isn&amp;#8217;t as visible as the other two. I also experimented ROSA as a turn-key Kubernetes platform with its opinionated stack of services.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some misinformed organizations even skip the entire platform layer in their estimate of effort. They build a cluster, ran a hello-world service and assumes they can start putting applications on the Kubernetes cluster. There are also customers who purchased the entire Managed OpenShift platform but only use it as a cluster. Yikes!&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The concept of Kubernetes platform, or generally platform engineering is still spreading. The consulting team that I worked in full-time last year re-branded itself as platform engineering. Marketings are pushing it. Builders are doing it. We&amp;#8217;ll keep an eye, on whether customers are buying 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/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Connect kubectl to private Kubernetes cluster in EKS and AKS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/07/oauth-2-0-and-oidc-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;OAuth 2.0 and OIDC 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Connect kubectl to private Kubernetes cluster in EKS and AKS</title><link>https://static.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/</link><pubDate>Sat, 10 Jun 2023 19:31:00 -0400</pubDate><guid>https://static.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-kubectl-private-cluster.webp" alt="Featured image of post Connect kubectl to private Kubernetes cluster in EKS and AKS" /&gt;&lt;p class="wp-block-paragraph"&gt;Managed Kubernetes services give user a cluster endpoint and a number of worker nodes, with the choice. For each access, users have the choice of making them publicly available, or keeping them on private networking. In my opinion, any deployment beyond personal hobbies, should use Kubernetes private cluster, with both cluster endpoint and worker nodes on private subnet. There is no reason to expose computing nodes or Kubernetes management traffic publicly. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For worker nodes, it is fairly easy to put VMs on private network, but many companies still have the cluster endpoint exposed publicly. There are usually two reasons. First, their CI/CD agent is hosted somewhere else on the Internet (instead of on private network with private connectivity to Kubernetes cluster) and need to access Kubernetes cluster endpoint. Second, when the cluster needs to connect with third-party identity provider as OIDC provider, a two-way communication is needed. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There is a classic pattern of using a public bastion host (jump box), with a bastion host on the public subnet, routable to the private endpoint of managed Kubernetes service. Clients then connect to the bastion host via port 22 on a public IP address. The authentication is based on SSH key pair, or worse, password. The port forwarding (aka &lt;a href="https://www.ssh.com/academy/ssh/tunneling-example"&gt;SSH tunnelling&lt;/a&gt;) capability enables all the magics. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Exposing a jump box in the public subnet with RSA key authentication is still not favourable. In this post, I&amp;#8217;ll examine some secure patterns to connect to private endpoint with improved security posture. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-aws-options"&gt;AWS options&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are two problems. First, how to establish connectivity to the Bastion host in a private subnet. Second, how to use the Bastion host to proxy traffic to the cluster endpoint also in private subnet. To the first problem, there are two potential solutions: SSM Session Manager, and EC2 Instance Connect (EIC) with EIC endpoint (EICE).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;SSM Session Manager was introduce in 2018. It runs an agent on the EC2, which initiates a connection to the SSM endpoint on the AWS side. This connection enables not only Session Manager, but also other Systems Managers (SSM) services such as Fleet Manager, Patch Manager and State Manager. The problem that session manager originally addresses is server management.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS launched EC2 Instance Connect (EIC) in 2019, and EIC Endpoint (EICE) in 2023. EIC addresses the problem with managing SSH key pairs at scale. It dynamically generates an SSH key pair for server access, based on IAM permission. However, it still requires an instance to have its SSH port publicly accessible. With EICE, it is no longer a requirement. In the &lt;a href="https://aws.amazon.com/blogs/compute/secure-connectivity-from-public-to-private-introducing-ec2-instance-connect-endpoint-june-13-2023/?utm_content=bufferfded7&amp;amp;utm_medium=social&amp;amp;utm_source=linkedin.com&amp;amp;utm_campaign=buffer"&gt;diagram&lt;/a&gt;, EICE is placed in a private subnet, allowing EICE service to reach private instances at their SSH port. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here is a comparison of the two:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;/th&gt;&lt;th&gt;EC2 Instance Connect (EIC) with EIC Endpoint&lt;/th&gt;&lt;th&gt;SSM Session Manager&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Location of Bastion host&lt;/td&gt;&lt;td&gt;Private Subnet.&lt;/td&gt;&lt;td&gt;Private Subnet&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Need Ingress Port&lt;/td&gt;&lt;td&gt;Yes. Port 22 must open to the endpoint.&lt;/td&gt;&lt;td&gt;No. SSM agent initiate outbound connection from the instance&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Traffic Path&lt;/td&gt;&lt;td&gt;AWS CLI → AWS EIC ES → EICE→EC2 Inst&lt;/td&gt;&lt;td&gt;AWS CLI → AWS SSM ES → SSM ← EC2 Inst&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Authentication&lt;/td&gt;&lt;td&gt;AWS IAM and ephemeral SSH key when using AWS CLI directly&lt;br&gt;AWS IAM and long-term SSH key when using SSH proxy command&lt;/td&gt;&lt;td&gt;AWS IAM and long-term SSH key when using AWS CLI directly or SSH proxy command&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Work with OpenSSH&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;td&gt;Yes&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Cost&lt;/td&gt;&lt;td&gt;There is no additional cost for using EIC.&lt;/td&gt;&lt;td&gt;No additional cost, unless private SSM Endpoint.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s take a look at each option.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;EC2 Instance Connect&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To use EIC, pick an AMI that has it pre-installed and ensure instance profile has correct policy, as the document states &lt;a href="https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-connect-prerequisites.html"&gt;here&lt;/a&gt;. AWC CLI will make use of local OpenSSL client. So make sure there connection at port 22 is open. To make it work with EC2 instance on a private subnet, create an EC2 Instance Connect Endpoint on the VPC, and ensure that the security group of EC2 allows port 22 from the Endpoint. Run this command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws ec2-instance-connect ssh --instance-id i-00ea30a6e02db33fe&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above simply generates a key pair internally, add the public key to the server side, and connect with SSH from the client side. The command takes you to an SSH session. Checking &lt;code&gt;ps -ef | grep ssh&lt;/code&gt; on the client machine, you can see the full parameter of SSH, including the location of the ephemeral private key. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="95" src="https://static.digihunch.com/wp-content/uploads/2023/06/ssh-process.webp" alt="" class="wp-image-12921" srcset="https://static.digihunch.com/wp-content/uploads/2023/06/ssh-process.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/06/ssh-process-300x28.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/06/ssh-process-768x71.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, if you use AWS CLI open-tunnel as proxy command to ssh, then you&amp;#8217;d still have to use the key pair used to create the EC2 instance. As suggested at the bottom of &lt;a href="https://aws.amazon.com/blogs/compute/secure-connectivity-from-public-to-private-introducing-ec2-instance-connect-endpoint-june-13-2023/?utm_content=bufferfded7&amp;amp;utm_medium=social&amp;amp;utm_source=linkedin.com&amp;amp;utm_campaign=buffer"&gt;this&lt;/a&gt; blog post, the command is:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh ec2-user@&lt;span style="color:#f92672"&gt;[&lt;/span&gt;INSTANCE&lt;span style="color:#f92672"&gt;]&lt;/span&gt; -i &lt;span style="color:#f92672"&gt;[&lt;/span&gt;SSH-KEY&lt;span style="color:#f92672"&gt;]&lt;/span&gt; -o ProxyCommand&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;aws ec2-instance-connect open-tunnel --instance-id %h&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This is a bummer, because with native SSH tool you do not get the primary benefit of EIC &amp;#8211; ephemeral key pair. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;SSM Session Manager&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now let&amp;#8217;s look at SSM session manager. Similarly, it needs an agent installed and &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/setup-instance-permissions.html"&gt;IAM role&lt;/a&gt; configured. You can connect to from web console but more importantly, from AWS CLI:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws ssm start-session --target i-0531b19bec8ad022d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This command takes you to an SSH session with user &lt;code&gt;ssm-user&lt;/code&gt;, without starting an OpenSSH client process locally. User do not have to manage key pair. There is also a &lt;a href="https://docs.aws.amazon.com/systems-manager/latest/userguide/session-manager-getting-started-enable-ssh-connections.html"&gt;document&lt;/a&gt; about using this command as proxy command, which uses an SSM document. I have one of the SSH config entry as:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;host i-* mi-*&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProxyCommand sh -c &lt;span style="color:#e6db74"&gt;&amp;#34;aws ssm start-session --target %h --document-name AWS-StartSSHSession --parameters &amp;#39;portNumber=%p&amp;#39;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User ec2-user&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; IdentityFile ~/.ssh/id_rsa&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This allows me to directly SSH to client using OpenSSL client (e.g. &lt;code&gt;ssh i-0531b19bec8ad022d&lt;/code&gt;) by Instance ID. With this, I also need to specify my own OS user and matching private key.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I know I will use the OpenSSH client a lot from pipelines because it is very powerful. In both options, I have to live with managing key pairs myself. With SSM session manager&amp;#8217;s proxy command, the instance does not need port 22 to open, which is a great advantage, in terms of security and operation. SSM Session Manager is a winner.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;SOCKS5 proxy for kubectl&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Either SSM Session Manager or EIC with EICE enables an SSH tunnel with key encryption between client (a local computer or a pipeline agent). On top of the SSH tunnel, we can build a &lt;a href="https://en.wikipedia.org/wiki/SOCKS#SOCKS5"&gt;SOCKS5&lt;/a&gt; proxy. Kubernetes document has a good &lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/socks5-proxy-access-api/"&gt;page&lt;/a&gt; on how to do this. I managed to get this to work with a few gotchas.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="827px" viewBox="-0.5 -0.5 827 512" style="max-width:100%;max-height:512px;"&gt;&lt;defs&gt;&lt;linearGradient x1="0%" y1="100%" x2="0%" y2="0%" id="mx-gradient-f78e04-1-d05c17-1-s-0"&gt;&lt;stop offset="0%" style="stop-color: rgb(208, 92, 23); stop-opacity: 1;"/&gt;&lt;stop offset="100%" style="stop-color: rgb(247, 142, 4); stop-opacity: 1;"/&gt;&lt;/linearGradient&gt;&lt;style type="text/css"&gt;@import url(https://fonts.googleapis.com/css?family=Architects+Daughter);&amp;#xa;&lt;/style&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="825" height="510" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;rect x="25" y="78.88" width="270" height="251.12" rx="25.11" ry="25.11" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/&gt;&lt;rect x="435" y="60" width="370" height="240" rx="24" ry="24" fill="#dae8fc" stroke="#6c8ebf" stroke-width="2" pointer-events="all"/&gt;&lt;rect x="455" y="80" width="260" height="200" rx="20" ry="20" fill="#f5f5f5" stroke="#666666" stroke-width="2" pointer-events="all"/&gt;&lt;g fill="#333333" font-family="Helvetica" text-anchor="middle" font-size="12px"&gt;&lt;text x="584.5" y="97.5"&gt;Private Subnet&lt;/text&gt;&lt;/g&gt;&lt;rect x="475" y="123.75" width="120" height="130" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 131px; margin-left: 476px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Bastion Host&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="535" y="143" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Bastion Host&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="75" y="110" width="190" height="193.75" fill="#fff2cc" stroke="#d6b656" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 188px; height: 1px; padding-top: 117px; margin-left: 76px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Pipeline Agent&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="170" y="129" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Pipeline Agent&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 351.25 68 C 351.25 66.34 353.49 65 356.25 65 C 357.58 65 358.85 65.32 359.79 65.88 C 360.72 66.44 361.25 67.2 361.25 68 L 361.25 309.5 C 361.25 311.16 359.01 312.5 356.25 312.5 C 353.49 312.5 351.25 311.16 351.25 309.5 Z" fill="#bac8d3" stroke="#23445d" stroke-miterlimit="10" transform="rotate(-90,356.25,188.75)" pointer-events="all"/&gt;&lt;path d="M 361.25 68 C 361.25 69.66 359.01 71 356.25 71 C 353.49 71 351.25 69.66 351.25 68" fill="none" stroke="#23445d" stroke-miterlimit="10" transform="rotate(-90,356.25,188.75)" pointer-events="all"/&gt;&lt;rect x="312.5" y="163.75" width="90" height="20" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 174px; margin-left: 314px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;SSH Tunnel&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="358" y="177" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SSH Tunnel&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 513.09 207.45 L 511.26 176.65 L 513.7 176.11 L 515.76 178.18 C 516.3 178.12 516.83 178.15 517.37 178.25 L 518.13 176.72 L 520.42 176.27 L 522.1 177.3 L 522.6 180.77 C 523.56 181.65 524.4 182.82 525.08 184.21 L 527.37 184.71 L 528.05 186.81 L 526.83 189.64 C 527.07 191.14 527.15 192.69 527.06 194.23 L 528.66 196.75 L 528.21 199.5 L 525.92 200.03 C 525.37 201.1 524.73 202.03 524.01 202.79 L 524.01 206.34 L 522.56 207.22 L 519.96 207.6 L 517.44 204.08 L 514.92 207.52 Z M 517.9 192.62 L 517.21 189.79 C 516.97 189.6 516.7 189.72 516.5 190.12 C 516.31 190.52 516.2 191.14 516.22 191.78 C 516.29 192.51 516.57 193.09 516.93 193.27 C 517.29 193.45 517.67 193.2 517.9 192.62 Z" fill="#697079" stroke="#697079" stroke-width="0.46" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="505" y="159.91" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 529.43 191.24 L 527.6 160.44 L 530.04 159.91 L 532.1 161.97 C 532.63 161.92 533.17 161.94 533.7 162.05 L 534.47 160.52 L 536.76 160.06 L 538.44 161.09 L 538.93 164.57 C 539.89 165.45 540.74 166.62 541.41 168.01 L 543.7 168.51 L 544.39 170.61 L 543.17 173.44 C 543.41 174.93 543.49 176.48 543.4 178.02 L 545 180.55 L 544.54 183.3 L 542.25 183.83 C 541.71 184.9 541.06 185.83 540.34 186.58 L 540.34 190.14 L 538.89 191.02 L 536.3 191.4 L 533.78 187.88 L 531.26 191.32 Z M 534.24 176.42 L 533.55 173.59 C 533.31 173.4 533.04 173.52 532.84 173.92 C 532.64 174.32 532.53 174.94 532.56 175.58 C 532.63 176.31 532.9 176.89 533.26 177.07 C 533.62 177.25 534 177 534.24 176.42 Z" fill="#697079" stroke="#697079" stroke-width="0.46" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 529.43 191.24 L 527.9 190.48 L 527.52 186.93 C 526.49 186.07 525.58 184.89 524.85 183.45 L 522.63 183.07 L 521.79 180.77 L 523.24 177.95 C 522.97 176.43 522.87 174.86 522.94 173.28 L 521.34 170.69 L 521.79 168.47 L 524.08 168.13 C 524.63 166.9 525.3 165.83 526.07 164.95 L 526.07 161.51 L 527.6 160.44 L 529.27 162.58 C 530.33 162.26 531.42 162.26 532.48 162.58 L 534.47 160.52 L 535.99 161.44 L 536.22 164.8 C 537.17 165.7 538 166.9 538.66 168.32 L 541.03 168.7 L 541.87 171.07 L 540.57 173.82 C 540.82 175.34 540.9 176.92 540.8 178.48 L 542.56 181.31 L 542.02 183.91 L 539.81 183.91 C 539.24 185.16 538.55 186.25 537.75 187.12 L 537.67 190.71 L 536.22 191.4 L 534.16 189.03 C 533.18 189.36 532.17 189.41 531.18 189.18 Z M 532.02 178.94 C 532.55 178.94 533.05 178.6 533.41 177.99 C 533.77 177.38 533.96 176.57 533.93 175.73 C 533.75 174.32 532.73 173.24 531.49 173.13 C 531 173.07 530.52 173.37 530.21 173.93 C 529.89 174.49 529.77 175.25 529.89 175.96 C 529.91 177.54 530.84 178.84 532.02 178.94" fill="#c0c6d2" stroke="#697079" stroke-width="0.46" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 513.09 207.45 L 511.56 206.68 L 511.18 203.13 C 510.15 202.28 509.24 201.09 508.51 199.65 L 506.3 199.27 L 505.46 196.98 L 506.91 194.15 C 506.64 192.64 506.53 191.06 506.6 189.49 L 505 186.89 L 505.46 184.67 L 507.75 184.33 C 508.29 183.1 508.96 182.03 509.73 181.16 L 509.73 177.72 L 511.26 176.65 L 512.94 178.79 C 514 178.46 515.09 178.46 516.15 178.79 L 518.13 176.72 L 519.66 177.64 L 519.89 181 C 520.84 181.91 521.67 183.1 522.33 184.52 L 524.69 184.9 L 525.53 187.27 L 524.24 190.02 C 524.48 191.54 524.56 193.12 524.47 194.68 L 526.22 197.51 L 525.69 200.11 L 523.47 200.11 C 522.91 201.36 522.21 202.45 521.41 203.32 L 521.34 206.91 L 519.89 207.6 L 517.82 205.23 C 516.85 205.56 515.84 205.62 514.85 205.38 Z M 515.69 195.14 C 516.21 195.14 516.71 194.8 517.07 194.19 C 517.43 193.59 517.62 192.77 517.6 191.93 C 517.41 190.52 516.4 189.44 515.15 189.33 C 514.66 189.28 514.19 189.57 513.87 190.13 C 513.55 190.69 513.43 191.45 513.55 192.16 C 513.58 193.74 514.5 195.04 515.69 195.14" fill="#c0c6d2" stroke="#697079" stroke-width="0.46" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 530.06 204.96 L 529.21 190.65 L 530.34 190.4 L 531.3 191.36 C 531.55 191.34 531.8 191.35 532.04 191.4 L 532.4 190.69 L 533.46 190.48 L 534.24 190.95 L 534.47 192.57 C 534.92 192.98 535.31 193.52 535.62 194.17 L 536.69 194.4 L 537.01 195.38 L 536.44 196.69 C 536.55 197.38 536.59 198.1 536.55 198.82 L 537.29 199.99 L 537.08 201.27 L 536.02 201.52 C 535.76 202.01 535.46 202.44 535.13 202.79 L 535.13 204.44 L 534.45 204.85 L 533.25 205.03 L 532.08 203.4 L 530.91 204.99 Z Z M 532.29 198.07 L 531.97 196.76 C 531.6 196.9 531.4 197.3 531.51 197.68 C 531.7 198.03 532 198.18 532.29 198.07 Z" fill="#697079" stroke="#697079" stroke-width="0.46" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 530.06 204.96 L 529.35 204.6 L 529.17 202.95 C 528.69 202.56 528.27 202.01 527.93 201.34 L 526.9 201.16 L 526.51 200.09 L 527.18 198.78 C 527.06 198.08 527.01 197.35 527.04 196.62 L 526.3 195.41 L 526.51 194.38 L 527.57 194.22 C 527.83 193.65 528.14 193.15 528.5 192.75 L 528.5 191.15 L 529.21 190.65 L 529.98 191.65 C 530.48 191.49 530.98 191.49 531.47 191.65 L 532.4 190.69 L 533.11 191.11 L 533.21 192.68 C 533.66 193.1 534.04 193.65 534.35 194.31 L 535.45 194.49 L 535.84 195.59 L 535.23 196.87 C 535.35 197.57 535.38 198.31 535.34 199.03 L 536.16 200.34 L 535.91 201.55 L 534.88 201.55 C 534.61 202.13 534.29 202.64 533.92 203.04 L 533.89 204.71 L 533.21 205.03 L 532.26 203.93 C 531.8 204.09 531.33 204.11 530.87 204 Z M 531.26 199.24 C 531.54 199.3 531.82 199.16 532 198.85 C 532.18 198.55 532.24 198.13 532.15 197.75 C 532.03 197.04 531.56 196.54 531.02 196.54 C 530.8 196.55 530.59 196.7 530.44 196.95 C 530.3 197.2 530.24 197.53 530.27 197.86 C 530.32 198.57 530.73 199.15 531.26 199.24 Z" fill="#c0c6d2" stroke="#697079" stroke-width="0.46" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 215px; margin-left: 525px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;SSHD&lt;br /&gt;Process&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="525" y="227" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SSHD&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 195 204.89 L 195 169.8 L 213.58 165.22 L 229.15 169.93 L 229.15 205.42 L 210.83 209.74 Z" fill="#abb4c5" stroke="none" pointer-events="all"/&gt;&lt;path d="M 195 169.8 L 213.58 165.22 L 229.15 169.93 L 210.83 174.39 Z" fill="#b8bfd1" stroke="none" pointer-events="all"/&gt;&lt;path d="M 210.83 174.39 L 229.15 169.93 L 229.15 205.42 L 210.83 209.74 Z" fill="#5b6369" stroke="none" pointer-events="all"/&gt;&lt;path d="M 211.02 193.68 C 207.61 192.37 204.34 190.7 201.28 188.71 C 199.81 187.69 198.56 186.38 197.62 184.86 C 196.97 183.67 196.63 182.34 196.65 180.98 C 196.59 179.53 196.62 178.07 196.73 176.61 C 196.8 175.31 196.94 174.02 197.15 172.74 C 197.34 172.03 197.63 171.35 198.01 170.72 C 198.8 170.18 199.71 169.74 200.68 169.41 C 202.34 168.85 204.02 168.36 205.73 167.97 C 207.51 167.49 209.32 167.14 211.15 166.92 C 212.39 166.77 213.64 166.72 214.89 166.79 C 216.92 166.94 218.92 167.38 220.83 168.1 L 227.03 170.38 C 227.58 171.28 227.93 172.29 228.08 173.34 C 228.25 174.75 228.35 176.16 228.37 177.58 C 228.47 180.71 228.38 183.84 228.1 186.95 C 228.07 188.33 227.6 189.66 226.77 190.75 C 225.56 192.09 223.92 192.97 222.14 193.24 C 220.71 193.46 219.27 193.57 217.82 193.55 Z" fill-opacity="0.4" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="195" y="165.22" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 196.83 175.17 L 196.83 174.25 L 208.74 177.92 L 208.74 178.84 Z M 198.79 202.93 L 198.79 202.01 L 206.12 204.11 L 206.12 205.02 Z" fill="#52595e" stroke="none" pointer-events="all"/&gt;&lt;path d="M 195 204.89 L 195 169.8 L 213.58 165.22 L 229.15 169.93 L 229.15 205.42 L 210.83 209.74 Z" fill="none" stroke="#000000" stroke-width="0.31" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="195" y="165.22" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;ellipse cx="224.81" cy="202.43" rx="9.839712136081127" ry="9.846174392076122" fill="#008cce" stroke="none" pointer-events="all"/&gt;&lt;path d="M 218.09 195.38 C 218.19 195.65 218.32 195.91 218.49 196.15 C 218.76 196.18 219.02 196.26 219.26 196.4 C 219.2 196.67 219.31 196.96 219.55 197.11 C 219.74 197.22 219.86 197.4 219.89 197.61 C 219.9 197.72 219.95 197.82 220.03 197.88 C 220.11 197.95 220.21 197.99 220.32 197.98 C 220.44 197.96 220.56 197.95 220.69 197.95 C 220.95 197.92 221.22 197.97 221.45 198.11 L 222.28 198.71 C 222.41 198.8 222.51 198.94 222.55 199.1 C 222.59 199.25 222.59 199.41 222.55 199.56 C 222.48 199.58 222.41 199.57 222.35 199.54 C 222.3 199.41 222.2 199.3 222.08 199.24 C 221.92 199.18 221.74 199.18 221.58 199.24 C 221.37 199.36 221.12 199.4 220.88 199.36 C 220.68 199.37 220.5 199.47 220.38 199.63 C 220.29 199.76 220.14 199.84 219.98 199.86 C 219.82 199.87 219.67 199.81 219.55 199.69 C 219.46 199.56 219.3 199.49 219.13 199.51 C 218.97 199.52 218.83 199.62 218.76 199.77 C 218.67 199.96 218.62 200.17 218.6 200.37 C 218.6 200.64 218.55 200.91 218.45 201.16 C 218.4 201.29 218.3 201.39 218.18 201.46 C 217.99 201.52 217.87 201.7 217.89 201.9 C 218.05 202.09 218.17 202.3 218.25 202.53 C 218.32 202.67 218.27 202.84 218.12 202.93 C 217.97 203.05 217.9 203.24 217.93 203.43 C 217.7 203.44 217.47 203.4 217.25 203.33 C 217.17 203.22 217.05 203.14 216.92 203.1 C 216.8 203.07 216.67 203.12 216.59 203.22 C 216.44 203.43 216.33 203.67 216.27 203.93 C 216.22 204.1 216.19 204.28 216.19 204.46 C 216.36 204.78 216.45 205.13 216.43 205.5 L 216.56 206.56 C 216.57 206.7 216.54 206.84 216.46 206.96 C 216.38 207.07 216.27 207.14 216.14 207.18 C 214.05 203.26 214.84 198.43 218.08 195.38 Z" fill="#00527f" stroke="none" pointer-events="all"/&gt;&lt;path d="M 230.12 194.25 C 230.08 194.4 229.99 194.53 229.86 194.61 C 229.74 194.66 229.61 194.69 229.49 194.69 L 228.76 194.78 C 228.44 194.75 228.12 194.78 227.82 194.89 C 227.85 194.99 227.81 195.11 227.72 195.17 C 227.64 195.23 227.52 195.24 227.43 195.18 C 227.24 195.19 227.06 195.27 226.93 195.41 C 226.84 195.52 226.71 195.59 226.56 195.61 C 226.36 195.65 226.19 195.78 226.1 195.97 C 226.01 196.13 225.85 196.16 225.74 196.05 C 225.67 195.9 225.62 195.74 225.61 195.58 C 225.48 195.27 225.24 195.01 224.94 194.85 C 224.64 194.72 224.35 194.54 224.1 194.32 C 223.94 194.2 223.84 194.01 223.84 193.81 C 223.83 193.69 223.84 193.57 223.88 193.45 C 223.78 193.25 223.57 193.1 223.34 193.08 C 223.17 193.04 223.03 192.91 222.97 192.75 C 225.5 192.28 228.12 192.82 230.26 194.25 Z" fill="#64aadc" stroke="none" pointer-events="all"/&gt;&lt;path d="M 234.25 205.1 C 234.22 204.79 234.1 204.5 233.91 204.26 C 233.71 204.24 233.51 204.34 233.41 204.52 C 233.25 204.76 233 204.92 232.72 204.96 C 232.52 204.74 232.56 204.44 232.81 204.26 C 232.97 204.02 233.01 203.71 232.92 203.43 C 232.9 203.31 232.85 203.2 232.79 203.1 C 232.6 202.91 232.46 202.69 232.39 202.43 C 232.21 202.55 232 202.53 231.84 202.39 C 231.68 202.25 231.59 202.01 231.61 201.77 C 231.62 201.48 231.52 201.21 231.32 201 C 231.34 200.54 231.2 200.1 230.92 199.73 C 230.65 199.46 230.28 199.29 229.89 199.27 C 229.56 199.27 229.29 199.08 229.28 198.83 C 229.27 198.59 229.53 198.38 229.86 198.38 C 230.36 198.43 230.86 198.42 231.35 198.34 C 231.83 198.28 232.29 198.13 232.72 197.91 L 233.18 197.27 C 234.61 199.62 235 202.45 234.25 205.1 Z" fill="#0a79b9" stroke="none" pointer-events="all"/&gt;&lt;path d="M 233.25 207.56 C 233.02 207.84 232.72 208.06 232.39 208.18 C 232.12 208.26 231.93 208.5 231.92 208.78 C 231.89 208.88 231.86 208.83 231.84 208.64 C 231.81 208.45 231.79 208.14 231.79 207.78 C 231.83 207.45 231.95 207.13 232.15 206.85 C 232.36 206.65 232.55 206.43 232.72 206.19 C 232.78 206.09 232.78 205.97 232.72 205.87 C 232.66 205.77 232.55 205.72 232.44 205.73 C 232.32 205.74 232.23 205.81 232.19 205.92 C 231.94 206.21 231.72 206.52 231.52 206.85 C 231.2 207.34 230.73 207.54 230.28 207.39 C 230.02 207.25 229.72 207.19 229.42 207.18 C 229.24 207.25 229.13 207.37 229.12 207.49 C 229.11 207.62 229.19 207.74 229.36 207.82 C 229.6 207.9 229.82 208.07 229.95 208.29 C 230.16 208.65 229.74 209.01 228.93 209.18 C 228.37 209.2 227.82 209.3 227.29 209.49 C 226.99 209.77 226.78 210.14 226.69 210.55 C 226.6 210.73 226.1 210.87 225.5 210.88 C 225.07 210.82 224.63 210.84 224.21 210.95 C 223.86 211.08 223.46 211.01 223.17 210.78 C 223.1 210.64 222.96 210.55 222.81 210.54 C 222.65 210.53 222.5 210.61 222.42 210.74 C 222.34 210.87 222.34 211.04 222.41 211.18 C 222.55 211.42 222.83 211.56 223.1 211.51 C 223.3 211.4 223.56 211.35 223.81 211.38 C 223.96 211.41 224.09 211.5 224.14 211.61 C 224.23 211.69 224.4 211.74 224.62 211.77 C 224.83 211.79 225.07 211.79 225.27 211.75 C 225.6 211.65 226.02 211.59 226.47 211.58 C 226.78 211.55 227.08 211.51 227.33 211.44 C 227.58 211.26 227.89 211.16 228.2 211.18 C 228.37 211.14 228.51 211.09 228.6 211.04 C 228.72 210.95 228.87 210.9 229.02 210.91 C 229.1 210.97 229.13 211.07 229.09 211.15 C 228.99 211.29 228.86 211.42 228.73 211.52 C 230.61 210.71 232.19 209.32 233.25 207.56 Z" fill="#266792" stroke="none" pointer-events="all"/&gt;&lt;ellipse cx="224.21" cy="198.24" rx="7.114164213280995" ry="4.923087196038061" fill-opacity="0.3" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;ellipse cx="220.96" cy="197.31" rx="2.0608439646712466" ry="1.5018034611318236" fill-opacity="0.4" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="195" y="165.22" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;ellipse cx="224.81" cy="202.43" rx="9.839712136081127" ry="9.846174392076122" fill="none" stroke="#000000" stroke-width="0.25" pointer-events="all"/&gt;&lt;rect x="195" y="165.22" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 219.46 209.2 L 219.46 200.52 L 221.53 199.95 L 221.53 193.72 L 222.48 193.41 L 227.82 195.04 L 227.82 200.96 L 229.64 201.46 L 229.64 210.39 L 226 211.27 Z M 223.29 199.57 L 226.12 200.45 L 226.12 195.86 L 223.29 195.04 Z" fill="#a3acbd" stroke="none" pointer-events="all"/&gt;&lt;path d="M 229.64 201.46 L 226.06 202.34 L 219.46 200.52 L 222.98 199.51 Z" fill="#b2b9ca" stroke="none" pointer-events="all"/&gt;&lt;path d="M 229.64 210.39 L 226.06 211.27 L 226.06 202.34 L 229.64 201.46 Z" fill="#5b6369" stroke="none" pointer-events="all"/&gt;&lt;path d="M 226.06 210.71 L 219.46 208.7 L 219.46 207.88 L 226.06 209.83 L 229.64 208.95 L 229.64 209.83 Z" fill-opacity="0.7" fill="#0378b2" stroke="none" pointer-events="all"/&gt;&lt;rect x="195" y="165.22" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 221.97 205.74 C 221.72 205.54 221.6 205.26 221.66 204.98 C 221.8 204.73 222.02 204.64 222.22 204.76 C 222.47 204.83 222.68 205.07 222.77 205.39 C 222.79 205.57 222.72 205.75 222.56 205.86 L 222.67 207.44 C 222.67 207.54 222.63 207.63 222.56 207.7 C 222.48 207.78 222.39 207.82 222.29 207.82 C 222.12 207.81 221.96 207.75 221.85 207.63 C 221.74 207.51 221.69 207.34 221.72 207.18 Z" fill="#3b393b" stroke="none" pointer-events="all"/&gt;&lt;path d="M 227.82 195.04 L 226.88 195.36 L 221.53 193.79 L 222.48 193.41 Z" fill="#d9dde6" stroke="none" pointer-events="all"/&gt;&lt;path d="M 226.06 203.52 L 219.46 201.65 L 219.46 200.82 L 226.06 202.64 L 229.64 201.76 L 229.64 202.64 Z" fill-opacity="0.7" fill="#0378b2" stroke="none" pointer-events="all"/&gt;&lt;path d="M 220.09 200.71 C 220.45 200.51 220.83 200.37 221.22 200.27 L 223.61 199.69 L 228.39 201.1 C 228.59 201.24 228.76 201.42 228.89 201.62 C 228.97 202.05 228.98 202.48 228.91 202.91 C 228.87 203.97 228.56 205 228.01 205.9 C 227.5 206.44 226.85 206.82 226.12 207 C 225.36 207.3 224.55 207.45 223.73 207.44 C 222.97 207.41 222.23 207.06 221.67 206.43 C 221.09 205.88 220.61 205.21 220.28 204.48 C 220.01 203.82 219.88 203.12 219.9 202.41 C 219.89 201.83 219.95 201.26 220.09 200.71 Z" fill-opacity="0.3" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;rect x="195" y="165.22" width="0" height="0" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 221.53 193.79 L 226.88 195.36 L 226.88 201.9 L 226.12 201.71 L 226.12 195.86 L 222.41 194.73 L 222.41 200.52 L 221.53 200.27 Z" fill="#9ba5b6" stroke="none" pointer-events="all"/&gt;&lt;path d="M 227.82 195.04 L 227.82 201.65 L 226.88 201.9 L 226.88 195.36 Z" fill="#60686f" stroke="none" pointer-events="all"/&gt;&lt;path d="M 223.29 195.03 L 223.29 200.27 L 222.41 200.52 L 222.41 194.73 Z" fill="#5c6369" stroke="none" pointer-events="all"/&gt;&lt;path d="M 219.46 209.2 L 219.46 200.52 L 221.53 199.95 L 221.53 193.72 L 222.48 193.41 L 227.82 195.04 L 227.82 200.96 L 229.64 201.46 L 229.64 210.39 L 226 211.27 Z M 223.29 199.57 L 226.12 200.45 L 226.12 195.86 L 223.29 195.04 Z" fill="none" stroke="#000000" stroke-width="0.15" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 219px; margin-left: 215px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;SOCKS5 Proxy&lt;br /&gt;by SSH&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="215" y="231" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SOCKS5&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="115" y="153.75" width="42.55" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 115.06 172.69 L 123.57 172.69 L 123.57 170.76 L 115.06 170.76 Z M 115.06 166.74 L 125.27 166.74 L 125.27 164.81 L 115.06 164.81 Z M 115.06 160.78 L 126.97 160.78 L 126.97 158.85 L 115.06 158.85 Z M 146.63 170.76 L 133.31 170.76 L 134.53 165.89 L 147.85 165.89 Z M 133.02 163.96 L 130.84 172.69 L 148.14 172.69 L 150.32 163.96 Z M 150.07 181.52 C 150.03 181.69 149.88 181.82 149.7 181.82 L 126.09 181.82 C 125.93 181.82 125.83 181.73 125.79 181.68 C 125.74 181.62 125.68 181.51 125.71 181.35 L 130.08 160.74 L 154.48 160.74 Z M 131.47 155.68 L 155.08 155.68 C 155.24 155.68 155.33 155.77 155.38 155.82 C 155.42 155.88 155.49 155.99 155.45 156.15 L 154.89 158.81 L 130.49 158.81 L 131.09 155.99 C 131.13 155.81 131.29 155.68 131.47 155.68 Z M 156.87 154.61 C 156.43 154.06 155.78 153.75 155.08 153.75 L 131.47 153.75 C 130.38 153.75 129.43 154.52 129.2 155.59 L 123.82 180.96 C 123.68 181.64 123.85 182.35 124.29 182.89 C 124.73 183.44 125.38 183.75 126.09 183.75 L 149.7 183.75 C 150.78 183.75 151.73 182.98 151.96 181.92 L 157.34 156.55 C 157.49 155.86 157.32 155.15 156.87 154.61 Z" fill="#b0084d" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 191px; margin-left: 136px;"&gt;&lt;div data-drawio-colors="color: #232F3E; " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;kubectl&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="136" y="203" fill="#232F3E" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubectl&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="115" y="240" width="42.55" height="30" fill="none" stroke="none" pointer-events="all"/&gt;&lt;path d="M 115.06 258.94 L 123.57 258.94 L 123.57 257.01 L 115.06 257.01 Z M 115.06 252.99 L 125.27 252.99 L 125.27 251.06 L 115.06 251.06 Z M 115.06 247.03 L 126.97 247.03 L 126.97 245.1 L 115.06 245.1 Z M 146.63 257.01 L 133.31 257.01 L 134.53 252.14 L 147.85 252.14 Z M 133.02 250.21 L 130.84 258.94 L 148.14 258.94 L 150.32 250.21 Z M 150.07 267.77 C 150.03 267.94 149.88 268.07 149.7 268.07 L 126.09 268.07 C 125.93 268.07 125.83 267.98 125.79 267.93 C 125.74 267.87 125.68 267.76 125.71 267.6 L 130.08 246.99 L 154.48 246.99 Z M 131.47 241.93 L 155.08 241.93 C 155.24 241.93 155.33 242.02 155.38 242.07 C 155.42 242.13 155.49 242.24 155.45 242.4 L 154.89 245.06 L 130.49 245.06 L 131.09 242.24 C 131.13 242.06 131.29 241.93 131.47 241.93 Z M 156.87 240.86 C 156.43 240.31 155.78 240 155.08 240 L 131.47 240 C 130.38 240 129.43 240.77 129.2 241.84 L 123.82 267.21 C 123.68 267.89 123.85 268.6 124.29 269.14 C 124.73 269.69 125.38 270 126.09 270 L 149.7 270 C 150.78 270 151.73 269.23 151.96 268.17 L 157.34 242.8 C 157.49 242.11 157.32 241.4 156.87 240.86 Z" fill="#b0084d" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 277px; margin-left: 136px;"&gt;&lt;div data-drawio-colors="color: #232F3E; " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;aws cli&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="136" y="289" fill="#232F3E" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;aws cli&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 545 183.75 L 688.63 183.75" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 693.88 183.75 L 686.88 187.25 L 688.63 183.75 L 686.88 180.25 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 480.7 72.67 L 489.28 72.67 L 489.28 67.89 C 489.28 66.64 488.46 64.69 486.66 64.69 L 483.25 64.69 C 482 64.69 480.7 65.98 480.7 67.89 Z M 475 90 L 475 72.67 L 477.83 72.67 L 477.83 65.36 C 477.83 62.48 479.81 60 482.05 60 L 487.91 60 C 490.18 60 492.13 62.43 492.13 65.49 L 492.13 72.67 L 495 72.67 L 495 90 Z" fill="#d9a741" stroke="none" pointer-events="all"/&gt;&lt;path d="M 489.28 67.89 C 489.28 66.64 488.46 64.69 486.66 64.69 L 483.25 64.69 C 482 64.69 480.7 65.99 480.7 67.89 L 480.7 66.87 C 480.7 65.2 481.77 63.63 483.26 63.63 L 486.75 63.63 C 488.02 63.63 489.28 65.03 489.28 66.82 Z M 475 90 L 475 88.98 L 495 88.98 L 495 90 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="all"/&gt;&lt;path d="M 47.26 100.88 C 45.95 100.88 45 99.85 45 98.69 L 45 61.03 C 45 59.99 45.83 58.88 47.12 58.88 L 72.84 58.88 C 73.96 58.88 75 59.79 75 61.08 L 75 98.94 C 75 99.94 74.01 100.88 72.82 100.88 Z" fill="#7d7c7c" stroke="none" pointer-events="all"/&gt;&lt;path d="M 75 96.78 L 75 98.94 C 75 99.94 74.01 100.88 72.82 100.88 L 47.26 100.88 C 45.95 100.88 45 99.84 45 98.69 L 45 97.04 C 45 98.19 46.1 99.01 47.09 99.01 L 72.78 99.01 C 73.96 99.01 75 98.07 75 96.78 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="all"/&gt;&lt;rect x="45" y="58.88" width="0" height="0" fill="none" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;path d="M 54.63 66.43 L 48.56 66.43 L 48.56 63.97 L 54.63 63.97 Z M 62.98 66.43 L 56.91 66.43 L 56.91 63.97 L 62.98 63.97 Z M 71.34 66.43 L 65.26 66.43 L 65.26 63.97 L 71.34 63.97 Z M 54.63 70.73 L 48.56 70.73 L 48.56 68.27 L 54.63 68.27 Z M 62.98 70.73 L 56.91 70.73 L 56.91 68.27 L 62.98 68.27 Z M 71.34 70.73 L 65.26 70.73 L 65.26 68.27 L 71.34 68.27 Z M 54.63 75.04 L 48.56 75.04 L 48.56 72.58 L 54.63 72.58 Z M 62.98 75.04 L 56.91 75.04 L 56.91 72.58 L 62.98 72.58 Z M 71.34 75.04 L 65.26 75.04 L 65.26 72.58 L 71.34 72.58 Z M 54.63 79.34 L 48.56 79.34 L 48.56 76.88 L 54.63 76.88 Z M 62.98 79.34 L 56.91 79.34 L 56.91 76.88 L 62.98 76.88 Z M 71.34 79.34 L 65.26 79.34 L 65.26 76.88 L 71.34 76.88 Z M 54.63 83.65 L 48.56 83.65 L 48.56 81.19 L 54.63 81.19 Z M 62.98 83.65 L 56.91 83.65 L 56.91 81.19 L 62.98 81.19 Z M 71.34 83.65 L 65.26 83.65 L 65.26 81.19 L 71.34 81.19 Z M 54.63 87.95 L 48.56 87.95 L 48.56 85.49 L 54.63 85.49 Z M 62.98 87.95 L 56.91 87.95 L 56.91 85.49 L 62.98 85.49 Z M 71.34 87.95 L 65.26 87.95 L 65.26 85.49 L 71.34 85.49 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;path d="M 745.96 76 C 740.09 76 735.55 71.43 735.55 67.19 L 735.55 65.43 C 735.09 60.07 739.29 55.87 742.21 55.04 C 742.04 51.25 743.66 46.93 747.09 44.04 C 752.05 40 757.87 40.62 761.39 42.53 C 763.87 43.81 766.1 46.07 767.39 49.06 C 770.04 47.23 772.87 47.78 774.49 48.73 C 776.67 50.01 777.84 52.28 777.87 54.5 C 780.12 54.75 782.79 55.78 785.11 58.85 C 786.32 60.5 787.06 63.42 787.09 65.17 L 787.09 66.98 C 787.09 71.56 782.32 76 776.7 76 Z" fill="#f58536" stroke="none" pointer-events="all"/&gt;&lt;path d="M 787.09 65.17 L 787.09 66.98 C 787.09 71.55 782.32 76 776.7 76 L 745.96 76 C 740.09 76 735.55 71.42 735.55 67.19 L 735.55 65.43 C 735.55 69.28 739.85 74.11 745.88 74.11 L 776.24 74.11 C 782.68 74.11 787.09 69.52 787.09 65.17 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="all"/&gt;&lt;rect x="735.09" y="40" width="0" height="0" fill="none" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;path d="M 748.15 64.72 L 750.99 64.72 L 749.61 58.59 Z M 750.92 56.94 L 754.14 69.34 L 752.09 69.34 L 751.36 66.32 L 747.85 66.32 L 747.04 69.34 L 744.99 69.34 L 748.43 56.94 Z M 766.29 56.94 L 763.88 69.34 L 761.6 69.34 L 760.03 59.73 L 758.47 69.34 L 756.26 69.34 L 753.92 56.94 L 755.82 56.94 L 757.47 66.81 L 759.04 56.94 L 761.09 56.94 L 762.74 66.88 L 764.32 56.94 Z M 774.58 60.11 L 772.67 60.11 C 772.71 59.17 772.47 58.55 771.87 58.25 C 771.5 58.07 770.71 57.92 770.1 58.29 C 769.77 58.5 769.36 58.89 769.37 59.92 C 769.39 61.23 770.33 61.72 771.25 62.09 C 772.37 62.45 773.36 62.76 774.09 63.47 C 775.22 64.55 775.11 66.27 774.79 67.31 C 774.35 68.59 773.35 69.21 772 69.42 C 770.8 69.56 769.51 69.53 768.56 68.81 C 767.49 67.95 767.29 66.68 767.32 65.41 L 769.23 65.41 C 769.2 66.25 769.31 66.91 769.65 67.33 C 770.17 67.99 771.07 68.09 771.85 67.88 C 772.8 67.58 773.05 66.69 773.02 66.12 C 773.04 65.06 772.51 64.38 771.57 64.06 C 770.04 63.52 768.88 63.28 768.05 62.3 C 767.63 61.82 767.22 60.83 767.46 59.28 C 767.67 57.98 768.53 57.22 769.31 56.9 C 770.4 56.39 772.17 56.46 773.12 56.94 C 774.09 57.39 774.65 58.38 774.58 60.11 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;path d="M 695 163.71 L 735.09 163.71 L 735.09 203.8 L 695 203.8 Z" fill="url(#mx-gradient-f78e04-1-d05c17-1-s-0)" stroke="none" pointer-events="all"/&gt;&lt;path d="M 718.61 179.1 L 714.77 183.55 L 718.95 188.43 L 717.39 188.43 L 713.64 184.05 L 713.64 188.55 L 712.52 188.55 L 712.52 178.99 L 713.64 178.99 L 713.64 182.92 L 717.11 179.1 Z M 728.26 188.12 L 723.76 185.42 L 723.76 178.99 C 723.76 178.79 723.65 178.6 723.48 178.5 L 717.01 174.73 L 717.01 169.29 L 728.26 175.94 Z M 729.11 175.13 L 716.74 167.82 C 716.56 167.72 716.35 167.72 716.17 167.82 C 716 167.92 715.89 168.11 715.89 168.31 L 715.89 175.05 C 715.89 175.25 715.99 175.44 716.17 175.54 L 722.63 179.31 L 722.63 185.74 C 722.63 185.93 722.74 186.12 722.91 186.22 L 728.53 189.59 C 728.62 189.64 728.72 189.67 728.82 189.67 C 728.91 189.67 729.01 189.65 729.1 189.6 C 729.27 189.5 729.38 189.31 729.38 189.11 L 729.38 175.62 C 729.38 175.42 729.28 175.23 729.11 175.13 Z M 715.01 198.59 L 701.83 191.58 L 701.83 175.94 L 713.08 169.29 L 713.08 174.74 L 707.15 178.51 C 706.99 178.62 706.89 178.8 706.89 178.99 L 706.89 188.55 C 706.89 188.76 707.01 188.95 707.2 189.05 L 714.76 192.98 C 714.92 193.07 715.11 193.07 715.28 192.98 L 722.61 189.19 L 727.13 191.9 Z M 728.55 191.44 L 722.92 188.06 C 722.76 187.96 722.55 187.96 722.38 188.05 L 715.02 191.85 L 708.02 188.21 L 708.02 179.3 L 713.94 175.53 C 714.1 175.42 714.2 175.25 714.2 175.05 L 714.2 168.31 C 714.2 168.11 714.09 167.92 713.92 167.82 C 713.74 167.72 713.53 167.72 713.35 167.82 L 700.98 175.13 C 700.81 175.23 700.71 175.42 700.71 175.62 L 700.71 191.92 C 700.71 192.13 700.82 192.32 701.01 192.42 L 714.75 199.73 C 714.84 199.77 714.93 199.79 715.02 199.79 C 715.11 199.79 715.21 199.77 715.29 199.72 L 728.53 192.41 C 728.71 192.31 728.82 192.13 728.82 191.93 C 728.82 191.73 728.72 191.54 728.55 191.44 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 1px; height: 1px; padding-top: 184px; margin-left: 737px;"&gt;&lt;div data-drawio-colors="color: #232F3E; " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(35, 47, 62); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;EKS&lt;br /&gt;Cluster&lt;br /&gt;Endpoint&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="737" y="187" fill="#232F3E" font-family="Helvetica" font-size="12px"&gt;EKS&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 525.02 456 C 511.25 456 500 444.6 500 431.53 L 500 428.52 C 500 415.94 510.46 404 524.91 404 C 539.69 404 550 415.94 550 428.72 L 550 431.14 C 550 444.73 538.98 456 525.02 456 Z" fill="#f58534" stroke="none" pointer-events="all"/&gt;&lt;path d="M 500 428.52 C 500 443.8 512.18 453.86 524.95 453.86 C 539.44 453.86 550 442.07 550 428.72 L 550 431.14 C 550 444.76 538.98 456 525.02 456 C 511.14 456 500 444.59 500 431.53 Z" fill-opacity="0.3" fill="#000000" stroke="none" pointer-events="all"/&gt;&lt;rect x="500" y="404" width="0" height="0" fill="none" stroke="rgb(0, 0, 0)" pointer-events="all"/&gt;&lt;path d="M 513.25 429.26 C 515.12 436.4 519.07 442.04 525 444.99 C 528.79 443.32 531.86 439.82 533.52 437.03 C 536.51 432.03 537.91 426.02 537.97 419.67 C 536.39 419.48 534.76 418.95 533.83 418.06 C 533 417.31 532.14 415.84 531.81 414 C 529.38 413.22 526.9 412.74 524.75 412.78 C 522.25 412.87 520.14 413.42 518.33 414 C 518.08 415.61 517.25 417.28 516.28 418.08 C 515.03 419.12 513.52 419.53 512.01 419.74 C 512.02 421.85 512.25 424.14 512.62 426.3 L 524.75 426.3 L 524.75 421.46 L 532.48 427.86 L 524.75 434.18 L 524.75 429.26 Z M 504.85 429.26 L 504.85 426.3 L 510.15 426.3 C 509.67 423.41 509.5 420.39 509.54 417.44 C 511.74 417.55 513.73 417.13 514.61 416.23 C 515.54 415.24 515.82 413.79 515.95 412.36 C 518.5 411.29 521.71 410.43 524.64 410.4 C 527.82 410.32 530.93 411.13 534.05 412.19 C 534.21 414.49 534.9 415.95 535.94 416.49 C 537.29 417.12 538.95 417.22 540.45 417.27 C 540.45 425.99 539.22 431.56 536 437.45 C 533.77 441.47 530.08 445.42 524.99 447.55 C 520.99 446.01 517.2 442.74 514.59 438.57 C 512.83 435.81 511.5 432.53 510.75 429.26 Z" fill="#ffffff" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 463px; margin-left: 525px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: nowrap;"&gt;AWS Service Endpoint&lt;br /&gt;https://eks.us-west-2.amazonaws.com&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="525" y="475" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;AWS Serv&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 157.49 255 L 230 255 L 230 430 L 493.63 430" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 498.88 430 L 491.88 433.5 L 493.63 430 L 491.88 426.5 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 157.49 174.14 L 188.83 182.1" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="stroke"/&gt;&lt;path d="M 193.92 183.39 L 186.27 185.06 L 188.83 182.1 L 187.99 178.28 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 290 410 C 266 410 260 430 279.2 434 C 260 442.8 281.6 462 297.2 454 C 308 470 344 470 356 454 C 380 454 380 438 365 430 C 380 414 356 398 335 406 C 320 394 296 394 290 410 Z" fill="rgb(255, 255, 255)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 430px; margin-left: 261px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: center;"&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Internet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="320" y="434" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Internet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="390" width="210" height="110" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 208px; height: 1px; padding-top: 445px; margin-left: 2px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Note: kubectl calls aws-cli for authentication. So make sure that aws-cli uses the right profile and assumes the right role, if applicable.&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="2" y="449" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="14px"&gt;Note: kubectl calls aws-cli fo&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 115.06 168.81 L 50 169 L 52.43 383.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="8 8" pointer-events="stroke"/&gt;&lt;path d="M 52.49 388.88 L 48.91 381.92 L 52.43 383.63 L 55.91 381.84 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;path d="M 356.25 193.75 L 359.36 333.63" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="8 8" pointer-events="stroke"/&gt;&lt;path d="M 359.48 338.88 L 355.82 331.96 L 359.36 333.63 L 362.82 331.81 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="286" y="340" width="294" height="40" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 292px; height: 1px; padding-top: 360px; margin-left: 288px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;The SSH Tunnel is established on top of a proxy command using SSM session manager or EC2 Instance Connect with EIC Endpoint&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="288" y="364" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="14px"&gt;The SSH Tunnel is established on top of a&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 215.45 165.22 L 217.38 65.25" fill="none" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" stroke-dasharray="3 3" pointer-events="stroke"/&gt;&lt;path d="M 217.48 60 L 220.84 67.06 L 217.38 65.25 L 213.84 66.93 Z" fill="rgb(0, 0, 0)" stroke="rgb(0, 0, 0)" stroke-miterlimit="10" pointer-events="all"/&gt;&lt;rect x="150" y="8.88" width="270" height="50" fill="none" stroke="none" pointer-events="all"/&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility" style="overflow: visible; text-align: left;"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 268px; height: 1px; padding-top: 34px; margin-left: 152px;"&gt;&lt;div data-drawio-colors="color: rgb(0, 0, 0); " style="box-sizing: border-box; font-size: 0px; text-align: left;"&gt;&lt;div style="display: inline-block; font-size: 14px; font-family: Helvetica; color: rgb(0, 0, 0); line-height: 1.2; pointer-events: all; white-space: normal; overflow-wrap: normal;"&gt;Tell kubectl to use SOCKS5 proxy by the HTTPS_PROXY environment variable or by the proxy-url attribute in .kube/config&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="152" y="38" fill="rgb(0, 0, 0)" font-family="Helvetica" font-size="14px"&gt;Tell kubectl to use SOCKS5 proxy by th&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"/&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.drawio.com/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Text is not SVG &amp;#8211; cannot display&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To put this in practice, I first created a VPC stack with a bastion host using terraform template from my &lt;a href="https://github.com/digihunch/vpc-base/tree/main"&gt;vpc-base&lt;/a&gt; project. The terraform output will give the next set of commands to run to create a private cluster, using a manifest rendered from the file &lt;a href="https://github.com/digihunch/vpc-base/blob/main/template/eksctl.tpl"&gt;private-cluster.yaml.tmpl&lt;/a&gt;:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# cd aws_vpc&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# terraform init&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# terraform plan&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# terraform apply&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# ... run the given command ...&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# envsubst &amp;lt; private-cluster.yaml.tmpl | tee | eksctl create cluster -f -&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Run this from a remote host without access to cluster endpoint.&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Run terraform apply and terraform output contains the variables needed for the next steps&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# the command below may take 15 minutes to create a private cluster&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;eksctl create cluster -f private-cluster.yaml &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;aws eks update-kubeconfig --name private-cluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;At this point, the kubeconfig file has been updated, but kubectl (from Internet or on-prem) is unable to connect to cluster endpoint (on private network). In order to &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;BASTION_SECURITY_GROUP_ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;terraform output -raw bastion_sg_id&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;CLUSTER_SECURITY_GROUP_ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;aws eks describe-cluster --name private-cluster --query &lt;span style="color:#e6db74"&gt;&amp;#34;cluster.resourcesVpcConfig.clusterSecurityGroupId&amp;#34;&lt;/span&gt; --output text&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# In Cluster Endpoint&amp;#39;s security group, open up port 443 to Bastion host&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;aws ec2 authorize-security-group-ingress --group-id $CLUSTER_SECURITY_GROUP_ID --source-group $BASTION_SECURITY_GROUP_ID --protocol tcp --port &lt;span style="color:#ae81ff"&gt;443&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Test with connecting to Bastion host with ssh i-0750643179667a5b6, assuming .ssh/config file is configured as above. From the bastion host, you can test:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# curl -k https://EC5405EE1846F19F9F61ED28FB12A6A9.sk1.us-west-2.eks.amazonaws.com/api &lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# if you get an HTTP response, even an error code 403, the bastion host has TCP connectivity to cluster endpoint&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# then we can start an SSH session as a SOCKS5 proxy on the remote host&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ssh -D &lt;span style="color:#ae81ff"&gt;1080&lt;/span&gt; -q -N i-0750643179667a5b6&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# add &amp;gt; /dev/null 2&amp;gt;&amp;amp;1 &amp;amp; to push it to background, or use ctrl+z after running the command&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# to validate that the SOCKS5 proxy is working, you can run the same curl command with a proxy parameter:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# curl -k https://EC5405EE1846F19F9F61ED28FB12A6A9.sk1.us-west-2.eks.amazonaws.com/api --proxy socks5://localhost:1080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# you can instruct kubectl to use the SOCKS5 proxy with the following environment variable&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export HTTPS_PROXY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;socks5://localhost:1080&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get node&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# alternatively, add &amp;#34;proxy-url: socks5://localhost:1080&amp;#34; below server attribute in ~/.kube/config file.&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;There are some pitfalls to watch for. On the remote host both ssh command and kubectl command implicitly uses AWS CLI. Therefore, make sure the profile and IAM role are correctly configured. For example, if SSM agent requires one IAM role, and kubectl is created with another IAM role, then make sure AWS CLI &lt;a href="https://repost.aws/knowledge-center/iam-assume-role-cli"&gt;assumes the correct IAM role&lt;/a&gt; using environment variables, and use &amp;#8220;aws sts get-caller-identity&amp;#8221; to validate the IAM identity being used.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;What about AKS in Azure&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I touched on this in my &lt;a href="https://static.digihunch.com/2021/10/notes-on-azure/"&gt;Azure notes&lt;/a&gt; in 2021 and did a research again. Unfortunately, options are still fairly limited. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first option is to use a managed service called &amp;#8220;Azure Bastion&amp;#8221;, which requires public IP and a dedicated subnet with the exact name of AzureBastionSubnet, as well as some &lt;a href="https://learn.microsoft.com/en-us/azure/bastion/configuration-settings#subnet"&gt;additional requirement&lt;/a&gt;. I&amp;#8217;m not impressed with these requirement because it is meant to be a managed service. The other option, is essentially to DIY a JumpBox. The idea is the same: put the jumpbox in a public subnet, which is routable to private subnets. When you need to connect to private VMs, get to the jumpbox first.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from having to put the bastion VM on a public subnet, the pattern that we discussed above involving SOCKS5 proxy still works. Exposing a bastion host isn&amp;#8217;t ideal but it still reduces attack surface significantly, comparing to exposing the cluster endpoints of all Kubernetes API servers.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Many immature Kubernetes configurations exposes private endpoint publicly. Having cluster endpoint in private subnet greatly improves security posture. In my opinion, there are very few situations where cluster endpoint must exposed publicly. Having private endpoint should be mandatory for all Kubernetes cluster. In the next &lt;a href="https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/"&gt;post&lt;/a&gt;, I also cover how to create a ROSA cluster with private endpoint.&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/2023/05/kubernetes-with-multiple-cpu-architectures/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes with Multiple CPU Architectures 2 of 2 – Node and Workload&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Platform as a Service and Red Hat OpenShift&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Optimize CPU and Memory for Kubernetes Pod</title><link>https://static.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/</link><pubDate>Fri, 13 Jan 2023 11:47:00 -0400</pubDate><guid>https://static.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/cpu-feature.webp" alt="Featured image of post Optimize CPU and Memory for Kubernetes Pod" /&gt;&lt;p class="wp-block-paragraph"&gt;When optimizing workload performance, it is important to understand how on earth operating system allocates CPU and memory to processes. This helps understand how to set resource limit Kubernetes Pod in an optimal way.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-cpu-resource-assignment"&gt;CPU resource assignment&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The OS distributes CPU resource to processes by the unit of time share of CPU time. Most of the time, many processes with CPU instructions (machine code) are waiting in the Job queue, for their share of CPU time in order to execute their instructions. As soon as CPU becomes idle, the CPU scheduler selects a process from the ready queue to run next:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="488" src="https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment.webp" alt="" class="wp-image-12886" style="width:552px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment-300x143.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment-768x366.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ideally, OS should schedule CPU in a way that it should not waste any CPU cycle. It should also minimizes waiting time and response time of processes. At a high level, there are two types of CPU scheduling:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Preemptive: OS allocate CPU resources to a process for only a limited period of time and then takes those resources back. It could interrupt a running process to execute a higher priority process.&lt;/li&gt;&#10;&lt;li&gt;Non-preemptive: New processes are executed only after the current executing process has completed its execution.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.geeksforgeeks.org/preemptive-and-non-preemptive-scheduling/"&gt;Here&lt;/a&gt; is more information about preemptive and non-preemptive scheduling. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;CPU is compressible resource in Linux&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://www.usenix.org/legacy/publications/library/proceedings/usenix01/freenix01/full_papers/alicherry/alicherry_html/node5.html#:~:text=All%20scheduling%20is%20preemptive%3A%20If,is%20a%20single%20run%2Dqueue."&gt;Linux&lt;/a&gt; world, all scheduling is preemptive. We also call it &lt;a href="https://en.wikipedia.org/wiki/Kernel_preemption"&gt;kernel preemption&lt;/a&gt;. As the wikipedia entry states: the&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Scheduling_(computing)"&gt;scheduler&lt;/a&gt;&amp;nbsp;is permitted to forcibly perform a&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Context_switch"&gt;context switch&lt;/a&gt;&amp;nbsp;(on behalf of a runnable and&amp;nbsp;&lt;a href="https://en.wikibooks.org/wiki/Operating_System_Design/Scheduling_Processes/Priority_Scheduling"&gt;higher-priority&lt;/a&gt;&amp;nbsp;process) on a driver or other part of the kernel during its execution, rather than&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing"&gt;co-operatively&lt;/a&gt;&amp;nbsp;waiting for the driver or kernel function (such as a&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/System_call"&gt;system call&lt;/a&gt;) to complete its execution and return control of the processor to the scheduler when done.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Linux scheduler implements a number of&amp;nbsp;&lt;em&gt;&lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-cpu-scheduler"&gt;scheduling policies&lt;/a&gt;&lt;/em&gt;, which determine when and for how long a thread runs on a particular CPU core. The scheduling policies in RHEL include real time policies such as SCHED_FIFO and SCHED_RR where processes have a sched_priority value in the range of 1 (low) to 99 (high); and normal policies such as SCHED_OTHER, SCHED_BATCH and SCHED_IDLE, where sched_priority (specified as 0) is not used in scheduling decisions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is important to understand preemptive CPU scheduling on Linux. When OS allocate CPU resource to a process for one time slot, it is not committed to the same process for the next time slot. The OS reserves the ability to revoke the next CPU use and re-assign it for processes of higher priority.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because of this, we regard CPU as a compressible resource. The compressible characteristic impacts how we optimize CPU utilization for a process, including setting CPU request and limit for Kubernetes workload. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Memory is non-compressible resource&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A few years ago, I discussed how to&lt;a href="https://static.digihunch.com/2020/04/how-memory-usage-adds-up-in-linux/"&gt; calculate memory usage&lt;/a&gt;. A process requests memory from OS using memory allocation functions (the &lt;a href="https://man7.org/linux/man-pages/man3/malloc.3.html"&gt;malloc&lt;/a&gt; family), and return memory to OS using &lt;a href="https://man7.org/linux/man-pages/man1/free.1.html"&gt;free&lt;/a&gt; functions. The design of Linux OS knows that processes have a tendency to request more memory than they use, which causes under-utilization. In combat against under-utilization, the Linux OS supports &lt;a href="https://en.wikipedia.org/wiki/Memory_overcommitment"&gt;memory overcommitment&lt;/a&gt; (on by default), allowing processes to request more memory than what is available. The processes have access to virtual memory space and the OS may swap some pages out to disks. The overcommitment mechanism also prevents processes from crashing due to insufficient memory assignment. The kernel can also OOM kill a process when the entire system is in a crisis.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Memory is non-compressible resource. When OS assigns memory pages to a process, the process has to right to keep those pages, until the OS takes them away. Unlike assigning CPU cycles, the assignment of memory pages to processes does not have an expiry time. This is the non-compressible characteristic of memory assignment. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;CPU limit and requests for Kubernetes workload&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It was considered best practice to set request and limit for memory and CPU. However, knowing CPU is compressible resource and memory isn&amp;#8217;t, we should re-consider this practice. In short, for CPU, we should set request only, &lt;a href="https://home.robusta.dev/blog/stop-using-cpu-limits"&gt;without setting limit&lt;/a&gt;. For memory, we should set &lt;a href="https://home.robusta.dev/blog/kubernetes-memory-limit"&gt;limit to exactly the same as request&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A process has different level of demands for CPU at different times. Depending on the activity in the process, the level of demand can even be spiky. If there is a lot of iowait, it may not need a lot of CPU. But when there are lots of computing-bound activities, the program is CPU-thirsty as it is programmed to to more. The last thing we want is to throttle the CPU use for a process in such legit situations. When &lt;a href="https://medium.com/indeed-engineering/unthrottled-fixing-cpu-limits-in-the-cloud-a0995ede8e89"&gt;throttling&lt;/a&gt; happens, the process does not get sufficient time share of CPU time. At the platform level, we can&amp;#8217;t control when the Pod (process) gets busy. The best thing it can do, is trying to fit more CPU time shares to this process when it becomes CPU thirsty. When we apply a limit of CPU in workload setting, we are potentially throttling the CPU use for a process at the times it needs more CPU time shares, which is counter-productive. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We should still configure CPU request, so that kube-scheduler factors it in when scheduling multiple Pods to a Node. The CPU request alone ensures the number of Pods are not excessive. This is the only thing we can do about controlling CPU assignment for Pods. We should also monitor &lt;a href="https://wbhegedus.me/understanding-kubernetes-cpu-limits/"&gt;CPU throttling&lt;/a&gt;. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Memory limit and request for Kubernetes workload&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Memory is not compressible, therefore we should set both limit and request to the same value. We set memory request so that kube-scheduler has an idea assigning Pods. We set the limit so that no single Pod takes more memory than its fair share. Unlike CPU, once a Pod takes more memory than its fair share, the platform will have to be aggressive to reclaim it back, which may impacts the running of the Pod (process). In contrast, CPU scheduler never guarantees the assignment of CPU time share to a Pod beyond the end of the current CPU cycle.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we&amp;#8217;re setting memory limit and request with different values, we&amp;#8217;re sending a confusing signal. We&amp;#8217;re inviting Pods to use more memory than they requested. This increases the chance of memory shortage at the node level, and hence the need to OOM kill a Pod.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Horizontal autoscaling and Cluster Autoscaling&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The native HPA is metrics-based. As I &lt;a href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;previously discussed&lt;/a&gt;, neither CPU nor memory metrics are good indicators of time to scale. A process or a Pod may have a temporary high demand of CPU purely due to how programmers write the code. Even if we followed the best practices as above, I would still not regard CPU and memory metrics as a reliable indicator to drive auto scaling. If a service is a potential point of congestion, we should use a queue in front and the queue size is almost always a much better indicator of the timing to scale. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to cluster autoscaler, on it FAQ, it says flat out that you should NOT use a &lt;a href="https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#should-i-use-a-cpu-usage-based-node-autoscaler-with-kubernetes"&gt;CPU usage based scaling mechanism&lt;/a&gt;. I guess this is for a similar reason (compressibility). As discussed, when a Pod is pending for schedule for too long, it emits and event that drives the cluster autoscaler.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Summary&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When I first worked on Kubernetes workload I did not give this much thought and proposed the use of CPU limit. As of January 2023 I still find static code analysis tools that requires CPU limit for Pods in the check (e.g. CKV_K8S_11 on &lt;a href="https://www.checkov.io/5.Policy%20Index/kubernetes.html"&gt;Checkov&lt;/a&gt;), which leads me to investigate the issue further, and noticed more voices advocating the correct use of resource limit (such as &lt;a href="https://sysdig.com/blog/kubernetes-limits-requests/"&gt;this&lt;/a&gt; post) in 2022. For existing deployments, it is worth a review the resource limit configuration.&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/12/eks-impression/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;EKS impression&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/01/github-action-gotchas/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;GitHub Action Gotchas&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>EKS impression</title><link>https://static.digihunch.com/2022/12/eks-impression/</link><pubDate>Fri, 23 Dec 2022 18:18:19 -0400</pubDate><guid>https://static.digihunch.com/2022/12/eks-impression/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/eks-impression-feature.webp" alt="Featured image of post EKS impression" /&gt;&lt;p class="wp-block-paragraph"&gt;I&amp;#8217;ve worked on a few &lt;a href="https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/"&gt;AKS projects&lt;/a&gt; previously. Since I joined AWS I wanted to put aside some time to check out EKS (Elastic Kubernetes Service). Here in this post, I put down my first impression on EKS, and also share my Terraform template in &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project to create an EKS cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to AKS, EKS exposes API endpoint and the control plane components are hidden from AWS users. When creating EKS cluster it does not create the underlying VPC and subnets. Therefore, you have create an existing VPC and at least two subnets ahead of time, and specify them during EKS creation. Bear in mind that there is a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html"&gt;list of requirement&lt;/a&gt; for the VPC and subnets.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the cluster, the CNI that EKS officially supports is Amazon VPC CNI plugin. It is available as an add-on. Similar to Azure CNI, each Pod gets its own IP address. In addition, EKS supports other &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/alternate-cni-plugins.html"&gt;compatible CNI plugins&lt;/a&gt; such as Calico, Cilium, Weave Net and Antrea.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-computing-nodes-in-eks"&gt;Computing Nodes in EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are three modes to address computing capacity: self-managed nodes, EKS managed node groups and AWS Fargate. The documentation has a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/eks-compute.html"&gt;comparison table&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With self-managed nodes, users create EC2 instances separately and then register them to the control plane. The instances must use the same IAM role and AMI. You can use Auto Scaling groups of &lt;a href="https://aws.amazon.com/bottlerocket/"&gt;Bottlerocket&lt;/a&gt; (AWS-sponsored purpose-built Linux distro for container host) nodes. The self-managed node option is mostly for AWS outpost customers who bring in their own computing capacity from data centre.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you provision computing capacity from AWS, it makes sense to assign EKS managed node groups when creating EKS cluster. We can turn on &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/autoscaling.html"&gt;Cluster Autoscaler&lt;/a&gt;, a Kubernetes construct to manage the auto scaling of node groups. Sometimes we want to have more than one node groups. For example, to build a multi-architecture cluster, we need one node group with amd64 nodes and the other with arm64 nodes (e.g. instances with &lt;a href="https://aws.amazon.com/ec2/graviton/"&gt;Graviton&lt;/a&gt; processor). In general, arm-based CPU delivers better performance with less power consumption and the industry is slowly moving towards more arm-based CPU architecture.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Fargate is what I call managed computing service for EKS. With Fargate you do not need to tweak Cluster Autoscaler to self-manage computing capacity. The Fargate documentation has a long list of &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate.html"&gt;considerations&lt;/a&gt;. For example, Pods must match a Fargate profile (&lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/eks/cluster-fargate.yaml"&gt;here&lt;/a&gt;&amp;#8216;s an example) at the time that they&amp;#8217;re scheduled to run on Fargate. So we need to build Fargate profile and Pod labelling properly. Also, Fargate does not support DaemonSet. Another big consideration is that Fargate does not support non-VPC CNI. In my opinion these are pretty significant limitations. Many workloads (system-level or application-level) would need Daemonset (e.g. kube-proxy, some CNI or CSI drivers, &lt;a href="https://www.dynatrace.com/support/help/setup-and-configuration/setup-on-container-platforms/kubernetes/get-started-with-kubernetes-monitoring/set-up-k8s-monitoring-daemonset"&gt;Dynatrace&lt;/a&gt; monitoring). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The pro of Fargate is the serverless computing model. The construct of a Fargate profile isn&amp;#8217;t complicated. You just specify subnets, namespace and labels. However, the downside is the long list of considerations. Some teams may consider these restrictions too much. The other overhead is the need to manage Fargate profile to ensure all Pods are scheduled somewhere. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To me, using Fargate alone impairs portability of workload. The good thing is that Fargate and Managed Node Group are not mutually exclusive on a cluster. In most cases, we can go partially serverless, and reap the benefits of both of them. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Node AutoScaling&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For workloads that don&amp;#8217;t have a matching Fargate profile, we have to figure out node autoscaling ourselves. I touched on Cluster Autoscaler in &amp;#8220;&lt;a href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;Autoscaling on Kubernetes Platform&lt;/a&gt;&amp;#8220;. CA works on AWS as well and is triggered upon a Pod coming to &lt;em&gt;unschedulable&lt;/em&gt; status in Scheduler. There is some limitations though. For example, CA interacts with Autoscaling Group (instead of EC2 instances directly). When it determines it&amp;#8217;s time to scale up, it bumps up the desired capacity by one at a time in the Autoscaling group. The configurations in Autoscaling group may also be at play and CA do not have direct control. For example, the &amp;#8220;&lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scaling-cooldowns.html"&gt;scaling cooldown&lt;/a&gt;&amp;#8220;. The pool of nodes is homogenous as per the pre-configured launch template and CA has no control. If a Pod requires a different type of node (e.g. ARM64 CPU, spot instance, etc), then we&amp;#8217;d first have to create a node group with the desired node type. Moreover, in the worst cases, one-at-a-time scale-up does not meet the increase of demand driven by Pod increases, causing nuances such as racing conditions. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because the Cluster Autoscaler doesn&amp;#8217;t really deal with the nodes themselves, this kind of integration is clunky and slow. Nearly half of Kubernetes customers on AWS report that configuring cluster auto scaling using the Kubernetes Cluster Autoscaler is challenging and restrictive, according to &lt;a href="https://aws.amazon.com/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/"&gt;this&lt;/a&gt; blog post. As a result, AWS launched an open-source cluster autoscaler project, &lt;a href="https://karpenter.sh/"&gt;Karpenter&lt;/a&gt;. Karpenter first only supported EKS but now the support includes other CSPs. For EKS, Karpenter directly interact with different types of EC2 instances.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Karpenter makes node scaling work in a more cloud-native manner. In the presence of unschedulable Pods, Karpenter &lt;span style="text-decoration: underline" class="underline"&gt;bypasses the Kubernetes scheduler&lt;/span&gt; and works directly with the Cloud provider, to launch the minimal compute resources needed to fit those Pods and immediately binds the Pods to the newly provisioned Nodes without waiting for scheduler. As Pods are removed or rescheduled to other nodes, Karpenter looks for opportunities to terminate under-utilized nodes. Karpender defines a CR called Provisioner to specify node provisioning configuration, such as instance size, zone, CPU architecture, etc. It is a manifest that describes a node group so the node scaler is aware of all the available node types. You can have multiple Provisioners for different needs, just like node groups. The Provisioner CR can also set TTL for empty Nodes, such that once a Node has no pods other than DaemonSet, Karpenter will terminate the Node on TTL expiry.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Karpenter&amp;#8217;s idea is similar to the idea of AutoPilot cluster in GKE. The new EKS workshop has an &lt;a href="https://www.eksworkshop.com/docs/autoscaling/compute/karpenter/"&gt;section&lt;/a&gt; on how to set up CA and Karpenter in practice.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-identity-management-for-eks"&gt;Identity Management for EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For IAM, we need to be concerned with three aspects. The management traffic to the cloud service, the management traffic for Kubernetes cluster and business traffic. &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;&lt;strong&gt;Traffic type&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;AWS&lt;/th&gt;&lt;th&gt;Azure&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;I. Cloud Service Endpoint (Management Traffic for Cloud Service)&lt;/td&gt;&lt;td&gt;AWS IAM identity&lt;/td&gt;&lt;td&gt;Azure RBAC&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;II. Kubernetes API (Management Traffic for K8s Cluster)&lt;/td&gt;&lt;td&gt;IAM mapping or OIDC&lt;/td&gt;&lt;td&gt;Azure RBAC (implementation of OIDC)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;III. Business traffic&lt;/td&gt;&lt;td&gt;Up to Kubernetes Ingress&lt;/td&gt;&lt;td&gt;Up to Kubernetes Ingress&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For business traffic (type III), it is all up to the Ingress. I&amp;#8217;ve written another article on &lt;a href="https://medium.com/slalom-build/managing-ingress-traffic-on-kubernetes-platforms-ebd537cdfb46"&gt;managing ingress traffic on Kubernetes platforms&lt;/a&gt;. We interact with cloud service endpoint (type II) with either AWS CLI or Terraform, to create any object, including resources needed for a cluster. This is generally how we work with cloud service, not specific to Kubernetes. Usually the IAM identity assumes another IAM role, which empowers it with a lot of permissions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For access to Kubernetes API (type III), EKS supports OIDC and IAM mapping. AWS documentation refers to this as &amp;#8220;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/cluster-auth.html"&gt;Cluster Authentication&lt;/a&gt;&amp;#8220;. There is one special scenario where your identity for type II access inherits your identity for type I access. As the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html"&gt;document&lt;/a&gt; puts:&lt;/p&gt;&#10;&lt;blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When you create an Amazon EKS cluster, the AWS Identity and Access Management (IAM) entity user or role, such as a&amp;nbsp;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html"&gt;federated user&lt;/a&gt;&amp;nbsp;that creates the cluster, is automatically granted&amp;nbsp;&lt;code&gt;system:masters&lt;/code&gt;&amp;nbsp;permissions in the cluster&amp;#8217;s role-based access control (RBAC) configuration in the Amazon EKS control plane. This IAM entity doesn&amp;#8217;t appear in any visible configuration, so make sure to keep track of which IAM entity originally created the cluster.&amp;nbsp;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This special scenario (I call it the &amp;#8220;&lt;strong&gt;implicit master&lt;/strong&gt; &lt;strong&gt;user&lt;/strong&gt;&amp;#8220;) allows us to perform critical activities on the cluster, such as creating IAM mapping, or OIDC configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The above addressed how AWS resource access Kubernetes resource. On the other hand, to address how a Kubernetes resource access AWS resources, we need IRSA (&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/setting-up-enable-IAM.html"&gt;IAM Roles for Service Account&lt;/a&gt;). We have a service account in Kubernetes and map it to an IAM role.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;AppMesh&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-app-mesh-service-mesh-for-microservices-on-aws/"&gt;AppMesh&lt;/a&gt; is AWS&amp;#8217; Envoy based service-mesh offering supporting Kubernetes cluster, ECS service and even EC2 instance. AppMesh&amp;#8217;s control plane is a managed AWS service, with a &lt;a href="https://aws.github.io/aws-app-mesh-controller-for-k8s/"&gt;controller&lt;/a&gt; running on the Kubernetes cluster. To install AppMesh on the cluster:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;On the EKS cluster, install AppMesh Controller using Helm&lt;/li&gt;&#10;&lt;li&gt;Associate the cluster with IAM OIDC provider&lt;/li&gt;&#10;&lt;li&gt;Create an IAM role for the appmesh-controller service account&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After these steps, you can create a mesh using CloudFormation, Terraform, etc. The data plane (Envoy proxy) can run on Kubernetes (as sidecar). Traffic between control plane and data plane can go through private link (Interface VPC &lt;a href="https://docs.aws.amazon.com/app-mesh/latest/userguide/infrastructure-security.html"&gt;endpoint&lt;/a&gt;) for added security. Like Istio, AppMesh enables mTLS. For observability, you can export Envoy metrics with Prometheus. Coupled with XRay, AppMesh also supports distributed tracing.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="832" height="354" src="https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway.webp" alt="" class="wp-image-12883" srcset="https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway.webp 832w, https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway-300x128.webp 300w, https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway-768x327.webp 768w" sizes="auto, (max-width: 832px) 100vw, 832px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AppMesh uses a different set of CRDs than Istio. Key CRDs are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Mesh&lt;/strong&gt;: represents an entire service mesh. At mesh level you can configure Egress filter (to allow or deny external traffic) and set IP version (v4 vs v6)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualGateway&lt;/strong&gt;: a CRD that represents an &lt;a href="https://aws.amazon.com/blogs/containers/introducing-ingress-support-in-aws-app-mesh/"&gt;Ingress&lt;/a&gt; in to the Mesh. A virtual gateway allows resources that are outside of your mesh to communicate to resources that are inside of your mesh. A virtual gateway references Envoy proxy deployment by podSelector. It references GatewayRoutes by namespaceSelector, and optionally gatewayRouteSelector. You also specify listeners in the manifest to reference Envoy proxy Service (LoadBalancer Type).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;GatewayRoute&lt;/strong&gt;: A gateway route is attached to a virtual gateway and routes traffic to an existing virtual service. If a route matches a request, it can distribute traffic to a target virtual service. In the manifest, you specify a list of httpRoute, each with matching condition and action. In the action section you can specify virtualService as target.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualService&lt;/strong&gt;: an abstraction of a real service provided by a virtual node directly or indirectly by means of a virtual router. Dependent services call your virtual service by its virtualServiceName, and those requests are routed to the VirtualNode or VirtualRouter that is specified as the provider for the VirtualService.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualRouter&lt;/strong&gt;: Virtual routers handle traffic for virtual services. In a virtual router manifest, you can define Route to direct incoming requests to virtual nodes as target.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualNode&lt;/strong&gt;: A virtual node acts as a logical pointer to a particular task group (i.e. ECS service, Kubernetes deployment). It represent a Service in the AppMesh. In the manifest, you reference Pods by podSelector, specify listeners for any inbound traffic that your virtual node expects, and specify serviceDiscovery for your task group.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can configure those Custom Resources using YAML manifests (and check the &lt;a href="https://docs.aws.amazon.com/app-mesh/latest/APIReference/Welcome.html"&gt;API reference&lt;/a&gt; a lot). Alternatively, you can configure them from AWS CLI or AWS console. The console will help you visualize what can be configured. For further details on how these CRs play together, there is a &lt;a href="https://www.appmeshworkshop.com/"&gt;workshop&lt;/a&gt; for AppMesh.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;EKS cluster using Terraform&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Officially, there is an &lt;a href="https://github.com/aws-ia/terraform-aws-eks-blueprints"&gt;EKS blueprint&lt;/a&gt; project for provisioning EKS cluster in Terraform.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I also keep my own Terraform code in the AWS directory of &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project. It works out to be a little more complex than my Terraform template to create Azure Kubernetes Cluster (Azure directory). Because I had to create Cognito resources with initial credential to allow users to connect to cluster without using the implicit master account.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Below is the diagram of the processes.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="776" height="496" src="https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod.webp" alt="" class="wp-image-12881" srcset="https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod.webp 776w, https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod-300x192.webp 300w, https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod-768x491.webp 768w" sizes="auto, (max-width: 776px) 100vw, 776px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Create EKS cluster with Terraform module&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The template configures kubectl access on a Bastion host, which assumed the same role that our IAM user uses to create the Kubernetes cluster. Therefore, the IAM role is the master identity. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the IAM user (power-user) has very powerful permissions. Usually it is ideal to assign lots of permission to IAM Roles (temporary credential) instead of IAM user (long-term credential). So the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html"&gt;role chaining&lt;/a&gt; would look like:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The IAM user that Terraform uses has no permission other than assuming a &amp;#8220;PowerUser&amp;#8221; role&lt;/li&gt;&#10;&lt;li&gt;The PowerUser role trusts the IAM user. It also has the permission to assume the &amp;#8220;EKS-Manager&amp;#8221; role&lt;/li&gt;&#10;&lt;li&gt;The EKS-Manager role trusts PowerUser&amp;#8217;s role session.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, the role chaining scenario above is not currently supported in &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/22728"&gt;Terraform&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use a Bastion host because the cluster endpoint is on private subnet. The bastion host is on a public subnet. However, if we do not like public subnet and public IP, we can place the bastion host on a private subnet, and use SSM system manager agent with &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/systems-manager-ssh-vpc-resources/"&gt;SSH tunnel plugin &lt;/a&gt;to have SSH access to private bastion host.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I first came across &lt;a href="https://medium.com/@andreidascalu/the-awfulness-of-aws-eks-d7700c1eccdc"&gt;this&lt;/a&gt; article about EKS and its awfulness and then decided to check out EKS. I&amp;#8217;m not sure all points are still valid but it&amp;#8217;s generally real-life experiences. There are also many peripheral services, such as AMP (AWS Managed Prometheus), AMG (AWS Managed Grafana), ADOT (AWS Distro for Open Telemetry), AppMesh (Another &lt;a href="https://www.appmeshworkshop.com/introduction/appmesh_components/"&gt;Envoy-based Service Mesh&lt;/a&gt;, &lt;a href="https://vedcraft.com/architecture/aws-appmesh-vs-istio-comparison-of-service-mesh/"&gt;easier to manage than Istio&lt;/a&gt; but less Powerful), with a lot to explore.&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/12/landing-zone-in-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Landing Zone in AWS – An Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Optimize CPU and Memory for Kubernetes Pod&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Build and Manage Kubernetes Clusters</title><link>https://static.digihunch.com/2022/09/build-a-kubernetes-cluster/</link><pubDate>Fri, 23 Sep 2022 11:50:00 -0400</pubDate><guid>https://static.digihunch.com/2022/09/build-a-kubernetes-cluster/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-cluster.webp" alt="Featured image of post Build and Manage Kubernetes Clusters" /&gt;&lt;p class="wp-block-paragraph"&gt;There are numerous options to build a Kubernetes cluster. If your company has a multi-cloud strategy, most likely you will have to deal with cluster creation on multiple cloud platform or on virtual machines on premise. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Most likely, the chosen cloud platform already make it simple for us. However, it is still important to understand what it really takes to build a Kubernetes cluster. In general, we need to figure out these tasks:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Decide where to host the computing infrastructure (i.e. Node) : on premise or public cloud;&lt;/li&gt;&#10;&lt;li&gt;Choose a Kubernetes release: either the vanilla release or one of the third-party distributions;&lt;/li&gt;&#10;&lt;li&gt;Install Kubernetes to the computing environment, and integrate it with the cloud platform;&lt;/li&gt;&#10;&lt;li&gt;Determine required add-ons (e.g. Istio or Linkerd for Service Mesh, dashboard utility, etc);&lt;/li&gt;&#10;&lt;li&gt;Deploy application workload to Kubernetes platform;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A public cloud platform provider usually can assist you with task 1 through 3, and partially 4, depending on the provider. If your Kubernetes resides on private cloud or on-prem environment, you can use a Platform solution such as VMware Tanzu or Openshift, which usually covers task 1, 3 and 4. There is no standard about what task these platform solution must address. Therefore it is important to have this list of tasks in mind in order to make a good comparison. I will discuss each of the tasks in this post.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-hosting-environment"&gt;Hosting environment&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Nodes are the building blocks of a Kubernetes cluster. We need master nodes as well as worker nodes. In addition, a working cluster also requires storage, and networking infrastructure. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Public cloud platforms typically provides control plane as a service, obviating administrator&amp;#8217;s effort to provision master nodes. For example, the control plane of Azure AKS has two levels of uptime commitment: a free tier of 99.5% SLO and a paid tier with an SLA of 99.95% (using AZs) and 99.9% (without using AZs). This uptime commitment applies to control plane only and do not apply to worker nodes. The management of etcd store is also a responsibility of the cloud provider, which frees up the cluster administrator from managing etcd store. However, they cannot access etcd store either. This is not very convenient because as the size of the cluster grows it is a common requirement to connect to etcd store for troubleshooting purpose.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The deployment APIs for public cloud allow the cluster administrator to define the instance size, count and availability zone for the worker nodes. They also automatically register the worker nodes to control plane so that the cluster administrators do not have to do so by themselves. As to &lt;a href="https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;storage&lt;/a&gt;, the public cloud usually provide some default storage classes based on their storage as service. For networking device, the cluster provision process automatically configures the cloud API so the cluster can manage cloud resources such as network load balancer. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With private cloud or data centre, we usually use virtual machines, or bare-metal servers. Cluster administrators will need to make their own control plane with master nodes. and install worker nodes and register them to the master nodes. The Kubernetes Installation section below will discuss this.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kubernetes release&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you have to install Kubernetes, you have to think about the Kubernetes release being used. You can use the binary from official Github &lt;a href="https://github.com/kubernetes/kubernetes"&gt;repository&lt;/a&gt;. For example, the &lt;a href="https://github.com/kubernetes/kubernetes/releases/tag/v1.24.3"&gt;release note&lt;/a&gt; of version 1.24.3 points to the &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md"&gt;change log&lt;/a&gt; file for &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#downloads-for-v1243"&gt;download&lt;/a&gt; links to &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#server-binaries"&gt;server binaries&lt;/a&gt;, &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/CHANGELOG/CHANGELOG-1.24.md#node-binaries"&gt;node binaries&lt;/a&gt;. This is the vanilla Kubernetes release.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the vanilla release, many developers build their own distributions, based off forks of the Kubernetes project. CNCF has a page to keep track of certified Kubernetes distributions. Some of the distributions are open source and can be used for on-prem infrastructure. Here is a list of top players:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-black-color has-cyan-bluish-gray-background-color has-text-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Distribution Name&lt;/th&gt;&lt;th&gt;Repo&lt;/th&gt;&lt;th&gt;Description&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://distro.eks.amazonaws.com/"&gt;EKS Distro&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/aws/eks-distro"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Used in EKS managed service or EKS Anywhere for on-prem infrastructure&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://docs.microsoft.com/en-us/azure-stack/user/azure-stack-kubernetes-aks-engine-overview?view=azs-2108#overview-of-the-aks-engine"&gt;AKS Engine&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/Azure/aks-engine"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Used in Azure Stack for on-prem infrastructure. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://cloud.google.com/kubernetes-engine/"&gt;Google Kubernetes Engine&lt;/a&gt;&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;td&gt;Used in GKE managed service only. &lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://docs.openshift.com/container-platform/4.8/welcome/oke_about.html"&gt;OpenShift Kubernetes Engine&lt;/a&gt;&lt;br&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/openshift/kubernetes"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Community distribution (OKD, or &lt;a href="https://www.okd.io/"&gt;OpenShift Kubernetes Distribution&lt;/a&gt;) is the open-source upstream.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://rancher.com/docs/rke/latest/en/"&gt;Rancher Kubernetes Engine&lt;/a&gt; (RKE)&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/rancher/rke"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;still using Docker as container runtime. Supported CNI include: Canal, Flannel, Calico and Weave&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://k3s.io/"&gt;K3s&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/k3s-io/k3s"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Lightweight distro without small resource requirement. Great for Edge, IoT, ARM etc&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;a href="https://docs.rke2.io/"&gt;RKE2&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/rancher/rke2"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;Originally named RKE government. Supports deployment via Cluster API. Supports containerd as container runtime. Supported CNI include: Cillium, Calico, Canal and Multus. Lightweight&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;VMware Tanzu&lt;/td&gt;&lt;td&gt;&lt;a href="https://github.com/vmware-tanzu/community-edition"&gt;Link&lt;/a&gt;&lt;/td&gt;&lt;td&gt;&lt;a href="https://tanzu.vmware.com/kubernetes-grid"&gt;VMWare Tanzu Grid&lt;/a&gt; and &lt;a href="https://tanzucommunityedition.io/"&gt;VMWare Tanzu Community&lt;/a&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Above is just a very incomplete list of Kubernetes distributions. There are many more distributions that are not on this list, such as CoreOS Tectonic, Docker Kubernetes, Heptio, Mesosphere, Mirantis, Platform9, Stackube, Telekube. For full details of how each distribution is different, you will need to go over their documents. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With the selected distribution, we still need to deploy the binaries to the nodes. We can do this with a cluster management platform, or standalone installers. Cluster management platform can also help us with baseline configuration (e.g. IAM integration, CNI plugin), in addition to the binary installation. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Cluster Management Platform&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These platforms are also sometimes referred to as container management platform.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For example, OpenShift container platform is a self-managed platform based on OpenShift Kubernetes Engine and can run on a variety of hosting environment, public cloud, or private cloud. The &lt;a href="https://docs.openshift.com/container-platform/4.7/installing/index.html"&gt;installation steps &lt;/a&gt;varies depending on the hosting environment. When running on public cloud such as &lt;a href="https://aws.amazon.com/rosa/"&gt;AWS&lt;/a&gt; (aka &lt;a href="https://docs.openshift.com/rosa/welcome/index.html"&gt;ROSA&lt;/a&gt;), the public cloud only provides computing nodes and associated infrastructure. Many corporate with multi-cluster strategy use this option on public cloud to keep their Kubernetes cluster fleet consistent across cloud vendors. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Openshift container platform also packages some useful open-source add-ons with corporate support, for example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/what-is-openshift-service-mesh"&gt;OpenShift Service Mesh&lt;/a&gt;: Istio&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/en/technologies/storage/ceph"&gt;Ceph Storage&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/en/technologies/storage/gluster"&gt;Gluster Storage&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.openshift.com/container-platform/4.10/cicd/gitops/understanding-openshift-gitops.html"&gt;OpenShift GitOps&lt;/a&gt; (ArgoCD)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.openshift.com/container-platform/4.10/cicd/pipelines/op-release-notes.html"&gt;OpenShift Pipelines&lt;/a&gt;&amp;nbsp;(Tekton)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/en/technologies/cloud-computing/quay"&gt;Quay&lt;/a&gt; (Quay Image Registry)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/openshift-streams-for-apache-kafka"&gt;OpenShift Streams for Apache Kafka&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://www.redhat.com/en/technologies/cloud-computing/openshift/serverless"&gt;OpenShift Serverless&lt;/a&gt; (Knative Serving)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Red Hat&amp;#8217;s strategy is to pick the most renowned open-source project in each domain and add enterprise support to it. However, for management portal, Red Hat developed its own &lt;a href="https://www.redhat.com/en/technologies/management/advanced-cluster-management"&gt;Advanced Cluster Management&lt;/a&gt; tool for Kubernetes, and &lt;a href="https://www.redhat.com/en/blog/open-sourcing-red-hat-advanced-cluster-management-kubernetes"&gt;open-sourced&lt;/a&gt; it in 2020 in the upstream &lt;a href="https://open-cluster-management.io/"&gt;project&lt;/a&gt; &lt;a href="https://github.com/open-cluster-management-io/OCM"&gt;Open Cluster Management&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to OpenShift, VMware Tanzu also attempts to cover the domains, with a smaller product portfolio:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://tanzu.vmware.com/service-mesh"&gt;Service Mesh&lt;/a&gt;: compatible with &lt;a href="https://tanzu.vmware.com/content/blog/istio-mode-tanzu-service-mesh"&gt;Istio&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://tanzu.vmware.com/mission-control"&gt;Mission Control&lt;/a&gt;: management portal&lt;/li&gt;&#10;&lt;li&gt;Observability&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Google &lt;a href="https://cloud.google.com/anthos/docs/concepts/overview"&gt;Anthos&lt;/a&gt; is also a container platform. Their product line include, but not limited to:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://cloud.google.com/anthos/config-management"&gt;Anthos Config Management&lt;/a&gt; (ACM)&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://cloud.google.com/anthos/service-mesh"&gt;Anthos Service Mesh&lt;/a&gt; (ASM, an Istio distribution)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;SUSE, the developer of RKE, RKE2, and K3s) offers Rancher as multi-cluster management platform. Apart from the engines, SUSE also offers Lonhorn as a storage solution. However, they do not have offerings for service mesh or GitOps. So there is no doubt that Red Hat OpenShift has the most complete portfolio for Kubernetes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are also companies that only offers management platforms without their own Kubernetes distribution. For example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://platform9.com/docs/kubernetes/about-pmk"&gt;Platform9&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rafay.co/"&gt;Rafay&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Product capabilities in this category vary a lot and you should refer to their specific documentation to understand. You will probably see a stack chart from each of the platform provider (e.g. SUSE Enterprise Container, &lt;a href="https://cloud.redhat.com/blog/introducing-red-hat-openshift-container-platform"&gt;OpenShift&lt;/a&gt;, &lt;a href="https://docs.vmware.com/en/VMware-Tanzu/services/tanzu-adv-deploy-config/GUID-components.html"&gt;Tanzu&lt;/a&gt;, &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/what-are-my-hybrid-and-multicloud-deployment-options-anthos"&gt;Anthos&lt;/a&gt;, &lt;a href="https://rafay.co/why-rafay/#what-rafay-does"&gt;Rafay&lt;/a&gt;) with all technology integrations.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Cluster Installation Tools&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As we saw in the installation steps for OpenShift, they are highly dependent on platform. With public cloud, the provisioning process also applies only to a specific platform. Since Kubernetes Installation process is tedious, some tools emerged to help, for example: &lt;a href="https://github.com/kubernetes-sigs/kubespray"&gt;kubespray&lt;/a&gt;, &lt;a href="https://github.com/kubernetes/kubeadm"&gt;kubeadm&lt;/a&gt;, &lt;a href="https://github.com/kubernetes/kops"&gt;kops&lt;/a&gt; and Cluster API. These are governed by &lt;a href="https://github.com/kubernetes/community/tree/master/sig-cluster-lifecycle"&gt;SIG cluster lifecycle&lt;/a&gt; special interest group. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here are some traditional options to install a Kubernetes clusters:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;kube-up&lt;/strong&gt;: the first tool to build cluster from 2015. It has been deprecated.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Kubeadm&lt;/strong&gt;: a tool built to provide best-practice &amp;#8220;fast paths&amp;#8221; for creating Kubernetes clusters that are minimum viable, and secure. Kubeadm&amp;#8217;s scope is limited to the local node filesystem and the Kubernetes API, and it is intended to be a composable building block of higher level tools. It is first released in Sep 2016. The high level configuration steps goes through initialization (kubeadm init), control plane (kubeadm join control plane), and node (kubeadm join node). Kubeadm does not integrate with cloud providers and it does not install addons (auth, monitoring, CNI, storage class)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Kubespray&lt;/strong&gt;: runs on bare metal or VMs using Ansible for provisioning and orchestration. The first release was in Oct 2015. Since v2.3 (Oct 2017) kubespray started to use kubeadm internally. In addition to kubeadm, kubespray configures CNI, storage class, other CRI. It supports cloud providers and air-gap environment. However it does not support infrastructure management.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The options above are official options. You may use kubeadm and kubespray to quickly (i.e. in an hour) spin up clusters for education purposes. However, with their limitations, it typically requires a lot of efforts to build a production-grade cluster with the needed addons and integrations. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the official options, there are also unofficial tools such as &lt;a href="http://kubicorn.io/"&gt;kubicorn&lt;/a&gt;, which was first introduced in 2018 as a cluster management framework with modular support for cloud providers. However it appears to be short-lived.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the next two sections, we introduce kops and cluster API, two most recent projects to install cluster.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Kops&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The kops utility directly perform the provisioning and orchestration via API to the cloud deployment engine. Kops, with first release in Oct 2016, is tightly integrated with the unique features of the cloud providers (e.g. AWS: ASG, ELB, EBS, KMS, S3, IAM). However, kops is only CLI without controller-style reconciliation. It does not support baremetal or vsphere. It also bundles addons with fixed version.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When picking a tool to install cluster, we need to strike a balance between how much simplification the tool brings, and how many different platform the installer can work with. &lt;a href="https://kops.sigs.k8s.io/"&gt;Kops&lt;/a&gt; appears to be such a good compromise. It works with a number of cloud platforms using different set of APIs, although most are in alpha and beta stages today. &lt;a href="https://kops.sigs.k8s.io/getting_started/aws/"&gt;Here&lt;/a&gt; is how to install cluster on AWS. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both kops and Cluster API have &lt;a href="https://thenewstack.io/cluster-api-kops-or-both-for-kubernetes-multicluster-deployments/"&gt;good momentum&lt;/a&gt; but they work differently. &lt;a href="https://cluster-api.sigs.k8s.io/"&gt;Cluster API&lt;/a&gt; was first released in Mar 2019, and is currently less mature than kops. However, it is declarative and may reflect the direction of where cluster lifecycle management is heading.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Cluster API&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://cluster-api.sigs.k8s.io/"&gt;Cluster API&lt;/a&gt; focuses on following areas:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Manage cluster &lt;span style="text-decoration: underline" class="underline"&gt;lifecycle &lt;/span&gt;declaratively&lt;/li&gt;&#10;&lt;li&gt;Infrastructure abstraction (e.g. computing, storage, networking, security, etc)&lt;/li&gt;&#10;&lt;li&gt;Utilizing existing tools (e.g. kubeadm, cloud-init)&lt;/li&gt;&#10;&lt;li&gt;Modular and pluggable: to be adaptable to different infrastructure providers.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It involves a number of CRs as illustrated in its &lt;a href="https://cluster-api.sigs.k8s.io/user/concepts.html#concepts"&gt;diagram&lt;/a&gt;. We should be clear on the providers for Bootstrap, Infrastructure and Control Plane.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The biggest benefit is the controller pattern to manage the entire lifecycle of a cluster. This allows managing clusters with GitOps, and rolling upgrade of the cluster. It also allows for declarative node scaling, self healing and multi-cluster management.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The client utility for is &lt;a href="https://cluster-api.sigs.k8s.io/clusterctl/overview.html"&gt;clusterctl&lt;/a&gt;, and with that along with the manifest, we can create a cluster in a few commands. A lot of workflows are still in development but we can take a look at its &lt;a href="https://cluster-api.sigs.k8s.io/user/quick-start.html#quick-start"&gt;quick start&lt;/a&gt; guide to get a taste of how it works. The installation steps vary a lot based on the environment and the cluster. Also it introduces the separation of management cluster and workload cluster.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Workload cluster is the target cluster being created, as per the manifests.&lt;/li&gt;&#10;&lt;li&gt;Management cluster is where you keep track of the workload cluster being managed. You can manage multiple workload clusters from a single management cluster. Note that this management cluster will store credentials about workload clusters, and may become a single point of failure.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although Cluster API reflects a great initiative to standardize the provisioning of Kubernetes cluster, whether it will succeed has to do with the level of complexity. In the next section, we will get a taste of how it looks to deploy a Kubernetes cluster in a lab.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="516" height="181" src="https://static.digihunch.com/wp-content/uploads/2022/08/diagram.png" alt="" class="wp-image-6757"/&gt;&lt;figcaption class="wp-element-caption"&gt;Management cluster vs workload cluster&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the lab, I use my MacBook to create a management cluster with &lt;a href="https://kind.sigs.k8s.io/"&gt;KinD&lt;/a&gt;. Then we configure a workload cluster in AWS from the management cluster. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Cluster API Lab&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the steps here are based on the &lt;a href="https://cluster-api.sigs.k8s.io/user/quick-start.html#quick-start"&gt;quick start guide&lt;/a&gt; on Cluster API document. Also, there is a bug with the AWS provider so the end of the lab will report a warning. The main purpose of this lab is to demonstrate how Cluster API is supposed to work, even though it still has yet to mature.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To start, I install clusterctl (the cluster API client utility), clusterawsadm (the utility specific for AWS) on MacBook, then start a simple KinD cluster.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -L https://github.com/kubernetes-sigs/cluster-api/releases/download/v1.2.0/clusterctl-darwin-amd64 -o clusterctl&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod +x ./clusterctl&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo mv ./clusterctl /usr/local/bin/clusterctl&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clusterctl version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -L https://github.com/kubernetes-sigs/cluster-api-provider-aws/releases/download/v1.4.1/clusterawsadm-darwin-amd64 -o clusterawsadm&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;chmod +x clusterawsadm&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo mv clusterawsadm /usr/local/bin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clusterawsadm version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind create cluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;So far, I installed the required utility and a KinD cluster on MacBook. Then I use clusterawsadm to create InstanceProfile, ManagedPolicy and IAM Roles required for cluster creation. The AWS region and access are configured as environment variables:&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;export AWS_REGION&lt;span style="color:#f92672"&gt;=&lt;/span&gt;us-east-1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export AWS_ACCESS_KEY_ID&lt;span style="color:#f92672"&gt;=&lt;/span&gt;AKIAXXXXXXXXXXX&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export AWS_SECRET_ACCESS_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;J8ByduiofpwuisDjDoijOISDs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clusterawsadm bootstrap iam create-cloudformation-stack&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This runs a CloudFormation stack to create the permission related resources:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1556" height="464" src="https://static.digihunch.com/wp-content/uploads/2022/08/image-1.png" alt="" class="wp-image-6795"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then I initialize the management cluster with the clusterctl utility, specifying AWS as a provider. I also need to assign the environment variable AWS_B64ENCODED_CREDENTIALS with proper value: &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;export AWS_B64ENCODED_CREDENTIALS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;clusterawsadm bootstrap credentials encode-as-profile&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clusterctl init --infrastructure aws&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now I use clusterctl to generate the manifest for the workload cluster. In environment variables, I specify cluster and node sizes, SSH key name, control plane machine type and node machine type:&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;export AWS_SSH_KEY_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;cskey&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export AWS_CONTROL_PLANE_MACHINE_TYPE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;t3.large&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export AWS_NODE_MACHINE_TYPE&lt;span style="color:#f92672"&gt;=&lt;/span&gt;t3.large&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clusterctl generate cluster myekscluster --kubernetes-version 1.24.3 --control-plane-machine-count&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; --worker-machine-count&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; &amp;gt; capi-quickstart.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f capi-quickstart.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;At the end I tell the management cluster to create a workload cluster as per the manifest, by simply declaring the CRs. It will take some time for the cluster to create, and there are a number of ways to monitor the progress. You can monitor the log on the controller pods in their respect namespaces. You can also check the cluster status with:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get kubeadmcontrolplane&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clusterctl describe cluster myekscluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Currently there is a &lt;a href="https://github.com/kubernetes-sigs/cluster-api/issues/6417"&gt;bug&lt;/a&gt; and the commands at the end will report as below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="2423" height="206" src="https://static.digihunch.com/wp-content/uploads/2022/08/image.png" alt="" class="wp-image-6785"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Hopefully the bug will be fixed shortly. To delete the cluster, simply delete the resources in the manifest with kubectl delete -f capi-quickstart.yaml&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are numerous ways to build a Kubernetes cluster. Before deciding on the approach, I recommend having a full understanding of the hosting environment. This is because installation approach and hosting environment are still tightly coupled. This is the status quo and is not going to change in the near future. Both kops and cluster API reflects initiative to decouple the two but both are still in early stage and already facing growing complexity. Cluster API manages complexity with CRDs to abstract system resources and infrastructure, as illustrated here:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="501" height="669" src="https://static.digihunch.com/wp-content/uploads/2022/08/image-7.png" alt="" class="wp-image-7086"/&gt;&lt;figcaption class="wp-element-caption"&gt;CRDs and providers to abstract system resources and infrastructure&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The diagram is from the &amp;#8220;&lt;a href="https://www.oreilly.com/library/view/cluster-api-and/9781098126865/"&gt;Cluster API and declarative Kubernetes Management&lt;/a&gt;&amp;#8221; white paper. &lt;a href="https://www.cncf.io/online-programs/cluster-api-yesterday-today-tomorrow/"&gt;Here &lt;/a&gt;is a stream with more about the same topic.&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/09/minio-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;MinIO for S3-compatible Object Storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/10/graphql-and-grpc/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;GraphQL and gRPC&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>MinIO for S3-compatible Object Storage</title><link>https://static.digihunch.com/2022/09/minio-object-storage/</link><pubDate>Fri, 09 Sep 2022 09:00:00 -0400</pubDate><guid>https://static.digihunch.com/2022/09/minio-object-storage/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-minio.webp" alt="Featured image of post MinIO for S3-compatible Object Storage" /&gt;&lt;p class="wp-block-paragraph"&gt;I reviewed some storage technologies on Kubernetes but they are all for block and file storage. In this post, I will discuss the current available options for container workload to use object storage. I will also touch on MinIO as an object storage solution.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-object-storage"&gt;Object storage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Block and file system are more native to operating system because they present themselves to the OS as a block device or file system attached to the OS. In other words, application processes running on the OS will be able to access the storage by address expressed as a POSIX-compatible path. On the contrary, object storage is a REST API service, operating at the application layer in the TCP/IP stack. Therefore, we can think of object storage as &amp;#8220;storage as a web service&amp;#8221;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Object storage can be made very cheap. However, the application protocol may vary depending on the object storage provider. Amazon S3 is a forerunner in object storage market and its protocol has emerged as the de-facto standard for object storage. When building an application and if there is one object storage protocol to support, it should be S3. For non-S3 object storage services, we can front them with an S3 interface, if the provider itself does not have one. For example Ceph storage has its &lt;a href="https://docs.ceph.com/en/latest/radosgw/s3/"&gt;Gateway S3 API&lt;/a&gt;. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Container Object Storage Interface&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we use S3 as the universal object storage protocol, does that also address object storage access with container workload on Kubernetes? Absolutely. Nonetheless, for a number of reasons using REST API from containers are not the best option. From platform&amp;#8217;s perspective, it is the platform that should define how to access object storage, instead of leaving it with an application-layer protocol. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When a pattern (for storage, or networking, etc) turns out very common, the platform layer should incorporate it as an infrastructure service, manage it with its own standard, and provide it to application so that developer can focus on business features. With that vision, the community brought up the &lt;a href="https://github.com/kubernetes-sigs/container-object-storage-interface"&gt;Container Object Storage Interface&lt;/a&gt; (COSI) initiative. It is currently in very early stage, but the idea is to commoditize object storage in Kubernetes platform with a unified interface. For more background about this initiative, refer to the post &amp;#8220;&lt;a href="https://thenewstack.io/beyond-block-and-file-cosi-enables-object-storage-in-kubernetes/"&gt;Beyond block and file &amp;#8211; COSI enables object storage in Kubernetes&lt;/a&gt;&amp;#8220;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;COSI is the ultimate cloud native solution but it is still in pre-alpha phase as of mid 2022. Unfortunately, it is not a recommended solution to any real-life project in 2022, and we are stuck with the unified API approach until COSI matures.. The unified API approach is by no means cloud native, but has come to maturity for adoption. S3 Rest API is our friend, regardless of whether the client process is in a container or not.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Update: on Sept 2, 2022, Kubernetes &lt;a href="https://kubernetes.io/blog/2022/09/02/cosi-kubernetes-object-storage-management/"&gt;introduced COSI&lt;/a&gt; as alpha feature.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;MinIO Introduction&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In order to use S3 protocol without using Amazon S3 storage, we can use MinIO to build our own object storage service serve client via a S3-compatible REST API interface. The main developer of the &lt;a href="https://min.io/"&gt;MinIO&lt;/a&gt; project is MinIO Inc, a startup from 2014. Having learned the lessons from GlusterFS, the founders and developers make MinIO very simple. MinIO operates in two modes: gateway mode (soon to be legacy) and server mode.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the Gateway mode, MinIO as a gateway between client and destination storage, and does not persist data to itself. In the past, the destination storage can be Azure Blob and Google Cloud Storage (GCS) and HDFS as backend. However, these supports are &lt;a href="https://github.com/minio/minio/pull/14418"&gt;deprecated&lt;/a&gt; now. The current release (July 2022) only supports S3 and NAS backend. According to MinIO&amp;#8217;s blog &lt;a href="https://blog.min.io/deprecation-of-the-minio-gateway/"&gt;post&lt;/a&gt; from February 2022, the entire MinIO Gateway feature will be removed in August, leaving server mode the only option for MinIO.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the Server mode, the MinIO service will persist data to itself in a file system (or volume). You can specify that file system (or volume) as you launch the server. As one of the &lt;a href="https://docs.min.io/docs/minio-quickstart-guide.html"&gt;quick-start guides&lt;/a&gt; shows, we can host MinIO server using a single executable. For administrative tasks, MinIO has a web console and a client utility called mc.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;MinIO Deployment Options&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For storage service, there are a number of &lt;a href="https://docs.min.io/minio/baremetal/installation/deployment-and-management.html"&gt;deployment options&lt;/a&gt;: &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;SNSD (single-node, single-drive): single MinIO server with a single storage volume or folder. &lt;/li&gt;&#10;&lt;li&gt;SNMD (signle-node, multi-drive): single MinIO server with four or more storage volumes.&lt;/li&gt;&#10;&lt;li&gt;MNMD (multi-node, multi-drive, aka distributed): multiple MinIO servers with at least four drives across all servers. This should be considered for production grade configuration.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The deployment options above describes the node and volume topology. No matter which topology option, there are also a number of ways to host the MinIO service process: on &lt;a href="https://min.io/docs/minio/linux/index.html"&gt;Linux OS&lt;/a&gt;, &lt;a href="https://min.io/docs/minio/windows/index.html"&gt;Windows OS&lt;/a&gt;, &lt;a href="https://min.io/docs/minio/macos/index.html"&gt;MacOS&lt;/a&gt;, &lt;a href="https://min.io/docs/minio/container/index.html"&gt;Docker Container&lt;/a&gt;, and on &lt;a href="https://min.io/docs/minio/kubernetes/upstream/index.html"&gt;Kubernetes&lt;/a&gt; platform. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In addition, MinIO Inc ships the software under different business models. For example, there are fully managed applications in &lt;a href="https://web.archive.org/web/20220927211802/https://azuremarketplace.microsoft.com/en-us/marketplace/apps/minio.minio-object-storage_v1dot1"&gt;Azure Marketplace&lt;/a&gt;, &lt;a href="https://aws.amazon.com/marketplace/pp/prodview-smchi7bcs4nn4"&gt;AWS Marketplace&lt;/a&gt;, and &lt;a href="https://console.cloud.google.com/marketplace/product/minio-inc-public/minio-enterprise"&gt;GCP Marketplace&lt;/a&gt; all hosted on virtual machines with extra charges. Clients not willing to pay can host MinIO storage all on their own, either on virtual machines, or on managed Kubernetes environment provided by each cloud provider. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;MinIO Hosting solutions&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MinIO lists these hosting solutions under multi-cloud products. These hosting solutions (or &amp;#8220;products&amp;#8221; in MinIO&amp;#8217;s term) vary in terms of where peripheral services and data tiers are hosted. Here is the list of the supported platforms:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/kubernetes"&gt;(generic) Kubernetes&lt;/a&gt;;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/private-cloud-vmware-tanzu"&gt;VMWare Tanzu&lt;/a&gt;;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/private-cloud-red-hat-openshift"&gt;OpenShift&lt;/a&gt;;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/multicloud-suse-rancher"&gt;SUSE Rancher&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/multicloud-elastic-kubernetes-service"&gt;EKS&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/multicloud-azure-kubernetes-service"&gt;AKS&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://min.io/product/multicloud-google-kubernetes-service"&gt;GKE&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To illustrate how these solutions are different, I put some details on a few options together for an incomplete comparison below:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Kubernetes&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;EKS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;AKS&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;GKE&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Hot Storage&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Direct PV (NVMe)&lt;/td&gt;&lt;td&gt;EKS EBS CSI&lt;/td&gt;&lt;td&gt;Azure CSI &lt;/td&gt;&lt;td&gt;GKE Standard SSD&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Warm Storage&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Direct PV (HDD)&lt;/td&gt;&lt;td&gt;S3 IA&lt;/td&gt;&lt;td&gt;Azure BlobStore&lt;/td&gt;&lt;td&gt;GCS&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Cold Storage&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Public Cloud storage&lt;/td&gt;&lt;td&gt;Glacier&lt;/td&gt;&lt;td&gt;Azure Cool Blob&lt;/td&gt;&lt;td&gt;GCS for Data Archiving&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Encryption&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;HashiCorp Vault&lt;/td&gt;&lt;td&gt;KMS&lt;/td&gt;&lt;td&gt;Azure Key Vault&lt;/td&gt;&lt;td&gt;Cloud Key Management&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Observability&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Elastic Stack and Grafana&lt;/td&gt;&lt;td&gt;Managed ElasticSearch Prometheus&lt;/td&gt;&lt;td&gt;Azure Monitor&lt;/td&gt;&lt;td&gt;Stack Driver&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Identity Provider&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;KeyCloak&lt;/td&gt;&lt;td&gt;LDAP, SSO&lt;/td&gt;&lt;td&gt;Azure Active Directory&lt;/td&gt;&lt;td&gt;GCP Cloud Identity&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;LB and Cert Mgmt&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;Nginx, Let&amp;#8217;s Entrypt&lt;/td&gt;&lt;td&gt;AWS Cert Mgr, ELB&lt;/td&gt;&lt;td&gt;Azure Load Balancer, JetStack, Let&amp;#8217;s Encrypt&lt;/td&gt;&lt;td&gt;GCP Cloud LB and Managed Cert&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that all of these hosting solutions are based on some flavour of Kubernetes. The hot tier is usually based on storage options available to the platform. MinIO service access this hot tier via Kubernetes persistent volume. The warm and cold tiers are backed by different object storage service. Between MinIO and storage client, it always use the same S3 compatible Rest API.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MinIO also has tiering capability. While the hot storage destination has to be either a file system or Kubernetes persistent volume, remote tiers can be S3 , Azure Blob, or GCS. MinIO supports encryption at rest (SSE-KMS, SSE-S3, SSE-C) and in transit (TLS) for security, as well as many other useful features such as object &lt;a href="https://docs.min.io/minio/baremetal/replication/replication-overview.html"&gt;replication&lt;/a&gt;, &lt;a href="https://docs.min.io/minio/baremetal/object-retention/bucket-versioning.html"&gt;versioning&lt;/a&gt;, &lt;a href="https://docs.min.io/minio/baremetal/object-retention/minio-object-locking.html"&gt;locking&lt;/a&gt;, &lt;a href="https://docs.min.io/minio/baremetal/monitoring/bucket-notifications/bucket-notifications.html"&gt;events&lt;/a&gt;, Prometheus &lt;a href="https://docs.min.io/minio/baremetal/monitoring/metrics-alerts/minio-metrics-and-alerts.html"&gt;metrics&lt;/a&gt;, &lt;a href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html"&gt;lifecycle management&lt;/a&gt; etc. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Connect to MinIO server with S3 client&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To validate that the client is compatible, we use MinIO&amp;#8217;s client utility (mc) to connect to an AWS S3 bucket. Then we use AWS CLI to connect to a MinIO server, similar to this &lt;a href="https://docs.min.io/docs/aws-cli-with-minio"&gt;instruction&lt;/a&gt;. To do so, we first install client and server utilities:&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;brew install minio/stable/minio&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install minio/stable/mc&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minio --version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc --version&#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 start MinIO server and store an object using AWS CLI&amp;#8217;s S3 tool. In our working directory, we create a new directory called minio_data and launch MinIO server with it:&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 minio_data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minio server minio_data --console-address :9090&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once the server is up, the screen should display the details, including the portal URL and the default username and password will be used as Access Key ID and Secret Key:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1307" height="649" src="https://static.digihunch.com/wp-content/uploads/2022/07/image-2.png" alt="" class="wp-image-6276"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the MinIO service does NOT have &lt;a href="https://docs.min.io/docs/how-to-secure-access-to-minio-server-with-tls.html"&gt;TLS enabled&lt;/a&gt; by default, on the console or API service. At this point, we can browse to the console web page using the given credential. Then, we can configure AWS CLI with a new profile just to act as a client to communicate with the MinIO server:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws configure --profile minio-cli&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AWS Access Key ID &lt;span style="color:#f92672"&gt;[&lt;/span&gt;None&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: minioadmin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;AWS Secret Access Key &lt;span style="color:#f92672"&gt;[&lt;/span&gt;None&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: minioadmin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Default region name &lt;span style="color:#f92672"&gt;[&lt;/span&gt;None&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: us-east-1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Default output format &lt;span style="color:#f92672"&gt;[&lt;/span&gt;None&lt;span style="color:#f92672"&gt;]&lt;/span&gt;: json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws configure set default.s3.signature_version s3v4 --profile minio-cli&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;At this point, the AWS CLI is configured to communicate with MinIO server. Then, we can create bucket, list object in the bucket, copy an object to the bucket, etc&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws --endpoint-url http://127.0.0.1:9000 s3 ls --profile minio-cli &lt;span style="color:#75715e"&gt;# list all bucket, should return empty&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws --endpoint-url http://127.0.0.1:9000 s3 mb s3://hehebucket --profile minio-cli &lt;span style="color:#75715e"&gt;# create new bucket&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;make_bucket: hehebucket&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws --endpoint-url http://127.0.0.1:9000 s3 cp README.md s3://hehebucket --profile minio-cli &lt;span style="color:#75715e"&gt;# copy a file to bucket as a new object&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;upload: ./README.md to s3://hehebucket/README.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ aws --endpoint-url http://127.0.0.1:9000 s3 ls s3://hehebucket --profile minio-cli &lt;span style="color:#75715e"&gt;# list objects in the bucket&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2022-07-09 00:30:23 &lt;span style="color:#ae81ff"&gt;631&lt;/span&gt; README.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The created bucket and object are also visible in MinIO web console, under &amp;#8220;Bucket&amp;#8221;:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="947" height="235" src="https://static.digihunch.com/wp-content/uploads/2022/07/image-3.png" alt="" class="wp-image-6287"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps above validate that AWS CLI can talk to MinIO server. Because of that, MinIO server can emulate an S3 service in any development environment so users do not always have to use S3 from AWS. This makes sense for both cost and security reasons for the organization. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Connect to S3 with MinIO client&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this lab, we create an S3 bucket and use mc utility to store an object to it. In order to consistently create S3 bucket and associated permissions, I use the CloudFormation template in &lt;a href="https://github.com/digihunch/cloudformation/blob/master/obj-store-helper/aws-s3-stack.yaml"&gt;this&lt;/a&gt; repo. The output of the CloudFormation stack returns the Access Key ID and Secret Key required for the client to access the bucket. Once we cloned the repo, let&amp;#8217;s enter the &lt;a href="https://github.com/digihunch/cloudformation/tree/master/obj-store-helper"&gt;obj-store-helper&lt;/a&gt; directory, and run aws cli command to launch the CloudFormation template, assuming it has been configured:&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;BUCKET_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;c0sas2dsadigihunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;S3_STACK_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$BUCKET_NAME-stack&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;aws cloudformation create-stack --template-body file://aws-s3-stack.yaml --stack-name $S3_STACK_NAME --parameters ParameterKey&lt;span style="color:#f92672"&gt;=&lt;/span&gt;S3BucketName,ParameterValue&lt;span style="color:#f92672"&gt;=&lt;/span&gt;$BUCKET_NAME --capabilities CAPABILITY_IAM&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# to delete stack after test, run: aws cloudformation delete-stack --stack-name $S3_STACK_NAME&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 AWS console, we should see the configuration information as below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="925" height="708" src="https://static.digihunch.com/wp-content/uploads/2022/07/image-1.png" alt="" class="wp-image-6266"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Supposed the bucket name is vna-tst-c0sas2dsadigihunch as shown above, this allows us to configure the client utility MC as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc alias set awss3 https://s3.amazonaws.com &lt;span style="color:#75715e"&gt;# Fill in access key ID and Secret key at the prompt&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc ls awss3/vna-tst-c0sas2dsadigihunch &lt;span style="color:#75715e"&gt;# list objects in the bucket, should return empty&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc cp README.md awss3/vna-tst-c0sas2dsadigihunch/README.md &lt;span style="color:#75715e"&gt;# upload and object to bucket&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc ls awss3/vna-tst-c0sas2dsadigihunch &lt;span style="color:#75715e"&gt;# list objects in the bucket, the uploaded object should be there&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc rm awss3/vna-tst-c0sas2dsadigihunch/README.md &lt;span style="color:#75715e"&gt;# delete the object&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;mc alias remove awss3 &lt;span style="color:#75715e"&gt;# remove awss3 alias&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 emptied the bucket, we can delete the CloudFormation stack. This test only needs client utility mc to verify that MinIO client is able to talk to AWS S3 server.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Erasure Coding&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For scalable production use, we should deploy MinIO in distributed mode. When MinIO is configured in &lt;a href="https://docs.min.io/minio/baremetal/installation/deploy-minio-distributed.html"&gt;distributed deployment&lt;/a&gt; (MNMD, or multi-node, multi-drive), it implicitly enables an important feature called &lt;a href="https://docs.min.io/minio/baremetal/concepts/erasure-coding.html#minio-erasure-coding"&gt;erasure coding&lt;/a&gt;. This erasure coding feature further unlocks a number of other MinIO features:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://docs.min.io/minio/baremetal/object-retention/bucket-versioning.html#minio-bucket-versioning"&gt;Object Versioning&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.min.io/minio/baremetal/replication/bucket-replication-overview.html#minio-bucket-replication-serverside"&gt;Server-Side Replication&lt;/a&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://docs.min.io/minio/baremetal/reference/minio-mc/mc-retention-set.html#minio-bucket-locking"&gt;Write-Once Read-Many (WORM) Locking&lt;/a&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Erasure coding is MinIO&amp;#8217;s data redundancy and availability feature that allows MinIO deployments to automatically reconstruct objects on-the-fly despite the loss of multiple drives or nodes in the cluster. Erasure coding provides object-level handling with less overhead than adjacent technologies such as RAID. The key concept is &lt;a href="https://docs.min.io/minio/baremetal/concepts/erasure-coding.html#erasure-sets"&gt;Erasure Set&lt;/a&gt;, a set of drives in a MinIO deployment that supports Erasure Coding. MinIO evenly distributes object data and parity blocks among the drives in the Erasure Set. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Two important variables are M and N: for a given erasure set of size M, MinIO splits objects into N parity blocks, and M-N data blocks. MinIO uses the &lt;a href="https://docs.min.io/minio/baremetal/concepts/erasure-coding.html#erasure-code-parity-ec-n"&gt;EC:N&lt;/a&gt; notation to refer to the number of parity blocks (N) in the deployment. To determine optimal erasure set size for the cluster, use MinIO&amp;#8217;s &lt;a href="https://min.io/product/erasure-code-calculator"&gt;Erasure Coding Calculator&lt;/a&gt; tool.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To help client to specify per-object parity with Erasure Coding, MinIO uses storage classes. Note that the storage class concept in MinIO is distinct from AWS &lt;a href="https://aws.amazon.com/s3/storage-classes/"&gt;S3 storage class&lt;/a&gt; or Kubernetes &lt;a href="https://kubernetes.io/docs/concepts/storage/storage-classes/"&gt;storage class&lt;/a&gt;. In MinIO, a &lt;a href="https://github.com/minio/minio/tree/master/docs/erasure/storage-class"&gt;storage class&lt;/a&gt; defines parity settings per object. The STANDARD &lt;a href="https://docs.min.io/minio/baremetal/concepts/erasure-coding.html#storage-classes"&gt;storage class&lt;/a&gt; (default) defines EC:N based on M, which can be overridden. In addition, there is REDUCED_REDUNDANCY storage class, whose parity must be less than or equal to that of STANDARD storage class. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://docs.min.io/minio/baremetal/concepts/erasure-coding.html#bitrot-protection"&gt;erasure coded backend&lt;/a&gt; also protects the storage against &lt;a href="https://github.com/minio/minio/blob/master/docs/erasure/README.md#what-is-bit-rot-protection"&gt;Bit Rot&lt;/a&gt; with HighwayHash algorithm. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;More Features&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Authentication and authorization between MinIO client and MinIO server have a number of options. MinIO client may use the built-in standalone identity management in MinIO server. This is the default mode. In addition, one may delegate IAM to external service. To Active Directory via LDAP, or any Identity provider that supports OIDC (JWT with Authorization Code Flow). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to &lt;a href="https://docs.min.io/minio/baremetal/lifecycle-management/lifecycle-management-overview.html"&gt;Object Lifecycle Management&lt;/a&gt; (OLM), MinIO allows you to define a remote tier storage for each local target (bucket). The remote tier can be Amazon S3, Google Cloud Storage or Azure Blob storage. We can use mc utility to administer the remote tier and OLM. Configuration steps (e.g. &lt;a href="https://docs.min.io/minio/baremetal/lifecycle-management/transition-objects-to-azure.html"&gt;Azure&lt;/a&gt; Blob, &lt;a href="https://docs.min.io/minio/baremetal/lifecycle-management/transition-objects-to-s3.html"&gt;AWS S3&lt;/a&gt;) usually include:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Configure required permissions on the MinIO bucket, create user account for OLM activities. &lt;/li&gt;&#10;&lt;li&gt;Configure the Remote Storage Tier&lt;/li&gt;&#10;&lt;li&gt;Create and Apply an ILM Transition Rule. The rule can be expressed in a json document.&lt;/li&gt;&#10;&lt;li&gt;Validate the creation of ILM transition rule&lt;/li&gt;&#10;&lt;li&gt;Validate the effect of transition rule. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As for encryption, MinIO can support encryption at rest. It can also work with &lt;a href="https://static.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/"&gt;etcd&lt;/a&gt; store to store encrypted IAM assets if KMS is configured. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Even though we watch for the progress of COSI initiative, we still use Rest API to access object storage from container, which is no different than from a virtual machine. If we develop an application, then we should make it support S3 protocol, a de-facto standard protocol for object storage. As for the storage backend, if we want to be vendor neutral, the feature-rich MinIO is the best bet. We can use MinIO to build our own Object storage as a service compatible with S3. We can also lifecycle our object to remote object storage tier backed by Azure, GCP or S3. In this post we validated the S3 compatibility, and discussed some advanced MinIO features.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 3 of 3 – Ceph by Rook&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/09/build-a-kubernetes-cluster/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Build and Manage Kubernetes Clusters&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Storage on Azure 3 of 3 – Ceph by Rook</title><link>https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/</link><pubDate>Fri, 26 Aug 2022 19:43:00 -0400</pubDate><guid>https://static.digihunch.com/2022/08/storage-solution-on-aks-2-of-3-ceph-by-rook/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-storage-3.webp" alt="Featured image of post Kubernetes Storage on Azure 3 of 3 – Ceph by Rook" /&gt;&lt;p class="wp-block-paragraph"&gt;In the last two posts, I covered the native storage options on Azure Kubernetes Service, as well as Portworx as an example of a proprietary Software Defined Storage (SDS) solution. There are also a number of open-source alternative SDS solutions. Ceph has nearly a decade of history from prior to containerization, and is the most widely adopted storage platform. In this post, we continue to explore Ceph as an open-source storage solution on Azure Kubernetes. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-ceph-by-rook"&gt;Ceph by Rook&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ceph is an open-source SDS platform for distributed storage on a cluster and provides object, block and file storage. Installation of Ceph SDS can be complex, especially on Kubernetes platform. &lt;a href="https://rook.io/"&gt;Rook&lt;/a&gt; is a graduated CNCF project to orchestrate storage platform. Rook by itself is not SDS and it supports:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/Getting-Started/intro/"&gt;Ceph&lt;/a&gt;: configure a Ceph cluster. Think of this as the equivalent of &lt;a href="https://docs.ceph.com/en/quincy/cephadm/"&gt;cephadm&lt;/a&gt; on Kubernetes platform.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/nfs/v1.7/"&gt;NFS&lt;/a&gt;: configure an NFS server. Think of this as the equivalent of nfsd daemon on Kubernetes platform.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/cassandra/v1.7/"&gt;Cassandra&lt;/a&gt;: an operator to configure a Cassandra database cluster. It is now &lt;strong&gt;deprecated&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We play with Rook Ceph. I also refer to it as Ceph by Rook. The contribution of Rook project is it simplifies the installation as a matter of declaring custom resources using CRDs. Here are some high-level CRDs to know:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/ceph-cluster-crd/"&gt;CephCluster&lt;/a&gt;: creates a Ceph storage cluster&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Block-Storage/ceph-block-pool-crd/"&gt;CephBlockPool&lt;/a&gt;: represents a block pool&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Shared-Filesystem/ceph-filesystem-crd/"&gt;CephFilesystem&lt;/a&gt;: represents a file system&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/Object-Storage/ceph-object-store-crd/#example"&gt;CephObjectStore&lt;/a&gt;: represents an object store&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://rook.io/docs/rook/v1.9/CRDs/ceph-nfs-crd/"&gt;CephNFS&lt;/a&gt;: spins up a NFS Ganesha server to export NFS shares of a CephFilesystem or CephObjectStore.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As with typical Kubernetes resources in controller pattern, Ceph by Rook needs an operator along with custom resources. We can use YAML manifest for both of them, and the manifests are usually very tediously long. We can also use Helm to install both of them, by providing a value file. Now we will install Ceph on AKS.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Install Ceph Operator on AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps are influenced by two relevant posts (&lt;a href="https://carlos.mendible.com/2021/10/23/aks-high-available-storage-with-rook-and-ceph/"&gt;here&lt;/a&gt; and &lt;a href="https://github.com/evillgenius75/rook-aks"&gt;here&lt;/a&gt;). However, I&amp;#8217;ve incorporated the cluster configuration in the &lt;a href="https://github.com/digihunch/cloudkube/tree/main/azure"&gt;Azure directory of the cloudkube project&lt;/a&gt;, a modular Terraform template to configure AKS cluster and facilitate storage configuration. The node group and instance sizes are selected to be just enough to run a ceph POC cluster with minimum cost. One of the node groups is tainted with storage-node, as if the following command were run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl taint nodes my-node-pool-node-name storage-node&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true:NoSchedule&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;You will only need to taint the nodes with the command above if you choose not to use the cloudkube template. The taint ensures that only Pods with corresponding toleration and effect can be scheduled to those nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Helm to install Rook Operator. We need a value file (e.g. rook-ceph-operator-values.yaml) with content as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//github.com/rook/rook/blob/master/Documentation/Helm-Charts/operator-chart.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;crds&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;csi&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;provisionerTolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effect&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NoSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;pluginTolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effect&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;NoSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;agent&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;AKS&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//rook.github.io/docs/rook/v1.7/flexvolume.html#azure-aks&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;flexVolumeDirPath&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;/etc/kubernetes/volumeplugins&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we install the operator with Helm:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install rook-ceph-operator rook-ceph --namespace rook-ceph --create-namespace --version v1.9.6 --repo https://charts.rook.io/release/ --values rook-ceph-operator-values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n rook-ceph get po -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;rook-ceph-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After installing the operator, we check the Pod status to make sure it is running. Then we can install the actual Ceph Cluster in one of the two ways. We can declare a CephClusterCRD ourself, or we can use Helm again to declare the CRD. Helm Chart gives us a lot of useful default values and saves us from editing a large body of YAML manifest.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Install Ceph CR on AKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Helm to install CephCluster CRD. We create a value file (e.g. rook-ceph-cluster-values.yaml) with content as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;https&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&lt;span style="color:#75715e"&gt;//github.com/rook/rook/blob/master/Documentation/Helm-Charts/ceph-cluster-chart.md&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;operatorNamespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;toolbox&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;cephObjectStores&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; [] &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephObjectStore&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;Setting&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;null&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;disables&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;it&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cephBlockPools&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephBlockPool&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;also&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cephFileSystems&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;a&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;cephFileSystem&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;also&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;created&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;with&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;cephClusterSpec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;mon&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;count&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeClaimTemplate&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;managed&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;premium&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;limits&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;1Gi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;100m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500Mi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;dashboard&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;enabled&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassDeviceSets&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;The&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;number&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;create&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;from&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;device&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;count&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IMPORTANT&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;If&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;volumes&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;specified&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;by&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;are&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;not&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;portable&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;across&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;nodes&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;needs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;set&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;For&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;, &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;using&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;local&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;provisioner&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;should&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;portable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Since&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;could&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;end&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;up&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;any&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;an&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;effort&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;needs&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;be&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;made&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSDs&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;across&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;nodes&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;much&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;possible&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;Unfortunately&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;the&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pod&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;anti&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;affinity&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;breaks&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;down&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;soon&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;have&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;more&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;than&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;one&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;OSD&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;per&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;. &lt;span style="color:#a6e22e"&gt;The&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;topology&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;constraints&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;will&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;give&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;us&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;an&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;even&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;spread&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;on&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;K8s&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1.18&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;or&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;newer&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;placement&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxSkew&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologyKey&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hostname&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ScheduleAnyway&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labelSelector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;preparePlacement&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;tolerations&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Exists&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;nodeAffinity&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;nodeSelectorTerms&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;agentpool&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;storagenp&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologySpreadConstraints&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;maxSkew&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;IMPORTANT&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;If&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;you&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;don&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;t&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;have&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;zone&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;, &lt;span style="color:#a6e22e"&gt;change&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;this&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;to&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;another&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;such&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;as&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;hostname&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;topologyKey&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;topology&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;kubernetes&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;zone&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;whenUnsatisfiable&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;DoNotSchedule&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labelSelector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchExpressions&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;key&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;operator&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;In&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;values&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;rook&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;ceph&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;osd&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;prepare&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;limits&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;4Gi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;500m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;2Gi&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeClaimTemplates&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;data&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storage&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;Gi&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;storageClassName&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;managed&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;premium&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;volumeMode&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Block&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;accessModes&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReadWriteOnce&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;During the cluster provisioning, there will be a number of preparing Pods. We want those Pods to run on nodes with label agentpool=storagenp. In real life, we need to orchestrate where to run each workload, by restricting the nodes to schedule certain types of workload.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can install the cluster using Helm:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install rook-ceph-cluster rook-ceph-cluster --namespace rook-ceph --create-namespace --version v1.9.6 --repo https://charts.rook.io/release/ --values rook-ceph-cluster-values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;After running the Helm install, it may take as long as 15 minutes for all resources to settle. Watch the Pod status in rook-ceph namespace. At the end, make sure that the cluster is created successfully:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get CephCluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME DATADIRHOSTPATH MONCOUNT AGE PHASE MESSAGE HEALTH EXTERNAL&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;rook-ceph /var/lib/rook &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; 15m Ready Cluster created successfully HEALTH_OK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get cephBlockPools&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME PHASE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ceph-blockpool Ready&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadmin@pro-sturgeon-bastion-host:~$ kubectl -n rook-ceph get cephFileSystems&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME ACTIVEMDS AGE PHASE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ceph-filesystem &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 20m Ready&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In my case it took 15 minutes before the cluster comes up as created successfully. You should notice that two storage classes were also created as a part of the install. It however did not create a storage class or CRD for object storage, because we explicitly disabled it in the Helm value file by setting cephObjectStores value to null.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Dashboard&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We enabled dashboard. To configure the dashboard view properly, we would need an ingress. For a quick view here, we can play port forwarding tricks. First we fetch the admin password for use in the next step. Then expose the dashboard to the bastion host:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n rook-ceph get secret rook-ceph-dashboard-password -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.data.password}&amp;#39;&lt;/span&gt; | base64 -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n rook-ceph port-forward svc/rook-ceph-mgr-dashboard 8443:8443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Since I don&amp;#8217;t have UI on the bastion host, I use the port forwarding trick again from my own MacBook. Start a new terminal and SSH to the bastion host with port-forwarding switch:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ ssh -L 8443:localhost:8443 kubeadmin@20.116.132.8&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above suppose the public IP of the bastion host is 20.116.132.8. Then from my MacBook I can browse to localhost:8443 (with Safari browser which gives me the option to bypass certificate error). At the web portal, provide username (admin) and password (as retrieved above):&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="1795" height="1026" src="https://static.digihunch.com/wp-content/uploads/2022/06/image-16.png" alt="" class="wp-image-6037"/&gt;&lt;figcaption class="wp-element-caption"&gt;Ceph console for Kubernetes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apart from the dashboard, we can also use &lt;a href="https://docs.ceph.com/en/quincy/man/8/ceph/"&gt;ceph admin tool&lt;/a&gt; from a &lt;a href="https://github.com/rook/rook/blob/master/deploy/examples/toolbox.yaml"&gt;toolbox&lt;/a&gt; pod, following &lt;a href="https://rook.io/docs/rook/v1.9/ceph-toolbox.html"&gt;this&lt;/a&gt; instruction. For monitoring, Ceph by Rook can expose metrics for &lt;a href="https://www.rook.io/docs/rook/v1.9/Storage-Configuration/Monitoring/ceph-monitoring/"&gt;Prometheus&lt;/a&gt; to scrape.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Performance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With default ceph configuration on AKS, I ran quick performance test using kube-str . The result is as follows:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-very-light-gray-to-cyan-bluish-gray-gradient-background has-background"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;read_iops&lt;/td&gt;&lt;td&gt;write_iops&lt;/td&gt;&lt;td&gt;read_bw&lt;/td&gt;&lt;td&gt;write_bw&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ceph-block&lt;/td&gt;&lt;td&gt;IOPS=464.507294 BW(KiB/s)=1874&lt;/td&gt;&lt;td&gt;IOPS=243.296143 BW(KiB/s)=989&lt;/td&gt;&lt;td&gt;IOPS=509.928162 BW(KiB/s)=65797&lt;/td&gt;&lt;td&gt;IOPS=248.530762 BW(KiB/s)=32338&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ceph-filesystem&lt;/td&gt;&lt;td&gt;IOPS=438.701324 BW(KiB/s)=1770&lt;/td&gt;&lt;td&gt;IOPS=226.270660 BW(KiB/s)=920&lt;/td&gt;&lt;td&gt;IOPS=405.936340 BW(KiB/s)=52456&lt;/td&gt;&lt;td&gt;IOPS=208.869293 BW(KiB/s)=27229&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The metrics reflects performance under default configuration. It should not be considered as the best performance that Ceph can deliver on Azure Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I discussed three storage options for Azure Kubernetes but the idea applies to other Kubernetes platform hosted on a CSP. The &lt;a href="https://static.digihunch.com/2022/07/kubernetes-storage-on-azure-1-of-3-built-in-storage-and-nfs/"&gt;native storage&lt;/a&gt; has significant limitation. NFS has latency. Block storage does not address high availability at the storage layer. Portworx and LINSTOR fill that gap as a commercial solution. Ceph is based on Object storage.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/08/kubernetes-storage-on-azure-2-of-3-portworx/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Storage on Azure 2 of 3 – Portworx&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/09/minio-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;MinIO for S3-compatible Object Storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Etcd – the key-value store for Kubernetes</title><link>https://static.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/</link><pubDate>Tue, 14 Jun 2022 00:10:00 -0400</pubDate><guid>https://static.digihunch.com/2022/06/etcd-the-key-value-store-for-kubernetes/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-etcd.webp" alt="Featured image of post Etcd – the key-value store for Kubernetes" /&gt;&lt;h2 class="wp-block-heading"&gt;Etcd in Kubernetes&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes &lt;a href="https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/"&gt;architecture&lt;/a&gt;, &lt;a href="https://etcd.io/"&gt;etcd&lt;/a&gt; is the data store. It stores the desired state of Kubernetes object. API server is the only client that connects to etcd (via &lt;a href="https://grpc.io/"&gt;gRPC&lt;/a&gt; protocol). Cluster builder specifies the endpoint of etcd as a parameter to the kube-api-server process. Other Kubernetes components, whether in the control plane or from the nodes, connect to API server. API server translates their request into etcd query, and then translates etcd query result into what its clients ask for. For this reason, communication with etcd accounts for a lot of network traffic in a Kubernetes cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The etcd store is a CNCF project for &amp;#8220;a distributed, reliable key-value store for critical data in a distributed system&amp;#8221;, developed by CoreOS team. So it is essentially a distributed key-value store for any distributed application. If an application runs on Kubernetes, it can leverage etcd store, by keeping their configurations in ConfigMap and Secret objects. One key feature is to watch for specific keys or directories for changes, and react to the changes. Voila! This is the underlying mechanism for &lt;a href="https://kubernetes.io/docs/concepts/architecture/controller/"&gt;controller&lt;/a&gt;!&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Kubernetes cluster may have stacked etcd deployment or connect to an external etcd store.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="848" height="560" src="https://static.digihunch.com/wp-content/uploads/2022/05/stacked.png" alt="" class="wp-image-5250"/&gt;&lt;figcaption class="wp-element-caption"&gt;stacked etcd architecture&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="856" height="601" src="https://static.digihunch.com/wp-content/uploads/2022/05/external.png" alt="" class="wp-image-5251"/&gt;&lt;figcaption class="wp-element-caption"&gt;external etcd architecture&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In managed Kubernetes services such as EKS in AWS and AKS in Azure, users usually do not directly access etcd store. However, it is still a very important component to understand. Its use case includes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Configuration sharing&lt;/li&gt;&#10;&lt;li&gt;Service discovery&lt;/li&gt;&#10;&lt;li&gt;Consistency&lt;/li&gt;&#10;&lt;li&gt;Watching mechanism&lt;/li&gt;&#10;&lt;li&gt;Expiry and extension of key &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The consistency use case is based on Raft protocol for distributed consensus.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Raft protocol&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I am not an expert in distributed consensus protocols and nor do I intent to cover it in depth. At a high level, I have heard of three of them so far:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Etcd uses Raft protocol&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://static.digihunch.com/2020/08/zookeeper/"&gt;Zookeeper&lt;/a&gt; uses ZAB protocol&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://static.digihunch.com/2018/03/cassandra-architecture-summary/"&gt;Cassandra&lt;/a&gt; uses paxos protocol&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.alibabacloud.com/blog/a-brief-analysis-of-consensus-protocol-from-logical-clock-to-raft_594675"&gt;Here&lt;/a&gt; is a good intro to the three protocols. Instead of getting into the fine details, I would like to discuss why we need such a consensus protocol (or consensus mechanism) in distributed systems, which are also decentralized systems.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="686" src="https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-1024x686.webp" alt="" class="wp-image-13111" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-1024x686.webp 1024w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-300x201.webp 300w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-768x514.webp 768w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology-410x275.webp 410w, https://static.digihunch.com/wp-content/uploads/2025/04/etcd-topology.webp 1138w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Centralized, Decentralized, Distributed systems&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The reason a distributed system needs consensus protocol, is that a distributed system lacks a single source of truth as centralized systems do. Different parts of the distributed system may receive different signals but they must come to agreement of a single plan to act. Lamport studies this with an analogy of &lt;a href="https://en.wikipedia.org/wiki/Byzantine_fault"&gt;Byzantine Generals&lt;/a&gt; problem, and first proposed Paxos protocol. &lt;a href="https://en.wikipedia.org/wiki/Paxos_(computer_science)"&gt;Paxos&lt;/a&gt; has been an important foundation to modern distributed systems. In Paxos, consensus is achieved in &lt;a href="https://martinfowler.com/articles/patterns-of-distributed-systems/paxos.html"&gt;two phases&lt;/a&gt;, which creates the problem of livelocks. Raft is an alternative to Paxos, and is widely adopted today. &lt;a href="http://thesecretlivesofdata.com/raft/"&gt;Here&lt;/a&gt; is a link to an animated illustration for Raft protocol. The Raft protocol is also used in Redis. It has three roles: Leader, Candidate, and follower. ZAB protocol is similar to Raft, where it needs to select a leader.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Etcd Lab&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In troubleshooting, if we suspect that the response from API server is inconsistent with etcd store, we want to directly connect to it.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Managed Kubernetes services do not expose their etcd store. We can use KinD or Minikube. There are two types of jump box to access etcd store: using etcd Pod, or SSH to a Node. To connect to etcd, we also need the X509 key, certificate and CA&amp;#8217;s certificate, in addition to the endpoint, usually an IP with port 2389. When I connect to Pod shell, I find the command shell not easy to use. They might miss basic command such as ls, or do not support auto completion.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take KinD for example, we first create a secret, then we can connect to the node with docker CLI command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create ns myns&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n myns create secret generic mysecret --from-literal key1&lt;span style="color:#f92672"&gt;=&lt;/span&gt;value1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n myns get secret mysecret -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.data.key1}&amp;#39;&lt;/span&gt; | base64 -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;docker exec -it control /bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From the node, &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apt update &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; apt install etcd-client&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nc -vz localhost &lt;span style="color:#ae81ff"&gt;2379&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat /etc/kubernetes/manifests/kube-apiserver.yaml | grep etcd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_API&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_CERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/pki/apiserver-etcd-client.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/pki/apiserver-etcd-client.key&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_CACERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/etc/kubernetes/pki/etcd/ca.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;export ETCDCTL_ENDPOINTS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;https://127.0.0.1:2379&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl member list write out&lt;span style="color:#f92672"&gt;=&lt;/span&gt;table&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can see the secret object directly with etcd store:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get /registry/secrets/myns/mysecret&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With get query, when using &amp;#8211;prefix, we can use &amp;#8211;keys-only switch to list keys without values:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get --prefix /registry/api --keys-only&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get --prefix /registry/namespace -wjson&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can write key-value with put command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl put myloc &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get myloc -wjson&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, all the key names start with / which makes the key looks like a POSIX path. Every Kubernetes object is stored in etcd with a unique key following a self-explanatory naming pattern. To display the path, we can also use debug log that records the call to API server:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get ns myns -v9&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Look for curl command such as:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;I0523 22:51:43.517728 32347 round_trippers.go:466] curl -v -XGET -H &amp;#34;Accept: application/json;as=Table;v=v1;g=meta.k8s.io,application/json;as=Table;v=v1beta1;g=meta.k8s.io,application/json&amp;#34; -H &amp;#34;User-Agent: kubectl/v1.23.6 (darwin/amd64) kubernetes/ad33385&amp;#34; &amp;#39;https://127.0.0.1:64081/api/v1/namespaces/myns&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From there we can see the etcd query as the URI is namespaces/myns, which we use in etcdctl query path:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl get /registry/namespaces/myns&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Every type of Kubernetes object has a storage.go file in their implementation that defines how api server should write object. &lt;a href="https://github.com/kubernetes/kubernetes/blob/master/pkg/registry/core/pod/storage/storage.go"&gt;Here&lt;/a&gt; is an example for Pod object.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Etcd also supports watch command to watch for changes. For example:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl watch --prefix /registry/namespace &lt;span style="color:#75715e"&gt;# watch output k create ns newns&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we create a namespace with kubectl:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create ns myns&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The output from etcdctl will reflect the change. The communication between etcdctl and etcd is gRPC protocol. The output is based on stream, as we can see from the watch result.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-etcd-maintenance"&gt;Etcd Maintenance&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Like any distributed store, etcd needs &lt;a href="https://etcd.io/docs/v3.5/op-guide/maintenance/"&gt;maintenance&lt;/a&gt; and operation work. For example, we can check endpoint status with endpoint command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl endpoint status&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can also backup and restore etcd store with etcdctl command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;etcdctl snapshot save /tmp/backup.db&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This was an question in &lt;a href="https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/"&gt;CKA exam&lt;/a&gt;. In real life, when the workload scales up, the etcd store may come across many pitfalls, such as degraded performance, unresponsiveness, some etcd member going down, network partition on etcd store causing split brain. It is important to ensure efficient communication between API server and etcd store. The etcdctl provides defrag and compact commands for common maintenance activities.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/05/hosting-database-on-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Hosting database on Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/06/chaos-mesh-cloud-native-chaos-engineering/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Chaos Mesh – Cloud Native Chaos Engineering&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Logging and Monitoring in Kubernetes with PLG stack</title><link>https://static.digihunch.com/2021/10/logging-and-monitoring-in-kubernetes-with-plg-stack/</link><pubDate>Wed, 13 Oct 2021 21:29:00 -0400</pubDate><guid>https://static.digihunch.com/2021/10/logging-and-monitoring-in-kubernetes-with-plg-stack/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-plg.webp" alt="Featured image of post Logging and Monitoring in Kubernetes with PLG stack" /&gt;&lt;p class="wp-block-paragraph"&gt;We&amp;#8217;ve checked out the the actors in PLG stack (Promtail, Loki, Node Exporter, Prometheus, Grafana) and whipped up a quick pipeline on MacOS. Now I&amp;#8217;m going a little further to implement the same PLG stack (Prometheus Loki and Grafana) in a Kubernetes cluster. This setup is for demo only, therefore no persistent storage is enabled.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-test-workload"&gt;Test Workload&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I host a deployment of Flog with three pods running on Minikube. Flog is an open-source emulating log generation behaviour of an application. On the Minikube cluster we start the deployment as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube start --driver&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hyperkit --container-runtime&lt;span style="color:#f92672"&gt;=&lt;/span&gt;containerd --memory&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12288&lt;/span&gt; --cpus&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;kubectl create ns obsv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv create deployment flog --image&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mingrammer/flog --replicas&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; -- flog -f rfc3164 -l -d 300ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv get po&#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 will come up in a heartbeat. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I will use &lt;a href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;helm&lt;/a&gt; to install the objects required for logging and metrics pipelines. There are multiple Helm charts for each components. Some high-level charts (usually with a name suffix of -stack) contain several other resource as sub-charts. They are created as one-stop-shop for multiple components but I found none of them serve my exact purpose. For example, both &lt;a href="https://artifacthub.io/packages/helm/grafana/loki-stack"&gt;loki-stack&lt;/a&gt; and &lt;a href="https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack"&gt;kube-prometheus-stack&lt;/a&gt; include Grafana. But I only need one instance of Grafana. Therefore I stick to the low-level charts.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt; &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-log-shipping"&gt;Log Shipping&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Add a helm repo, and install loki and promtail. Note that we need to specify correct loki address when installing Promtail. &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 repo add grafana https://grafana.github.io/helm-charts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo update&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade --namespace obsv --install loki grafana/loki&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade --namespace obsv --install promtail grafana/promtail --set &lt;span style="color:#e6db74"&gt;&amp;#34;config.lokiAddress=http://loki.obsv.svc.cluster.local:3100/loki/api/v1/push&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When installing Promtail, a DaemonSet is created on the Node. The default configuration applies appropriate configuration and tagging strategy for Kubernetes Pod and Node. So the only customization I specified is Loki address. We can then check logging with Loki. To do so, first expose port 3100 to host, and then use logcli (e.g. on MacOS) to query for logs:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv port-forward service/loki 3100:3100&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli labels&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli labels pod&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli query &lt;span style="color:#e6db74"&gt;&amp;#39;{pod=&amp;#34;flog-775d5fc5c8-p4rlx&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Log lines should be pumped to Loki a minute after Loki comes up. The logcli labels command should display the tags. The logcli query command should return the log lines. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-metrics"&gt;Metrics&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use Premetheus with node exporter. In its &lt;a href="https://prometheus.io/docs/introduction/overview/#architecture"&gt;architecture&lt;/a&gt;, Prometheus contain the server, the pushgateway, and alertmanager. The &lt;a href="https://github.com/prometheus-community/helm-charts"&gt;helm chart&lt;/a&gt; for &lt;a href="https://artifacthub.io/packages/helm/prometheus-community/prometheus"&gt;Prometheus&lt;/a&gt; contains all of those components. It also has a dependency repo for &lt;a href="https://artifacthub.io/packages/helm/prometheus-community/kube-state-metrics"&gt;kube-state-metrics&lt;/a&gt;. To install:&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 repo add prometheus-community https://prometheus-community.github.io/helm-charts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add kube-state-metrics https://kubernetes.github.io/kube-state-metrics&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm --namespace&lt;span style="color:#f92672"&gt;=&lt;/span&gt;obsv install prometheus prometheus-community/prometheus&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv get svc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once installed, a DaemonSet for Prometheus Node Exporter is created. The exporter is already configured by default for Kubernetes monitoring. The Prometheus server is also configured, on port 80 by default. it needs to be forwarded in order to access from Browser:&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 --namespace obsv port-forward service/prometheus-server 9100:80&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To verify installation of Prometheus, browse to localhost:9100 to examine the metrics.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-visualization"&gt;Visualization&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Last but not least, I will configure Grafana. The repo has been added already so we&amp;#8217;ll just install the 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;helm upgrade --namespace obsv --install grafana grafana/grafana&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get secret --namespace obsv grafana -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;{.data.admin-password}&amp;#34;&lt;/span&gt; | base64 --decode ; echo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The second command retrieves the credential. To access the web portal, we need port forwarding again:&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 port-forward --namespace obsv service/grafana 3000:80&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To verify installation, browse to http://localhost:3000 and log in as user admin with the password above. Then add two data sources:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Type: Prometheus, URL: http://prometheus-server.obsv.svc.cluster.local:80&lt;/li&gt;&#10;&lt;li&gt;Type: Loki, URL: http://loki.obsv.svc.cluster.local:3100&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can explore data using both data sources.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://d33wubrfki0l68.cloudfront.net/0862f7967545b9ebe1041764e9427a8bf0f44a08/6b8ba/assets/img/uploads/2020/04/image2.png" alt="grafana workflow"/&gt;&lt;figcaption class="wp-element-caption"&gt;PLG stack&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the last two posts I reviewed the setups for PLG stack in Kubernetes, from a regular environment to k8s cluster. Fluentd, Prometheus are both CNCF projects. The PLG stack seems to be more adopted than EFK but both have their own advantages. Welcome to the PLG vs EFK debate.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/10/intro-to-plg-stack-prometheus-loki-and-grafana/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to PLG stack -Prometheus, Loki and Grafana&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/10/notes-on-azure/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Azure Deets&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Certified Kubernetes Administrator (CKA) Exam</title><link>https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/</link><pubDate>Fri, 30 Apr 2021 09:50:00 -0400</pubDate><guid>https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/</guid><description>&lt;p class="wp-block-paragraph"&gt;The Certified Kubernetes Administrator (CKA) exam is a hands-on session where you need to follow the instructions to configure the system in a bash terminal on the web browser. In my experience, some shortcut keys (such as Alt+F) do not work, which slows me down a little bit. For each question, you need to switch kubectl context as instructed in the question. Some questions share the same context so it is very easy to omit this step. You can verify response with your own command but will not be told whether you scored in each question. During the CKA exam I tried to spin up a terminal session from within &lt;a href="https://static.digihunch.com/2019/10/personal-vim-cheatsheet/"&gt;Vim&lt;/a&gt; editor and the terminal ran out of buffer. I had to reboot the machine with the help of proctor, and my completed work are saved.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1405" height="121" src="https://static.digihunch.com/wp-content/uploads/2021/05/image.png" alt="" class="wp-image-2266"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In general this is an exam I enjoy preparing and writing because it is very hands on. Result is out a day after, and I passed at 96%. I heard about tight timelines but I managed to finish 15 minutes before the end, most likely owing to my dexterity with Linux commands. With that I&amp;#8217;m happy to share my notes in preparation for the CKA exam.&lt;/p&gt;&#10;&lt;p class="has-white-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="590px" viewBox="-0.5 -0.5 590 638" style="max-width:100%;max-height:638px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="347" width="170" height="290" rx="25.5" ry="25.5" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 344px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; background-color: #ffffff; white-space: normal; word-wrap: normal; "&gt;Worker Node&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="344" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="377" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 392px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="396" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="427" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 442px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kubelet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="446" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubelet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="477" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 492px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container runtime&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="496" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container runtime&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 85 457 L 85 477 L 85 457 L 85 470.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 85 475.88 L 81.5 468.88 L 85 470.63 L 88.5 468.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="10" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="190" y="347" width="170" height="290" rx="25.5" ry="25.5" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 344px; margin-left: 191px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; background-color: #ffffff; white-space: normal; word-wrap: normal; "&gt;Worker Node&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="344" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="377" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 392px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="396" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="427" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 442px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kubelet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="446" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubelet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="477" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 492px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container runtime&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="496" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container runtime&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 275 457 L 275 477 L 275 457 L 275 470.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 275 475.88 L 271.5 468.88 L 275 470.63 L 278.5 468.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="200" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="235" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 281px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="235" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 281px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="235" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 281px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="380" y="347" width="170" height="290" rx="25.5" ry="25.5" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 344px; margin-left: 381px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; background-color: #ffffff; white-space: normal; word-wrap: normal; "&gt;Worker Node&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="344" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="377" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 392px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="396" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="427" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 442px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kubelet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="446" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubelet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="477" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 492px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container runtime&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="496" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container runtime&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 465 457 L 465 477 L 465 457 L 465 470.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 465 475.88 L 461.5 468.88 L 465 470.63 L 468.5 468.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="390" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="425" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="505" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="425" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="505" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="425" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="505" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 546.37 392 L 570 392 Q 580 392 580 382 L 580 197 Q 580 187 570 187 L 431.37 187" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 541.12 392 L 548.12 388.5 L 546.37 392 L 548.12 395.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 426.12 187 L 433.12 183.5 L 431.37 187 L 433.12 190.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="10" y="17" width="460" height="250" rx="37.5" ry="37.5" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 458px; height: 1px; padding-top: 14px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; white-space: normal; word-wrap: normal; "&gt;Control&amp;nbsp; Plane&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="240" y="14" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Control&amp;nbsp; Plane&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="37" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 44px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="56" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="107" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 114px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-scheduler&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="126" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-scheduler&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="40" y="47" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 54px; margin-left: 41px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="66" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="57" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 64px; margin-left: 51px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="76" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="40" y="117" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 124px; margin-left: 41px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-scheduler&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="136" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-scheduler&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="127" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 134px; margin-left: 51px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-scheduler&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="146" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-scheduler&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="187" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 194px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;cloud-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="206" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;cloud-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="40" y="197" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 204px; margin-left: 41px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;cloud-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="216" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;cloud-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="207" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 214px; margin-left: 51px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;cloud-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="226" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;cloud-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="305" y="167" width="120" height="40" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 174px; margin-left: 306px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-api-server&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="365" y="186" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-api-server&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 335 55 C 335 44.33 395 44.33 395 55 L 395 99 C 395 109.67 335 109.67 335 99 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 335 55 C 335 63 395 63 395 55 M 335 59 C 335 67 395 67 395 59 M 335 63 C 335 71 395 71 395 63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 74px; margin-left: 336px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;etcd&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="365" y="86" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;etcd&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 254.25 76.75 L 330.75 162.25" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 250.75 72.83 L 258.02 75.72 L 254.25 76.75 L 252.8 80.38 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 334.25 166.17 L 326.98 163.28 L 330.75 162.25 L 332.2 158.62 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 255.8 219.36 L 299.2 199.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 251.02 221.54 L 255.94 215.45 L 255.8 219.36 L 258.84 221.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 303.98 197.46 L 299.06 203.55 L 299.2 199.64 L 296.16 197.17 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 255.37 145.42 L 299.63 173.58" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 250.94 142.6 L 258.73 143.41 L 255.37 145.42 L 254.97 149.31 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 304.06 176.4 L 296.27 175.59 L 299.63 173.58 L 300.03 169.69 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 365 113.37 L 365 160.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 365 108.12 L 368.5 115.12 L 365 113.37 L 361.5 115.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 365 165.88 L 361.5 158.88 L 365 160.63 L 368.5 158.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 546.37 442 L 570 442 Q 580 442 580 432 L 580 197 Q 580 187 570 187 L 431.37 187" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 541.12 442 L 548.12 438.5 L 546.37 442 L 548.12 445.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 426.12 187 L 433.12 183.5 L 431.37 187 L 433.12 190.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For taking CKA exam, we should be familiar with the diagram above.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-tips-for-troubleshooting"&gt;Tips for Troubleshooting&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The CKA exam is hands-on and therefore requires quite a bit of troubleshooting. Here are my notes.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Check Node status to start with&lt;/li&gt;&#10;&lt;li&gt;Check core services on each node:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;sudo systemctl status kubelet&lt;/li&gt;&#10;&lt;li&gt;sudo systemctl status docker&lt;/li&gt;&#10;&lt;li&gt;sudo journalctl -u kubelet&lt;/li&gt;&#10;&lt;li&gt;sudo journalctl -u docker&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Check component logs (on hosting VM)&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;/var/log/kube-apiserver.log&lt;/li&gt;&#10;&lt;li&gt;/var/log/kube-scheduler.log&lt;/li&gt;&#10;&lt;li&gt;/var/log/kube-controller-manager.log&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;If cluster is built by kubeadm, then some of those services are running in Pods within kube-system namespace. Check those pods:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;run interactive shell: &amp;gt; kubectl exec podname &amp;#8211;stdin &amp;#8211;tty &amp;#8212; /bin/sh&lt;/li&gt;&#10;&lt;li&gt;there is an image for lots of useful network tool called nicolaka/netshoot&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Store pod names to variable. e.g. &amp;gt; POD_NAME=$(kubectl get pods -l run=nginx -o jsonpath=&amp;#8221;{.items[0].metadata.name}&amp;#8221;)&lt;/li&gt;&#10;&lt;li&gt;With kubectl, you may&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;alias it to k for faster typing&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;dry-run: to run imperative command without creating object&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;record: record the command that was used to make a change&lt;/li&gt;&#10;&lt;li&gt;-o: set output format, wide, yaml, or jsonpath=&amp;#8221;expression&amp;#8221;. For example, to get pod name: &amp;gt; kubectl get pods -l run=nginx -o jsonpath=&amp;#8221;{.items[0].metadata.name}&amp;#8221;&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;sort-by: use JSONPath expression&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;selector: filter results &lt;strong&gt;by label&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-build-k8s-cluster-using-kubeadm"&gt;Build K8s cluster using kubeadm&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The CKA exam requires you to know how to build cluster with kubeadm. This involves installing four components (docker-ce, kubeadm, kubectl and kubelet), as outlined below:&lt;/p&gt;&#10;&lt;table id="tablepress-12" class="tablepress tablepress-id-12 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;th class="column-1"&gt;step&lt;/th&gt;&lt;th colspan="2" class="column-2"&gt;command&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 class="column-1"&gt;1. Install docker-ce&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;br /&gt;&#10;&gt; sudo add-apt-repository \&lt;br /&gt;&#10; "deb [arch=amd64] https://download.docker.com/linux/ubuntu \&lt;br /&gt;&#10; $(lsb_release -cs) \&lt;br /&gt;&#10; stable"&lt;br /&gt;&#10;&gt; sudo apt-get update&lt;br /&gt;&#10;&gt; sudo apt-get install -y docker-ce=18.06.1~ce~3-0~ubuntu&lt;br /&gt;&#10;&gt; sudo apt-mark hold docker-ce&lt;br /&gt;&#10;&gt; sudo systemctl status docker&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-1"&gt;2. Install kubeadm, kubelet and kubectl&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -&lt;br /&gt;&#10;cat &lt;&lt; EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list&lt;br /&gt;&#10;deb https://apt.kubernetes.io/ kubernetes-xenial main&lt;br /&gt;&#10;EOF&lt;br /&gt;&#10;&gt; sudo apt-get update&lt;br /&gt;&#10;&gt; sudo apt-get install -y kubelet kubeadm kubectl&lt;br /&gt;&#10;&gt; sudo apt-mark hold kubelet kubeadm kubectl&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-1"&gt;3. Form a K8s cluster&lt;/td&gt;&lt;td class="column-2"&gt;On master node:&lt;br /&gt;&#10;&gt; sudo kubeadm init --pod-network-cidr=10.244.0.0/16&lt;br /&gt;&#10;This command prints out a command for worker nodes to join.&lt;br /&gt;&#10;&lt;/td&gt;&lt;td class="column-3"&gt;On worker node:&lt;br /&gt;&#10;sudo the command generated on master&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-1"&gt;4. Configure kubectl&lt;/td&gt;&lt;td class="column-2"&gt;On master node:&lt;br /&gt;&#10;&gt; mkdir -p $HOME/.kube&lt;br /&gt;&#10;&gt; sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config&lt;br /&gt;&#10;&gt; sudo chown $(id -u):$(id -g) $HOME/.kube/config&lt;br /&gt;&#10;&lt;/td&gt;&lt;td class="column-3"&gt;Optionally on worker node:&lt;br /&gt;&#10;&gt; mkdir -p $HOME/.kube&lt;br /&gt;&#10;then scp $HOME/.kube/config from control plane node&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-1"&gt;5. Set up cluster networking&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf&lt;br /&gt;&#10;&gt; sudo sysctl -p&lt;br /&gt;&#10;Then from any environment with kubectl, bring up the system pods for cluster networking&lt;br /&gt;&#10;&gt; kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-12 from cache --&gt;&#10;&lt;h3 class="wp-block-heading" id="h-add-new-node-to-kubeadm-cluster"&gt;Add new node to KubeAdm cluster&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is fairly simple with the help of kubeadm. The node to join cluster must be able to communicate with master node. Create a token and print join command from master node:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; kubeadm token create --print-join-command&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then from the node to join, run this command &lt;strong&gt;as sudo&lt;/strong&gt;. You will see that it performs the TLS bootstrap for you. Once completed, the standard output will say this node has joined the cluster. You can confirm with command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; kubectl get nodes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Sometimes one needs to migrate pods to the newly joined node. This can be done by draining the existing nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note you can also use &lt;a href="https://github.com/kubernetes-sigs/kubespray" class="rank-math-link"&gt;kubespray &lt;/a&gt;to build K8s cluster as &lt;a href="https://static.digihunch.com/2020/12/instance-initialization-with-aws-cdk-in-python/" class="rank-math-link"&gt;previously &lt;/a&gt;discussed, and &lt;a href="https://github.com/digihunch/kubelab" class="rank-math-link"&gt;here &lt;/a&gt;is a my IaC project to launch AWS instances and build a K8s cluster with kubespray on top of it. For my learning, I often create a GKE (Google Kubernetes Engine) cluster from GCP&amp;#8217;s cloudshell. There is a &lt;a href="https://cloud.google.com/kubernetes-engine/docs/quickstart" class="rank-math-link"&gt;guide&lt;/a&gt; on how to start a cluster but it comes down to three 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;$ gcloud config set compute/zone us-east1-b&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ gcloud container clusters create tcluster --num-nodes&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;$ gcloud container clusters get-credentials tcluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The third command above is to configure kubectl on the cloudshell. Follow &lt;a href="https://cloud.google.com/anthos/clusters/docs/on-prem/1.5/how-to/ssh-cluster-node" class="rank-math-link"&gt;this&lt;/a&gt; guide if you need to SSH to node.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-upgrade-kubeadm-cluster"&gt;Upgrade KubeAdm cluster&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/"&gt;This&lt;/a&gt; involves upgrade three components (kubeadm, kubectl and kubelet) on two types of node: master node and worker node. They steps vary slightly for two nodes. But drain and uncordon is needed for both types of nodes. Pick a node and follow the steps below:&lt;/p&gt;&#10;&lt;table id="tablepress-13" class="tablepress tablepress-id-13 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;th class="column-1"&gt;Step&lt;/th&gt;&lt;th colspan="2" class="column-2"&gt;Command&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 class="column-1"&gt;1. drain the node from kubectl client (e.g. master node)&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; sudo kubectl drain nodename --ignore-daemonsets&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-1"&gt;2. Determine kubeadm target version&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; apt-mark showhold&lt;br /&gt;&#10;&gt; sudo apt-mark unhold kubeadm kubectl kubelet&lt;br /&gt;&#10;&gt; apt list --installed | grep kube&lt;br /&gt;&#10;&gt; apt-cache show kubeadm | less&lt;br /&gt;&#10;&gt; sudo apt-get install -y kubeadm=1.20.2-00&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-1"&gt;3. update kubeadm&lt;/td&gt;&lt;td class="column-2"&gt;On master node:&lt;br /&gt;&#10;&gt; sudo kubeadm upgrade plan v1.20.2&lt;br /&gt;&#10;&gt; sudo kubeadm upgrade apply v1.20.2&lt;br /&gt;&#10;&lt;/td&gt;&lt;td class="column-3"&gt;On worker node:&lt;br /&gt;&#10;&gt; sudo kubeadm upgrade node&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-1"&gt;4. On the node to update, determine target version for kubectl and kubelet, then install&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; apt-cache show kubectl | less&lt;br /&gt;&#10;&gt; apt-cache show kubelet | less&lt;br /&gt;&#10;&gt; sudo apt-get install -y kubectl=1.20.2-00 kubelet=1.20.2-00&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-1"&gt;5. Restart kubelet&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; sudo systemctl daemon-reload&lt;br /&gt;&#10;&gt; sudo systemctl restart kubelet&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-7"&gt;&#10;&#9;&lt;td class="column-1"&gt;6. Uncordon&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; kubectl uncordon nodename&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-13 from cache --&gt;&#10;&lt;h3 class="wp-block-heading" id="h-backup-and-restore-etcd"&gt;Backup and restore Etcd&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://etcd.io/"&gt;Etcd&lt;/a&gt; is a distributed key-value store. It uses Raft protocol for distributed consensus. Etcd is the third distributed system I touch on. The previous two are: Cassandra (using Paxos protocol for distributed consensus) and ZooKeeper (using ZAB protocol). &lt;a href="https://www.alibabacloud.com/blog/a-brief-analysis-of-consensus-protocol-from-logical-clock-to-raft_594675"&gt;Here&lt;/a&gt; is a good article that summarizes the protocols. As for the exam we only need to use etcd with the client tool.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The etcd itself can run on a cluster of servers, each running etcd as a systemd service as etcd/etcd (user/group). It can be deployed in two ways: &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;stacked etcd: an instance of etcd lives with kube-api-server on the same control plane node&lt;/li&gt;&#10;&lt;li&gt;external etcd: in a dedicated cluster of etcd&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Alternatively, etcd can run as a pod, most likely in kube-system namespace. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The etcd service listens on port 2379 for client communication and on port 2380 for server (peer-to-peer) communication. When the systemd service was initialized there are a few key environment variables (e.g. cert locations, ETCD_DATA_DIR) privoded as configuration. To see them, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; cat /etc/systemd/system/etcd.service | grep Env&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;These environment variables (prefixed with ETCD_) are for the service only. They can provide current configuration information for us to use later. &amp;nbsp;When it’s running as a pod, check out the directory for static pod for the yaml declaration (e.g. /etc/Kubernetes/manifests/etcd.yaml), where these parameters are passed in as environment variable.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/"&gt;etcdctl&lt;/a&gt; utility is a command line client for etcd. The default API version is 3 so no need any more to set ETCDCTL_API=3 before each command. The utility needs three arguments three arguments (&amp;#8211;cacert, &amp;#8211;cert, and &amp;#8211;key) but we can pass the information via environment variables:&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;&amp;gt; export ETCDCTL_CACERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt; /home/cloud_user/etcd-certs/etcd-ca.pem&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_CERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt; /home/cloud_user/etcd-certs/etcd-server.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt; /home/cloud_user/etcd-certs/etcd-server.key&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_ENDPOINTS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://etcd1:2379&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The environment variable names are uppercase of the argument name with prefix ETCDCTL_. Only global options of arguments can be supplied via environment variables. They remain effective throughout the rest of activities. Also note that the CACERT is needed only when client-cert-auth is true. Now to backup, we can simply run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; etcdctl snapshot save /home/cloud_user/etcd_backup.db&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To restore from a file, you want to remove existing etcd data directory first. The directory can be found in ETCD_DATA_DIR variable. Suppose it is /var/lib/etcd, you need root permission to write to it, then correct ownership before starting the service:&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;&amp;gt; sudo systemctl stop etcd &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo mv /var/lib/etcd/ /tmp/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; sudo etcdctl snapshot restore /home/cloud_user/etcd_backup.db --data-dir /var/lib/etcd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; sudo chown -R etcd:etcd /var/lib/etcd &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo systemctl start etcd&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To verify the restore result, simply run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; etcdctl get cluster.name&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 class="wp-block-heading" id="h-object-management"&gt;Object Management&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the CKA exam, we need to interact with many types of built-in Kubernetes objects.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;RBAC objects:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;A Role defines permissions &lt;strong&gt;within namespace&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;A ClusterRole defines &lt;strong&gt;cluster-wide&lt;/strong&gt; permissions.&lt;/li&gt;&#10;&lt;li&gt;Both Roles and ClusterRoles are K8s objects that defines a set of permissions&lt;/li&gt;&#10;&lt;li&gt;RoleBinding and ClusterRoleBinding are objects that connect Roles and ClusterRoles to users.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Service Account: an account &lt;strong&gt;used by container processes&lt;/strong&gt; within Pods to authenticate the K8s API. If your Pods need to communicate with the K8s API, you can use service accounts to control their access.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="has-white-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="271px" viewBox="-0.5 -0.5 271 261" style="max-width:100%;max-height:261px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="150" y="70" width="120" height="50" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 95px; margin-left: 152px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;RoleBinding&lt;br&gt;* roleRef&lt;br&gt;* subjects&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="152" y="99" fill="#000000" font-family="Helvetica" font-size="12px"&gt;RoleBinding&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="70" width="120" height="50" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 95px; margin-left: 2px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;ClusterRoleBinding&lt;br&gt;* roleRef&lt;br&gt;* subjects&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="2" y="99" fill="#000000" font-family="Helvetica" font-size="12px"&gt;ClusterRoleBinding&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="0" width="100" height="30" rx="4.5" ry="4.5" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 15px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;ServiceAccount&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="19" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;ServiceAccount&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="170" width="120" height="90" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 215px; margin-left: 2px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;&lt;div&gt;&lt;span&gt;ClusterRole:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;* rules&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; apiGroups&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resources&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resourceNames&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; verbs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="2" y="219" fill="#333333" font-family="Helvetica" font-size="12px"&gt;ClusterRole:&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="150" y="170" width="120" height="90" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 215px; margin-left: 152px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;&lt;div&gt;&lt;span&gt;Role:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;* rules&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; apiGroups&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resources&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resourceNames&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; verbs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="152" y="219" fill="#333333" font-family="Helvetica" font-size="12px"&gt;Role:&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 196.37 15 L 203.18 15 Q 210 15 210 25 L 210 70" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 191.12 15 L 198.12 11.5 L 196.37 15 L 198.12 18.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 83.63 15 L 70 15 Q 60 15 60 25 L 60 70" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 88.88 15 L 81.88 18.5 L 83.63 15 L 81.88 11.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 60 120 L 60 163.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 168.88 L 56.5 161.88 L 60 163.63 L 63.5 161.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 210 120 L 210 163.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 210 168.88 L 206.5 161.88 L 210 163.63 L 213.5 161.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Inspect resource usage either with a K8s Metrics Server, or by command:&lt;/li&gt;&#10;&lt;/ul&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;&amp;gt; kubectl top pod --sort-by &amp;lt;JSONPATH&amp;gt; --selector &amp;lt;selector&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://andrewlock.net/running-kubernetes-and-the-dashboard-with-docker-desktop/"&gt;Here&lt;/a&gt; is a good guide to install metrics server and dashboard (e.g. on docker-desktop).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-pods-and-containers"&gt;Pods and Containers&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;ConfigMaps: store data in key-value map.&lt;/li&gt;&#10;&lt;li&gt;Secrets: same as ConfigMaps but for sensitive data only&lt;/li&gt;&#10;&lt;li&gt;Two ways to pass ConfigMap and Secret data to your container:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;As environment variables in container operating system&lt;/li&gt;&#10;&lt;li&gt;As files presented on mounted volumes in container file system.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Container Resource management:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Resource requests: K8s scheduler will use resource requests to avoid scheduling pods on nodes that do not have enough available resources. 1 CPU unit = 1/1000 of one core&lt;/li&gt;&#10;&lt;li&gt;Resource limits: allow you to limit the amount of resources your containers can use. The container runtime is responsible for enforcement. The enforcement behaviour is different. For example, some terminates container that attempts to use more resource than the limit.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Probes&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Liveness Probe: automatically determine whether or not a container application is in a healthy state. By default K8s does not consider a container to be down until the container process stops. Liveness Probe allow you to customize this detection mechanism and make it more sophisticated.&lt;/li&gt;&#10;&lt;li&gt;Startup Probes: similar to liveness probes. However, while liveness probes run constantly on a schedule, startup probes run at container startup and stop running once they succeed. Startup probes are used to determine when the application has successfully started up. It is especially useful for legacy applications that can have long startup times.&lt;/li&gt;&#10;&lt;li&gt;Readiness Probes: determine when a container is ready to accept requests. When you have a service backed by multiple container endpoints, user traffic will not be sent to a particular pod until its containers have all passed the readiness checks defined by their readinesse probes. Use readiness probes to prevent user traffic from being sent to pods that are still in the process of starting up.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Restart policy for self-healing pods&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;(default) Always: container will always be restarted if they stop, even if they completed successfully (returned 0).&lt;/li&gt;&#10;&lt;li&gt;OnFailure: container will be restarted if the container process exists with an error code, or the container is determined to be unhealthy by a liveness probe.&lt;/li&gt;&#10;&lt;li&gt;Never: let it be&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Multi-container pods:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;containers share the same networking namespace and can communicate with one another on any port, even if the port is not exposed to the cluster&lt;/li&gt;&#10;&lt;li&gt;Container can use volumes to share data in a Pod. Example: a legacy application is hard-coded to write log output to a file on disk. You use a sidecar container to read the log file from shared volume and prints it to the console so the log output will appear in the container log.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Init containers: containers that run once during the startup process of a pod. A pod can have any number of init containers, and they will each run once into completion, before the next init container starts. You may use init containers to perform a variety of startup tasks, they can contain and use software and setup scripts that are not needed by your main containers. They are often useful in keeping your main containers lighter and more secure by offloading startup tasks to a separate container. Use case include:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;cause a pod to wait for another K8s resource to be created before finishing startup&lt;/li&gt;&#10;&lt;li&gt;perform sensitive startup steps securely outside of app containers&lt;/li&gt;&#10;&lt;li&gt;populate data into a shared volume at startup&lt;/li&gt;&#10;&lt;li&gt;communicate with another service at startup&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Scheduling: Scheduler (a component in control plane) assigns Pods to a suitable Node so kubelets can run them. The factor taken into account:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;resource request vs available node resources&lt;/li&gt;&#10;&lt;li&gt;various configurations that affect scheduling using node labels&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Pod allocation&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;nodeSelector is an attribute of Pod to allow you to limit which Node(s) the Pod can be scheduled on. The selector is based on label.&lt;/li&gt;&#10;&lt;li&gt;nodeName is an attribute of Pod that allows you to bypass scheduling and assign Pod to a specific Node by name.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;DaemonSet: automatically runs a copy of a Pod on each node. When a new node is added to the clsuter, DaemonSet will run a new copy of the Pod on it. DaemonSets also respect normal scheduling rules around node labels, taints and tolerations. If a pod would not normally be scheduled on a node, a DaemonSet will not create a copy of the Pod on that node.&lt;/li&gt;&#10;&lt;li&gt;Static Pod: A Pod that is managed directly by the kubelet on a node, not by the K8s API server. They can run even if there is not K8s API server present. Kubelet automatically creates static Pods from YAML manifest files located in the manifest path on the node.&lt;/li&gt;&#10;&lt;li&gt;Mirror Pod: Kubelet will create a mirror Pod for each static Pod. Mirror Pods allow you to see the status of the static Pod via the K8s API, but you cannot change or manage them via the API.&lt;/li&gt;&#10;&lt;li&gt;Taints: applied to nodes to repel a set of pods. A taint specifies key-value and effect. Effect can be &lt;code&gt;NoSchedule&lt;/code&gt; or &lt;code&gt;NoEffect&lt;/code&gt;. The former prevents pods without matching tolerations to schedule to the tainted node. The latter also evicts pre-existing pods with no matching toleration. &lt;/li&gt;&#10;&lt;li&gt;Tolerations: applied to pods so they can be scheduled to nodes with matching taints. A toleration consists of key-value pair, effect and operation. The operation can be &lt;code&gt;Equal&lt;/code&gt; or &lt;code&gt;Exists&lt;/code&gt;. To determine whether a toleration matches a taint. The keys and the effects must be the same. In addition:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;the operator is Exists (and thus no value should be specified in the toleration); or&lt;/li&gt;&#10;&lt;li&gt;the operator is Equal, and all the values match those of the taints;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we have three ways to influence the scheduling behaviour. The first, is simply by specifying &lt;code&gt;nodeSelector&lt;/code&gt; on the Pod, with the required the node label. The second, as just discussed, is to use &lt;code&gt;Taints&lt;/code&gt; and &lt;code&gt;Tolerations&lt;/code&gt;. The third way, is similar to the first, using &lt;code&gt;nodeAffinity&lt;/code&gt; attributes on Pods. nodeAffinity is more powerful and flexible than nodeSelector by supporting more complex scheduling rules (e.g. matching rules).&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Use Node Affinity when your scheduling rule is based on &lt;span style="text-decoration: underline" class="underline"&gt;direct condition&lt;/span&gt;, i.e. &lt;span style="text-decoration: underline" class="underline"&gt;schedule a Pod to this Node when XXX&lt;/span&gt;. In this case, you have &lt;a href="https://kubernetes.io/docs/reference/labels-annotations-taints"&gt;well-known labels&lt;/a&gt; on nodes, and specify &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/#schedule-a-pod-using-required-node-affinity"&gt;nodeAffinity&lt;/a&gt; on Pods. &lt;/li&gt;&#10;&lt;li&gt;Use Taints and Tolerations when your scheduling rule is based on &lt;span style="text-decoration: underline" class="underline"&gt;inverse statement, i.e. do not schedule a Pod to this Node unless XXX&lt;/span&gt;. In this case, you put a taint &amp;#8220;MyCondition:NoSchedule&amp;#8221; on a Node, so that no Pod will ever get scheduled to this Node. The only exception is when a Pod has the Toleration &amp;#8220;MyCondition:NoSchedule&amp;#8221;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-deployments"&gt;Deployments&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Deployment is an object that defines a desired state for a ReplicaSet (a set of replica Pods). The Deployment Controller seeks to maintain the desired state by creating, deleting, and replacing Pods with new configurations.&lt;/li&gt;&#10;&lt;li&gt;With Deployments, you can horizontally scale an application up and down by changing the number of replicas. You can perform rolling updates and rollback.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-networking"&gt;Networking&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The K8s network model defines how Pods communicate with each other, regardless of which Node they are running on.&lt;/li&gt;&#10;&lt;li&gt;Each Pod has its own unique IP address within the cluster. Any Pod can reach any other Pod using that Pod&amp;#8217;s IP address. This creates a virtual network that allows Pods to easily communicate with each other.&lt;/li&gt;&#10;&lt;li&gt;One type of K8s network plugin is CNI plugin. It has many flavours such as Calico. Each plugin has its own unique installation process. Kubenetes nodes will remain &lt;strong&gt;NotReady&lt;/strong&gt; until a network plugin is installed.&lt;/li&gt;&#10;&lt;li&gt;The K8s virtual network uses a DNS (e.g. a Kubeadm cluster uses CoreDNS pod in kube-system namespace) to allow Pods to locate other Pods and Services using domain names. The Pod DNS name follows this format: pod-ip-address.namespace.pod.cluster.local&lt;/li&gt;&#10;&lt;li&gt;A K8s NetworkPolicy is an object that allows you to control the flow of network communication to and from Pods so you can isolate traffic. NetworkPolicy can apply to Ingress (using from selector), Egress (using to selector) or both.&lt;/li&gt;&#10;&lt;li&gt;NetworkPolicy has an attribute podSelector to determine to which Pods in the namespace the NetworkPolicy applies, by selecting Pods by with Pod labels.&lt;/li&gt;&#10;&lt;li&gt;By default, Pods are considered non-isolated and completed open to all communication. If any NetworkPolidy selects a Pod, the Pod is considered isolated and will only be open to traffic allowed by NetworkPolicies.&lt;/li&gt;&#10;&lt;li&gt;A variety of selector can be used: podSelector, namespaceSelector, ipBlockSelector and port.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-services"&gt;Services&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Services provide a way to expose an application running as a set of pods, so clients can access applications in an abstract way without needing to be aware of the application pods. In this model, client make requests to a Service, which routes traffic to its pods in a load-balanced fashion&lt;/li&gt;&#10;&lt;li&gt;Endpoints are the backend entities to which Services route traffic. If there are multiple Pods behind a service, each Pod will have an endpoint associated with the service.&lt;/li&gt;&#10;&lt;li&gt;Each service has a type that determines how and where service will expose your application.&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;ClusterIP: expose application inside the cluster network&lt;/li&gt;&#10;&lt;li&gt;NodePort: expose application outside the cluster network&lt;/li&gt;&#10;&lt;li&gt;LoadBalancer: expose application outside thecluster network, but use an extermal cloud load balancer from cloud platform.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Services are assigned with DNS names. The FQDN follows this format: service.namespace.svc.cluster-domain.example, which is used by pods across namespaces&lt;/li&gt;&#10;&lt;li&gt;Pods within the same namespace can reference service simply by service name.&lt;/li&gt;&#10;&lt;li&gt;To manage external access to service, you can also use Ingress object. Ingress object is capable of providing more functionality than a simple NodePort Service, such as SSL termination, advanced load balancing, or name-based virtual hosting. You must install one or more Ingerss controller (many different implementations) to back up the ingress objects.&lt;/li&gt;&#10;&lt;li&gt;Ingress defines a set of routing rules. Each rule has a set of paths, each with a backend. Requests matching a path will be routed to its associated backend.&lt;/li&gt;&#10;&lt;li&gt;If a Service uses a named port, an ingress can also use the port&amp;#8217;s name (instead of port number) to choose to which port of a service it will route.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-storage"&gt;Storage&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Volumes allow you to store data outside the container file system, while allowing the container to access the data at runtime. When Pod is gone, volumes do not persist.&lt;/li&gt;&#10;&lt;li&gt;Persistent Volumes are a slightly more advanced form of Volume. They allow you to treat storage as an abstract resource and consume it in Pods. PV can be provisioned separately by storage administrator, and they persist regardless of pod lifecycle. PV needs to be claimed by pods. PV uses a set of attributes to describe the underlying storage resource.&lt;/li&gt;&#10;&lt;li&gt;Both volumes and PVs each have a volume type: NFS, Cloud (AWS, Azure, GCP), ConfigMaps and Secrets, Simple Directory on node&lt;/li&gt;&#10;&lt;li&gt;Two volume types to distinguish:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;hostPath: store data in a specified directory on K8s node&lt;/li&gt;&#10;&lt;li&gt;emptyDir: store data in dynamically created location on the node. The directory exists only as long as the Pod exists on the node. The directory and the data are deleted as Pod is removed. This type is useful for simply sharing data between containers in the same pod.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Both volumes and PVs are specified under Pod, and individual containers must include volumeMounts object to map volume name to local mountPath&lt;/li&gt;&#10;&lt;li&gt;Storage Class object allow K8s admins to specify the types of storage services they offer on their platform. A key property is allowVolumeExpansion. This allows PVC to resize. At storage class level, there are two reclaim policies: Retain and Delete. The default is Delete.&lt;/li&gt;&#10;&lt;li&gt;PV has an attribute named persistentVolumeReclaimPolicy. This is reclaim policy at PV level. If the attribute is not defined, it is inherited from storage class. The persistentVolumeReclaimPolicy has three options. When PVC is deleted:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Retain: keeps all data but requires admin to manually reclaim the volume (i.e. delete PV, clean up data, delete storage asset)&lt;/li&gt;&#10;&lt;li&gt;Delete (cloud storage only): deletes both PV and the underlying storage resource automatically&lt;/li&gt;&#10;&lt;li&gt;Recycle: scrub (rm -rf /vol/) all data in the underlying storage resource, and allow the volume to be reused.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;PVC represents a user&amp;#8217;s request for storage resources. It defines a set of attributes similiar to those of a PV. When a PVC is created, it will look for a PV that is able to meet the requested criteria. If it finds one, it will automatically be bound to the PV. PVC can be mounted to a Pod&amp;#8217;s containers just like any other volume&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In general the CKA exam experience is quite positive and rewarding. In future posts I will shift focus on Kubernetes not only for the CKA exam, but also for keeping track of my learning.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Good luck with your CKA exam.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/04/public-key-infrastructure-pki/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Public Key Infrastructure (PKI) – Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/05/secure-web-application-deployment/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Secure web application deployment&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Blockchain and DeFi</title><link>https://static.digihunch.com/2021/01/blockchain-and-di-fi/</link><pubDate>Sat, 23 Jan 2021 11:30:00 -0400</pubDate><guid>https://static.digihunch.com/2021/01/blockchain-and-di-fi/</guid><description>&lt;h3 class="wp-block-heading"&gt;Background&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is a reading note for book &amp;#8220;&lt;a href="https://landing.coingecko.com/how-to-defi/" class="rank-math-link"&gt;How to DeFi&lt;/a&gt;&amp;#8220;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When Tom hand in $20 to Jack in exchange of goods. Both Tom and Jack has to agree that the greenback with $20 sign actually is worth the value of the goods. Since the paper money is signed by some big shot from the central banker, which they both trust, they can therefore reach consensus on the value of that paper. The central bank acts as a centralized body of trust. Now you&amp;#8217;d ask what if the central banker cheats on us? As long as we centralize our trust to a single body, we have to worry about the centralized trust deteriorate. This is what decentralized finance aspires to address.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The other background problem is the centralized payment and clearance system. When you need to send money from one country to another, there are not only high fees involved, but also days holding for clearance. This is another opportunity for decentralized finance.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Decentralized Finance (DeFi)&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The technologies in DeFi falls under three categories based on the level of decentralization:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&lt;li&gt;Centralized: custodial, uses centralized price feeds, centrally determined interest rates&lt;/li&gt;&lt;li&gt;Semi-Decentralized: non-custodial, decentralized price feeds&lt;/li&gt;&lt;li&gt;Completely Decentralized: every component is decentralized. &lt;/li&gt;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Most DeFi dapps are sitting in the semi-decentralized category. There is no DeFi protocol that is completely decentralized yet. DeFi involves protocols that covers financial sectors such as Lending &amp;amp; Borrowing, Exchanges, Derivatives, Lottery, Payments, Insurance, etc. This all sounds futurism, but there are a few protocols already at play.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Ethereum&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The majority of the DeFi Dapps are built on the Ethereum blockchain, a global, open-source platform for decentralized applications. You can think of it as a world computer that cannot be shutdown. Developers can also deploy smart contracts to the Ethereum network, where it will run 24/7. Smart Contract is a programmable contract that allows two counterparties to set conditions of a transaction without needing to trust another third party for the execution. Whenever a certain condition is fulfilled, the smart contract will carry out the operation as programmed, and the process is transparent to all involved parties, bypassing the need for a trusted third party intermediary.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ether is the native currency of the Ethereum blockchain so Ether is similar to Bitcoin. Ether is also used to pay for the fee that allows smart contracts and Dapps to run on the Ethereum network. Ether is also evolving to become its own unique reserve currency and store of value.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Dapps (decentralized applications) are interfaces that interact with the blockchain through the use of smart contracts. On Ethereum, all transactions and smart contract executions require a small fee to be paid. The fee is called Gas. In technical terms, Gas refers to the unit of measure on the amount of computational effort required to execute an operation or a smart contract. Gas fees are paied entirely in ETH. The price of gas can fluctuate from time to time depending on the network demand.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ethereum can also be used for two other functions: creating DAO (Decentralized Autonomous Organization), or issuing other cryptocurrencies. A DAO is a fully autonomous organization which is not governed by a single person but is instead governed through code. This code is based on smart contracts and enables DAOs to replace how traditional organizations are typically run. As it runs on code, it would be protected from human intervention and will operate transparently. Governance decisions or rulings would be decided via DAO token voting. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are currently two popular protocols for tokens on the Ethereum Network: ERC-20 and ERC-721&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A wallet is a user-friendly interface to the blockchain network. It manages your private keys, which are basically keys to the lock on your cryptocurrencies&amp;#8217; vault. Wallets allow you to receive, store and send cryptocurrencies.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Custodial wallets are wallets where third-parties keep and maintain control over your cryptocurrencies on your behalf. By using a custodial wallet, you trust an external party to store your coins safely. However, by trusting a third party with your cryptocurrencies, you open yourself up to the risk of the custodian losing your cryptocurrencies through mismanagement or hacks (Mt. Gox)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Non-custodial wallets are wallets where you take full control and ownership of your cryptocurrencies. By using a non-custodial wallet, you trust no external party and only yourself to ensure safe storage. However, you pass the burden of security to yourself and you have to be fully equipped to store your private keys safely. If you lose your private keys, you will lose access to your cryptocurrencies too. Example of non-custodial wallet: Argent&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Stablecoins&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Stablecoins are pegged to other stable assets such as the USD. The top 5 cryptocurrency stablecoins as of Feb 2020 are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Tether (USDT)&lt;/li&gt;&lt;li&gt;USD Coin (USDC)&lt;/li&gt;&lt;li&gt;Paxos Standard (PAX)&lt;/li&gt;&lt;li&gt;True USD(TUSD)&lt;/li&gt;&lt;li&gt;Dai (DAI)&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Not all stablecoins are the same as they employ different mechanisms to keep their peg against USD. There are two types of pegs: fiat-collateralized (e.g. USDT)and crypto-collateralized (e.g. DAI). Most stablecoins are the former.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;USDT pegs itself to $1 by maintaining reserves of $1 per Tether token minted. While Tether is the largest and most widely used USD stablecoin, Thether reserves are kept in financial institutions and users will have to trust Tether as an entity to actually have the reserved amounts that they claim. Tether is therefore a centralized, fiat-collateralized stablecoin.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Dai (DAI) on the other hand, is collateralized using cryptocurrencies such as Ethereum (ETH). Its value is pegged to $1 through protocols voted on by a decentralized autonomous organization and smart contracts. At any given time, the collateral to generate DAI can be easily validated by users. DAI is a decentralized, crypto-collateralized stablecoin.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;DAI has a smaller market capitalization but is increasing tremendously. DAI is the native stablecoin used most widely in the DeFi ecosystem. It is the preferred USD stablecoin used in DeFi trading, lending and more. DAI operates on Maker, a smart-contract platform that runs on the Ethereum blockchain and has three tokens: &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Sai &amp;#8211; aka Single Collateral Dai, backed only by Ether(ETH) as collateral. It is legacy Dai, and will be phased out.&lt;/li&gt;&lt;li&gt;Dai &amp;#8211; aka multi-collateral Dai. Currently backed by Ether (ETH) and Basic Attention Token (BAT) as collaterals with plans to add other assets&lt;/li&gt;&lt;li&gt;Maker (MKR): is Maker&amp;#8217;s governance token and users can use it to vote for improvements on the Maker platform via the Maker Improvement Proposals. Maker is a type of DAO.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MKR holders have voting rights proportional to the amount of MKR tokens they own in the DAO and can vote on parameters governing the Maker Protocol. The parameters that MKR holder vote on are vital in keeping the ecosystem healthy, which in turn helps ensure that Dai remains pegged to $1.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The amount of Dai that can be minted is dependent on the collateral ratio (150% worth of ETH or BAT to mint Dai). There is a stability fee and Dai Saving Rate (DSR).&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Lending and Borrowing&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Compound Fiannce is an Ethereum-based open-source money market protocol where anyone can supply or borrow cryptocurrencies frictionlessly. Many tokens (BAT, ETH, USDC, DAI, and more) can be supplied or used as collateral on the Compound platform.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Compound operates as a liquidity pool that is built on the Ethereum blockchain. Suppliers supply asset to the pool and earn interest, while borrowers take a loan from the pool and pay interest on their debt. In essense, Compound bridges the gaps between the lenders who wish to accrue interest from idle funds and borrowers who wish to borrow funds for productive or investment use. Suppliers and Borrowers interact directly with the protocol for interest rate.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Anyone with a supported cryptocurrency wallet such as Argent can start using Compound immediately. To earn interest, you have to supply assets to the protocol. Once you have deposited your asset into Compound, you will immediately begin to earn interest on the assets you have put in. Upon deposit, you will receive corresponding amounts of cTokens. If you supply DAI, you will receive cDAI. If you supply Ether, you will receive cETH. Interest is not immediately distributed to you, but rather accrues on the cTokens which you now hold and are redeemable for the underlying asset and interest it represents. cTokens represents your balance in the protocol. cTokens become convertible into an increasing amount of the underlying asset it represents over time.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you want to borrow, you have to first supply assets into the system as collateral for your loan. Borrowed assets are sent directly to your Ethereum wallet and from there you can use them. Do not that borrowing incurs a small fee of 0.025% to avoid spams and misuse of the Compound protocol&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;DEX (Decentralized Exchanges)&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Uniswap Exchange is a decentralized token exchange protocol built on Ethereum that allows direct swapping of tokens without the need to use a centralized exchange. On Uniswap, you can simply swap your tokens directly from your wallet without having to go through centralized exchange.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;dYdX is a decentralized exchange protocol for lending, borrowing and margein/leveraged trading. It supports ETH, USDC, and DAI. You can enter either short or long positions with leverages up to 5x.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Other Use Cases &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Derivatives&lt;/strong&gt;: A derivative is a contract whose value is derived from another underlying asset such as stocks, commodities, currencies, indexes, bonds, or interest rates. There are several types of derivatives such as futures, options and swaps, each serving a different purpose. In DeFi, the biggest derivative protocol is Synthetix&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Fund Management&lt;/strong&gt;: In DeFi, fund management is conducted in a manner where it removes the investment manager and lets you choose the asset management strategy that best suits your financial need. TokenSets is a platform that allows crypto users to buy Strategy Enabled Tokens&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Payments&lt;/strong&gt;: Lighting Network, Request Network, xDai and Sablier&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Insurance&lt;/strong&gt;: Nexus Mutual is a decentralized insurance protocol built on Ethereum that currently offers cover on any smart contract on the Ethereum blockchain.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Dashboard&lt;/strong&gt;: a dashboard is a simple platform that aggregates all your DeFi activities in one place.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Basic Resource Object in Kubernetes 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/01/a-shallow-dive-into-artificial-intelligence/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;A shallow dive into Artificial Intelligence&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>High Performance Computing</title><link>https://static.digihunch.com/2020/12/high-performance-computing-cluster/</link><pubDate>Fri, 11 Dec 2020 23:42:00 -0400</pubDate><guid>https://static.digihunch.com/2020/12/high-performance-computing-cluster/</guid><description>&lt;h3 class="wp-block-heading" id="h-overview"&gt;Overview&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;High Performance Computing (HPC) has recently been commoditized with the advent of commodity server hardware (x86 server), virtualization technology and cloud delivery model. It is common in specialized industries where intensive computing tasks are required, for example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;HCL (healthcare and life science): drug discovery, computer aided diagnosis (CAD), genome engineering; &lt;/li&gt;&#10;&lt;li&gt;CAD, CAE, CAM (computer aided design, engineering, and manufacturing): 3D modeling, computational fluid dynamics (CFD), finite element analysis (FEA), structural mechanical design, etc &lt;/li&gt;&#10;&lt;li&gt;Finance: portfolio management, automated trading, risk analysis&lt;/li&gt;&#10;&lt;li&gt;Geoscience and geo-engineering: oil and gas exploration, geographic data, weather forecasting;&lt;/li&gt;&#10;&lt;li&gt;Scientific computation&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Computing performance is measured in FLOPS (floating point operations per second) and is usually delivered in a cluster to aggregate the computing power from a number of networked nodes. This is referred to as an HPC cluster. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-hardware-stack"&gt;Hardware stack&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An HPC cluster features the following components:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Head node (aka master node or login node): a gateway and coordinator; head node may be broken into several nodes&lt;/li&gt;&#10;&lt;li&gt;Compute node (worker node): the executor of jobs; the compute node can either be homogenous or heterogeneous, for different purposes. the number of compute nodes can be quite large&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are four common form factors for server: tower, rack-mount, blade, mainframe. Traditionally, the nodes are rack-mount 1U &amp;#8220;pizza box&amp;#8221; servers. Bladed systems started to replace due to the increased node density, thanks to the shared/redundant power and cooling management. In the past, the HPC cluster is operated in data centres, which is an expensive operation item. In the last decades, many organizations extends their compute workload to the cloud, forming a hybrid model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;HPC typically has specialized storage system because HPC applications notoriously create large amounts of data. NFS traditionally does not scale well as number of node increases. Some proprietary storage system such as Isilon provides good performance via NFS protocol. There are also open-source parallel file system such as Lustre and HDFS. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;HPC networking handles three types of traffic:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;computation traffic between compute nodes (if the compute nodes interact with each other)&lt;/li&gt;&#10;&lt;li&gt;file system traffic: for compute nodes to read and write on file system (e.g. NFS)&lt;/li&gt;&#10;&lt;li&gt;administrative traffic: fairly light compared to the two above&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For that, many HPC runs two networks, a private (backend) network and a public (frontend) network. Backend network must be high speed and low latency, typically in the form of 10Gig Ethernet, or InfiniBand.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-software-stack"&gt;Software stack&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the software layer, the core functionality is &amp;nbsp;&lt;strong&gt;Message Passing Interface (MPI)&lt;/strong&gt;, a specification for the developers and users of message passing libraries. MPI constitutes a standardized and portable message-passing system which consists of a library and a protocol to support parallel computing. MPI enables passing information between various nodes of a HPC cluster or between particular clusters, and has different implementations that provide the libraries to run HPC applications in a distributed manner across different physical nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the operation, user submits a job through head node in order to request the resource. User needs to specify the resources for the job (e.g. how many CPU cores, how much memory, etc). The head node runs a scheduler to allocate computing resource based on pre-defined policies, based on priority of jobs, availability of resources, distribution of load, etc. Depending on the nature of the computing jobs, the nodes participating in the task may or may not communicate with one another. If they do need to talk to each other, the program must support it. Such program can be called a cluster program, and the MPI (message passing interface) library greatly facilitates the development of such program. The sub-jobs communicating with each other also creates a considerable amount of network traffic within the cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cluster software ties all nodes in the cluster together. It turns raw hardware into a functioning cluster by provisioning (installing and configuring) the head nodes. Compute nodes can usually be added or removed dynamically therefore the head nodes should be able to provision compute nodes, and administer cluster, leaving the programming as the job for the user to complete. As mentioned, in parallel programming, the most important HPC tool is MPI (Message Passing Interface), which allows programs to talk to one another over cluster networks. There are both open (e.g. &lt;a href="https://www.open-mpi.org/" class="rank-math-link"&gt;Open MPI&lt;/a&gt;) and commercial MPI (e.g. &lt;a href="https://docs.microsoft.com/en-us/message-passing-interface/microsoft-mpi" class="rank-math-link"&gt;Microsoft MPI&lt;/a&gt;) versions. Cluster software should also provide compilers, debuggers, and profilers in addition to MPI.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are cluster software in both Linux and Windows operating systems: &lt;a href="http://www.rocksclusters.org/" class="rank-math-link"&gt;Rocks Clusters&lt;/a&gt;, &lt;a href="https://oscar-cluster.github.io/oscar/" class="rank-math-link"&gt;Oscar &lt;/a&gt;(Open Source Clusters Application Resources), Red Hat HPC solution, &lt;a href="https://docs.microsoft.com/en-us/powershell/high-performance-computing/overview?view=hpc19-ps" class="rank-math-link"&gt;Microsoft HPC pack&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/parallelcluster/latest/ug/what-is-aws-parallelcluster.html" class="rank-math-link"&gt;AWS Parallel Cluster&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-implementation"&gt;Implementation&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a class="rank-math-link" href="https://www.webmo.net/support/pdf/byoc.pdf"&gt;Here&lt;/a&gt; is an example of setting up HPC cluster with CentOS. Despite of the well documented steps, note that the author of the document refers to HPC cluster simply as cluster, which is ambiguous. There are&lt;strong&gt; three basic motivators for creating a cluster&lt;/strong&gt;: high performance computing (HPC), network traffic load balancing, and service resilience in the form of high availability (HA). The author should be specific in the &lt;a class="rank-math-link" href="https://www.webmo.net/support/pdf/byoc-centos7.pdf"&gt;document&lt;/a&gt; about the HPC cluster. If RDMA (Infiniband) network is involved, a configuration guide is provided in RedHat literature.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://aws.amazon.com/getting-started/hands-on/deploy-elastic-hpc-cluster/" class="rank-math-link"&gt;Here&lt;/a&gt; is an example of deploying HPC cluster in AWS. &lt;a href="https://docs.microsoft.com/en-us/powershell/high-performance-computing/overview?view=hpc19-ps" class="rank-math-link"&gt;Here&lt;/a&gt; is the guide to deploy HPC pack in Microsoft technologies.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-hpc-and-big-data"&gt;HPC and Big Data&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;HPC and Big Data are two distinctive computing paradigmes. Although there is some signs of convergence and blurred boundaries, it is still a long way before one can treat HPC and Big Data interchangeably. This &lt;a href="https://ieeexplore.ieee.org/document/7776538" class="rank-math-link"&gt;paper&lt;/a&gt; does a phenomenal job in comparing the two paradigms. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The fundamental difference lies in the respective problems they intend to address. HPC focuses on the large computational loads, whereas Big Data targets applications that need to handle very large and complex data sets (usually in the order of multi-terabytes or exabytes). Many scientific data analytics applications are becoming I/O bound in modern systems, such as seismic algorithms, Big Data applications are thus very demanding in terms of storage, to accommodate such a masive amount of data, while HPC is usualy thought more in inters of sheer computational needs. The open-source projects in Big Data also aims to run on conventional hardware to make it easier and less expensive to scale. This is not the main focus of HPC.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So, you can run Big Data (e.g. Hadoop) analytics jobs on HPC gear. On the other hand, you can&amp;#8217;t run HPC jobs on commodity hardware as commonly seen in the Big Data stack. Both HPC and Hadoop analytics use parallel processing of data. In a Hadoop/analytics environment, data is stored on commodity hardware and distributed across multiple nodes of hardware. In HPC, where the size of data file is much greater, data storage in centralized. Also, because of the sheer volume of its files, HPC also requires more expensive networking communications such as Infiniband, because the size of the file it processes require high throughput and low latency.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In BigData job, each query in Hadoop reads data from disk and runs as a separate MapReduce job. Spark enables in-memory iterative processing (through the RDD abstraction), allowing the user to query repeatedly on a dataset without having to perform intermediate disk operations. RDD are exposed in the Spark API where each dataset is represented as a read-only object, and transformations are invoked using methods on these objects. For an example project, check out &lt;a href="https://static.digihunch.com/2020/09/spark-cassandra-and-python/" class="rank-math-link"&gt;this&lt;/a&gt; post.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The underlying software stacks for HPC and Big Data are fundamentally different, mainly due to the differences represent in their target class of applications, as outlined in the diagram below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1598" height="862" src="https://static.digihunch.com/wp-content/uploads/2020/12/image.png" alt="" class="wp-image-1942"/&gt;&lt;figcaption class="wp-element-caption"&gt;software stack difference between HPC and Big Data&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to which one is for me, the &lt;a href="https://www.techrepublic.com/article/4-steps-to-implementing-high-performance-computing-for-big-data-processing/" class="rank-math-link"&gt;over-simplified advice&lt;/a&gt; is: if you can avoid HPC and just use Hadoop for your analytics, do it. It is cheaper, easier, and more cloud friendly. However, bear in mind that an all-Hadoop shop is not possible for many industries such as life sciences, weather, pharmaceutical, and academic applications.&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/2020/12/instance-initialization-with-aws-cdk-in-python/"&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 Python – provision Kubernetes Nodes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/12/ansible-tower-lab-environment-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next 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;/nav&gt;&#10;</description></item><item><title>Zookeeper Summary</title><link>https://static.digihunch.com/2020/08/zookeeper/</link><pubDate>Wed, 26 Aug 2020 23:10:00 -0400</pubDate><guid>https://static.digihunch.com/2020/08/zookeeper/</guid><description>&lt;h3 class="wp-block-heading" id="h-distributed-systems"&gt;Distributed systems&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Distributed system involves &lt;span style="text-decoration: underline;"&gt;independent computing entities&lt;/span&gt; linked together by network. The components &lt;span style="text-decoration: underline;"&gt;communicate and coordinate&lt;/span&gt; with each other to achieve a &lt;span style="text-decoration: underline;"&gt;common goal&lt;/span&gt;. In early days, designers and developers often had made some assumptions (aka. fallacies) of distributed computing:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The network is reliable&lt;/li&gt;&#10;&lt;li&gt;Latency is zero&lt;/li&gt;&#10;&lt;li&gt;Bandwidth is infinite&lt;/li&gt;&#10;&lt;li&gt;Network is secure&lt;/li&gt;&#10;&lt;li&gt;Topology doesn&amp;#8217;t change: in reality, components to a network get removed/added over time. the system should tolerate such changes.&lt;/li&gt;&#10;&lt;li&gt;There is one administrator: for distributed systems to function, they interact with external system beyond administrative control.&lt;/li&gt;&#10;&lt;li&gt;Transport cost is zero:&amp;nbsp; cost is involved everywhere, in the form of CPU cycles spent, to actual dollars paid to service provider.&lt;/li&gt;&#10;&lt;li&gt;Network is homogenous&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These fallacies make coordinating distributed computing entities a huge challenge and Zookeeper is introduced to address these challenges. Zookeeper implements common tasks for distributed coordination, such as:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Configuration Management (propagate configuration changes to all worker nodes dynamically)&lt;/li&gt;&#10;&lt;li&gt;Naming service&amp;nbsp;&lt;/li&gt;&#10;&lt;li&gt;Distributed synchronization (locks and barriers)&lt;/li&gt;&#10;&lt;li&gt;Cluster membership operations (e.g. detection of node leave/join)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ZooKeeper is a centralized coordination service for the distributed application. ZooKeeper itself is distributed as well. It runs on its own cluster of servers called a ZooKeeper ensemble, separate from application&amp;#8217;s cluster. Distributed consensus, group management, presence protocols, and leader election are implemented by the service so that the application developers do not need to reinvent the wheel by implementing them on their own.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://zookeeper.apache.org/doc/r3.6.1/images/zkservice.jpg" alt="ZooKeeper Service"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Developers will have to use APIs through ZooKeeper&amp;#8217;s client library, which has language bindings for almost all popular programming languages. The client library is responsible for the interactions of an application with the ZooKeeper service. For testing with API access one can alternatively use its Java-based command-line shell (zkCli.sh)&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;$ zkCli.sh -server zknode:2181&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 class="wp-block-heading" id="h-how-zookeeper-works"&gt;How Zookeeper works&lt;/h3&gt;&#10;&lt;h4 class="wp-block-heading" id="h-data-model"&gt;Data Model&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ZooKeeper allows distributed process to coordinate with each other through a shared hierarchical namespace of data registers (znodes). The hierarchy start with root node which has child znode(s). Each znode can have their children, as well as store its own data (hence the name data register). The data in a znode is stored in byte format for a maximum of 1MB (ZooKeeper by design is just a coordinator service of host application, so its own data set size is fairly small).&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="360" height="368" src="https://static.digihunch.com/wp-content/uploads/2023/01/zkdm.jpeg" alt="" class="wp-image-7753" srcset="https://static.digihunch.com/wp-content/uploads/2023/01/zkdm.jpeg 360w, https://static.digihunch.com/wp-content/uploads/2023/01/zkdm-293x300.jpeg 293w" sizes="auto, (max-width: 360px) 100vw, 360px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Zookeeper data model&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Znodes have two types (set at time of creation) &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;persistent znode: for storing persistent data, such as configuration. The znodes and their data will exist even if the creator client dies.&lt;/li&gt;&#10;&lt;li&gt;ephemeral znode: deleted by ZooKeeper service when the creating client&amp;#8217;s session ends (due to disconnection or explicit termination). It can also be explicitly deleted by creator client through delete API call. They cannot have children. Their visibility is controlled by ACL policy&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ZooKeeper can assign an incremental sequence number as part of znode name during its creation. This makes a sequential node. Both persistent znode and ephemeral znode can be either sequential or not.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In typical client-server architecture, server is passively open and do not initiate communication to client. Client pulls information from server. This is however an anti-pattern for large scale distributed system. ZooKeeper implements a Watch mechanism where clients can get notifications from ZooKeeper service, instead of having to poll for events. Clients can register with the ZooKeeper service (by setting a watch on znode) for any changes associated with a znode. A watch will only trigger notification once, and needs to be re-registered (by client) for trigger the next notification. A watch is triggered upon:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Any changes to the data of a znode;&lt;/li&gt;&#10;&lt;li&gt;any changes to the children of a znode;&lt;/li&gt;&#10;&lt;li&gt;Creation of deletion of a znode&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ZooKeeper guarantees that notifications are delivered in the order of event occurrence. When a client disconnects from ZooKeeper server, it doesn&amp;#8217;t receive any watches until the connection is re-established. &lt;/p&gt;&#10;&lt;h4 class="wp-block-heading" id="h-api-operations"&gt;API Operations&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The ZooKeeper operations are:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Operation&lt;/td&gt;&lt;td&gt;Description&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;create&lt;/td&gt;&lt;td&gt;Creates a znode in the specified path&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;delete&lt;/td&gt;&lt;td&gt;Deletes a znodes from the specified path. Not allowed if the znode has children. version number required&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;exists&lt;/td&gt;&lt;td&gt;Check if a znode at the specified path exists, and get version number; support watch&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;getChildren&lt;/td&gt;&lt;td&gt;Get a list of children of a znode; support watch&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;getData&lt;/td&gt;&lt;td&gt;get the data associated with a znode; support watch&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;setData&lt;/td&gt;&lt;td&gt;writes data into the data field of a znode. Version number required.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;getACL&lt;/td&gt;&lt;td&gt;get the ACL of a znode&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;setACL&lt;/td&gt;&lt;td&gt;set the ACL in a znode&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;sync&lt;/td&gt;&lt;td&gt;synchronizes a client&amp;#8217;s view of a znode &lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The write operations (setData, create, delete) are atomic, durable and eventually consistent. Every znode has a stat structure including cZxid, mZxid an dpZxid that keeps track of the ID of the transactions that created, last modified this znode, or pertains to adding or removing its children.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Production znode ensemble with more than one node is running in quorum mode. Updates to ZooKeeper tree by clients must be persistently stored in this quorum of nodes for a transaction to be completed successfully. Odd number of node is recommended to avoid split-brain where network partition causes two subsets of servers in the ensemble function independently, and different clients get different results for the same requests, depending upon the server they are connected to.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All ZooKeeper nodes are listed in the configuration for client application to randomly pick from and try to connect and establish a session. The session is associated with every operation the client executes in a ZooKeeper service. The session also has a timeout period specified by the application client during session establishment. If the connection remains idle for more than the timeout period, the server expires the session. Appropriate session timeout should be set based on network condition. Sessions are kept alive by client sending heartbeat to ZooKeeper service. Application developer needs to handle connection-loss scenarios properly.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading" id="h-leader-election-and-atomic-broadcast"&gt;Leader Election and Atomic Broadcast&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ZooKeeper ensemble contains a leader nodes, follower nodes and observer nodes.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The leader node is elected by the cluster. It handles all write requests. &lt;/li&gt;&#10;&lt;li&gt;The follower nodes are leader candidates that are not elected. They are backup to the leader nodes. They handle read request, and receive the updates proposed by the leader, and through a majority consensus mechanism, a consistent state is maintained across the ensemble. &lt;/li&gt;&#10;&lt;li&gt;The observer nodes are ineligible as leader candidates. They have otherwise the same function as followers.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The service relies on the replication mechanism to ensure that all updates are persistent in all servers that constitute the ensemble. This is the core mechanism in ZooKeeper, implemented as a special atomic messaging protocol called ZooKeeper Atomic Broadcast (ZAB). ZAB (a variant of Paxos algorithm) ensures the election of new leader in the event of old leader crash, and ensures integrity of data. It defines three states (looking, following and leading) of a node, and goes through four phases (election, discovery, sync, broadcast) in its operation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All read requests (exists, getData, getChildren) are process locally by the ZooKeeper node where the client is connected to. This makes read operation fast. All write requests (create, delete, and setData) are forwarded to the leader in the ensemble, which carries out the client request as a transaction. A transaction is identified by zxid and is idempotent. Transaction also satisfies the property of isolation (no transaction is interfered with by any other transaction). Only after a majority of the followers acknowledge that they have persisted the change does the leader commit the update.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://zookeeper.apache.org/doc/r3.6.1/images/zkcomponents.jpg" alt="ZooKeeper Components"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Transaction processing involves two steps in ZooKeeper: leader election and atomic broadcast. This resembles a two-phase commit protocol (which also includes a leader election and an atomic broadcast)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ZooKeeper use local storage to persist transactions. The transactions are logged to transaction logs, in sync&amp;#8217;ed write, requiring a dedicated block device separated from boot device of server. The local storage also keep point-in-time copies (snapshots) of the ZooKeeper tree.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-zookeeper-recipes"&gt;ZooKeeper Recipes&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The ZooKeeper recipes defines high-level implementation (construct) of some common distributed coordination mechanism:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Barrier_(computer_science)"&gt;Barrier&lt;/a&gt;: any thread/process must stop at this point and cannot proceed until all other threads/processes reach this barrier.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://computersciencewiki.org/index.php/Queue"&gt;Queue&lt;/a&gt;: allow FIFO in distributed system&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Lock_(computer_science)"&gt;Lock&lt;/a&gt;: Fully distributed locks that are globally synchronous, meaning at any snapshot in time no two clients think they hold the same lock.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Leader_election"&gt;Leader Election&lt;/a&gt;: designate a single process as the organizer of some task distributed among several nodes.&lt;/li&gt;&#10;&lt;li&gt;Group membership: node may join or leave a group, which needs to be made available to clients. An alternative to ZooKeeper to manage group membership is &lt;a href="https://en.wikipedia.org/wiki/Gossip_protocol"&gt;gossip protocol&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="http://jasonwilder.com/blog/2014/02/04/service-discovery-in-the-cloud/"&gt;Service discovery&lt;/a&gt;: help client to determine IP and port for a service that are hosted by multiple servers.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Two-phase_commit_protocol"&gt;Two-phase commit&lt;/a&gt;: a mechanism for atomic commitment in two steps: first a commit request phase involving a voting by participants; and second, either a commit action, or an abort action, based on the voting result.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-zookeeper-administration"&gt;Zookeeper Administration&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The official &lt;a href="https://zookeeper.apache.org/doc/r3.6.1/zookeeperAdmin.html"&gt;documentation&lt;/a&gt; includes all we need to know about administration. In addition, we need to configure &lt;a href="https://logging.apache.org/log4j/1.2/manual.html"&gt;log4j&lt;/a&gt; for proper logging. As best practices, we also should turn off &lt;a href="https://static.digihunch.com/2018/04/centos-remove-swap-safely/"&gt;swapping&lt;/a&gt; on ZooKeeper. We should clean up the data directory periodically if auto purge is not enabled. For optimal performance, ZooKeeper transaction log should be configured in a dedicated device.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For monitoring, ZooKeeper responds to a small sets of four-letter commands issued through telnet or nc to server&amp;#8217;s client port. This allows the admin to check health of server or diagnose any problems. This requires the following property in zoo keeper config:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;4lw.commands.whitelist=stat, ruok, conf, isro, wchc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The value can be set to asterick to allow all four-letter keyword. Once enabled, we can check server status&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;$ echo ruok | nc localhost &lt;span style="color:#ae81ff"&gt;2181&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;imok&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;More four-letter commands are listed &lt;a href="https://zookeeper.apache.org/doc/r3.1.2/zookeeperAdmin.html#sc_zkCommands"&gt;here&lt;/a&gt;. Apart from the four-letter commands, ZooKeeper can also be managed through Java Management Extensions (&lt;a href="https://www.oracle.com/java/technologies/javase/javamanagement.html"&gt;JMX&lt;/a&gt;).&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Apache ZooKeeper is a coordination service for distributed application. It has become the solution for high availability for many other projects. Some of Apache&amp;#8217;s well known open-source distributed services include:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Apache Hadoop (an umbrella of projects including many components for BigData processing such as Hadoop Common, Hadoop Distributed File System (HDFS), Hadoop YARN (yet another resource negotiator) and Hadoop MapReduce)&lt;/li&gt;&#10;&lt;li&gt;Apache HBase: non-relational database on top of HDFS&lt;/li&gt;&#10;&lt;li&gt;Apache Hive: data warehouse with SQL-like interface&lt;/li&gt;&#10;&lt;li&gt;Apache Kafka: stream processing&lt;/li&gt;&#10;&lt;li&gt;Apache Nifi: automated data flow processing. &lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some of them, such as Nifi, has an embedded implementation of ZooKeeper ensemble if there isn&amp;#8217;t a separate ensemble. There is some limitation with embedded Zookeeper ensemble. First, we cannot start ZooKeeper without starting Nifi service on the same server. Second, we need to orchestrate the configuration so that the ZooKeeper ensemble does not grow too large. We need to keep in mind that the ZooKeeper ensemble is a separate cluster of its own, and the it is not recommended to have more than 7 nodes on ZooKeeper.&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/2020/08/virtualization-4-of-4-networking/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Virtualization 4 of 4 – Networking&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/09/host-legacy-application-with-docker-compose/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Host legacy application in Docker 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kafka high-level Overview</title><link>https://static.digihunch.com/2020/07/zookeeper-and-kafka-overview/</link><pubDate>Tue, 21 Jul 2020 23:19:00 -0400</pubDate><guid>https://static.digihunch.com/2020/07/zookeeper-and-kafka-overview/</guid><description>&lt;h3 class="wp-block-heading" id="h-zookeeper"&gt;Zookeeper&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;General definition of distributed system: a software system that is composed of &lt;strong&gt;independent &lt;/strong&gt;computing entities linked &lt;strong&gt;together &lt;/strong&gt;by a computer network whose components communicate and coordinate with each other to achieve a common computational goal. Implementing coordination among components of a distributed system is hard. For example, designated master node becomes single point of failure; cluster needs to detect availability of new nodes as it joins cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Zookeeper is designed to &lt;strong&gt;simplify cluster coordination&lt;/strong&gt;. Zookeeper implements key aspects in cluster coordination, such as distributed consensus, group management, presence protocols and leader election. In order to coordinate a cluster, zookeeper itself also runs in its own cluster, called &lt;strong&gt;ensemble&lt;/strong&gt;. Zookeeper exposes a simple but powerful interface of primitives. Applications can be designed on these primitives implemented through ZooKeeper APIs to solve the problems of distributed synchronization, cluster configuration management, group membership, etc.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://zookeeper.apache.org/doc/r3.4.6/images/zkservice.jpg" alt=""/&gt;&lt;figcaption class="wp-element-caption"&gt;Zookeeper Ensemble&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Clients can connect to a Zookeeper service by connecting to any member of the ensemble. The members of the ensemble are aware of each other&amp;#8217;s state. As long as a majority of the nodes are available, the service will be available. &lt;strong&gt;Zookeeper cli (zkCli.sh)&lt;/strong&gt; can be used to connect to Zookeeper server. they can be downloaded from &lt;a href="https://zookeeper.apache.org/releases.html"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Zookeeper is integrated with many other services apart from Kafka, such as Nifi and Hadoop.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-kafka"&gt;&lt;strong&gt;Kafka&lt;/strong&gt;&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://kafka.apache.org/"&gt;Kafka &lt;/a&gt;is a messaging system that is horizontally scalable, fault tolerant. It can also serve as queue storage system and stream processing system. It is distributed and use Zookeeper for cluster coordination. Each node is called a broker.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://kafka.apache.org/25/images/log_anatomy.png" alt=""/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Topics &lt;/strong&gt;in Kafka (think of table in database) is a category or feed name to which messages (records) are published. Topic is broken up into ordered commit logs called partitions. Each partition has an ID. Each message in a partition is assigned an offset. Topics that are created in Kafka are distributed across brokers based on the partition, replication, and other factors. Each partition is replicated across several brokers depending on replication factor. For each partition, Kafka elect one replica as the leader of partition.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Writes to a partition is generally sequential. Reading messages can either be from the beginning, or rewind or skip to any port in partition given an offset value. Data in a topic is retained for a configurable period of time. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A &lt;strong&gt;message &lt;/strong&gt;is a unit of data in Kafka, in the format of key-value pair. A key is used to control the message that is to be written to partitions. Messages with the same keys are always written to the same partition (hash map)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A &lt;strong&gt;producer &lt;/strong&gt;publishes new message to a topic. Producers do not care which partition the message is written to and will balance messages over every partition of a topic evenly. Directing messages to a partition is done using the message key and a partitioner, this will generate a hash of the key and map it to a partition.&lt;/p&gt;&#10;&lt;figure class="wp-block-image is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://kafka.apache.org/25/images/log_consumer.png" alt="" width="370" height="225"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A &lt;strong&gt;consumer &lt;/strong&gt;is subscribed to one or more topics and read messages sequentially. The consumer keeps track of messages it has consumed by keeping track on the offset of the message. The offset is a bit of metadata (an integer value that continually increases) that kafka adds to each message. Each partition has a unique offset which is stored with the offset of the last consumed message. A consumer can stop and start without losing its current state.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Kafka &lt;strong&gt;broker &lt;/strong&gt;is designed to operate as part of a cluster. One broker in the cluster also function as the cluster&amp;#8217;s controller, which is responsible for administrative operations such as: assigning partitions to brokers; monitoring for broker failures in cluster. A particular partition is owned by a broker and that broker is called the leader of the partition.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;All consumers and producers operating on that partition must connect to the leader.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kafka cluster may replicate across cluster using MirrorMaker.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Reference: &lt;strong&gt;Kafka: The Definitive Guide: Real-Time Data and Stream Processing at Scale&lt;/strong&gt;&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" src="https://static.digihunch.com/wp-content/uploads/2023/01/kafka-780x1024.jpeg" alt="" class="wp-image-7913" width="207" height="272" srcset="https://static.digihunch.com/wp-content/uploads/2023/01/kafka-780x1024.jpeg 780w, https://static.digihunch.com/wp-content/uploads/2023/01/kafka-229x300.jpeg 229w, https://static.digihunch.com/wp-content/uploads/2023/01/kafka-768x1008.jpeg 768w, https://static.digihunch.com/wp-content/uploads/2023/01/kafka.jpeg 1036w" sizes="auto, (max-width: 207px) 100vw, 207px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt; &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/07/nfs-network-file-system-and-rpc-remote-procedure-call/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;How RPC and NFS work&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/07/overview-of-virtualization/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Virtualization 1 of 4 – Hypervisor&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>