<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Command Line Interface (CLI) on Digi Hunch</title><link>https://www.digihunch.com/tags/command-line-interface-cli/</link><description>Recent content in Command Line Interface (CLI) on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Tue, 08 Apr 2025 14:43:30 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tags/command-line-interface-cli/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes Platform as a Service and Red Hat OpenShift</title><link>https://www.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/</link><pubDate>Sun, 25 Jun 2023 11:10:15 -0400</pubDate><guid>https://www.digihunch.com/2023/06/kubernetes-paas-and-red-hat-openshift/</guid><description>&lt;img src="https://www.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://www.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://www.digihunch.com/wp-content/uploads/2023/06/rosa-ui.webp" alt="" class="wp-image-12926" srcset="https://www.digihunch.com/wp-content/uploads/2023/06/rosa-ui.webp 912w, https://www.digihunch.com/wp-content/uploads/2023/06/rosa-ui-267x300.webp 267w, https://www.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://www.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://www.digihunch.com/wp-content/uploads/2023/06/openshift-ui.webp" alt="" class="wp-image-12927" srcset="https://www.digihunch.com/wp-content/uploads/2023/06/openshift-ui.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/06/openshift-ui-300x97.webp 300w, https://www.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://www.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://www.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://www.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://www.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://www.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/</guid><description>&lt;img src="https://www.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://www.digihunch.com/wp-content/uploads/2023/06/ssh-process.webp" alt="" class="wp-image-12921" srcset="https://www.digihunch.com/wp-content/uploads/2023/06/ssh-process.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/06/ssh-process-300x28.webp 300w, https://www.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://www.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://www.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://www.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://www.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>Kubernetes with Multiple CPU Architectures 2 of 2 – Node and Workload</title><link>https://www.digihunch.com/2023/05/kubernetes-with-multiple-cpu-architectures/</link><pubDate>Sat, 20 May 2023 01:34:00 -0400</pubDate><guid>https://www.digihunch.com/2023/05/kubernetes-with-multiple-cpu-architectures/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-k8s-multi-cpu.webp" alt="Featured image of post Kubernetes with Multiple CPU Architectures 2 of 2 – Node and Workload" /&gt;&lt;p class="wp-block-paragraph"&gt;The most common server CPU architectures today are amd64 (aka x86_64) and arm64. Although AMD developed the former first, Intel names it as x86_64 (or x64 for short). In terms of compatibility, they are the same. In general, arm64 architecture consumes less power and therefore mobile systems first favour it. Its power efficiency now drives a trend towards computing infrastructure. For example, Apple&amp;#8217;s MacBook moved to M1 processor in 2020. Since 2018, Amazon&amp;#8217;s &lt;a href="https://aws.amazon.com/ec2/graviton/"&gt;Graviton&lt;/a&gt; processor has entered the third generation. In 2022, Azure also brought &lt;a href="https://azure.microsoft.com/en-us/blog/azure-virtual-machines-with-ampere-altra-arm-based-processors-generally-available/"&gt;Ampere Altra&lt;/a&gt; processor, and GCP introduced &lt;a href="https://cloud.google.com/compute/docs/instances/arm-on-compute"&gt;ARM based VMs&lt;/a&gt;. Less power consumption ultimately leads to less computing cost.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I can only see more workloads gradually move to servers with ARM architecture. With Kubernetes, we will most likely have a fleet of computing node consisting of hybrid CPU architectures. We can take a look at what the arm64 adoption entails for workloads on Kubernetes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-arm64-architecture"&gt;ARM64 architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Graviton processor is on 64-bit Arm Neoverse cores, targeting for optimizing cloud-native workloads. Currently at AWS, the majority of arm64 instances use Graviton2 processor. &lt;a href="https://aws.amazon.com/blogs/aws/new-graviton3-based-general-purpose-m7g-and-memory-optimized-r7g-amazon-ec2-instances/"&gt;This&lt;/a&gt; AWS blog posted the news about Graviton3-based general purpose (m7g) and memory-optimized (r7g) EC2 instances. At the bottom, there is a chart that compares the performance of Graviton3 with Graviton2, x86 and M6g instances. We can expect that in a few months the services that supports Graviton2 processor to start supporting Graviton3 processor.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the serverless landscape, you can &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/foundation-arch.html"&gt;specify CPU architecture&lt;/a&gt; for Lambda function. If your runtime supports arm64 architecture, you enjoy up to 34% price performance improvement according to &lt;a href="https://aws.amazon.com/blogs/aws/aws-lambda-functions-powered-by-aws-graviton2-processor-run-your-functions-on-arm-and-get-up-to-34-better-price-performance/"&gt;this&lt;/a&gt; post. In late 2021, &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/userguide/ecs-arm64.html"&gt;AWS Fargate for ECS&lt;/a&gt; also started to support Graviton2 Processor with arm64 workload. As to Fargate for EKS, it has not supported Graviton2 processor as of yet, but is on &lt;a href="https://github.com/aws/containers-roadmap/issues/1629"&gt;track&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to Kubernetes, I&amp;#8217;ve discussed how to get container registries to support platform-specific images. So we can assume image registries all support OCI format image index(aka fat manifest), which points platform-specific images for arm64 and amd64. In this post, I&amp;#8217;ll focus on the node and workload, with EKS as an example. Since control plane is a managed service, we will focus on the worker node, where the Pods are running. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Worker Node&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project uses Terraform to build our test EKS cluster. One of the node groups consists of the new &lt;a href="https://aws.amazon.com/blogs/aws/new-graviton3-based-general-purpose-m7g-and-memory-optimized-r7g-amazon-ec2-instances/"&gt;m7g&lt;/a&gt;.large instance (Graviton3 processor). For this new node group, the AMI type must be AL2_ARM_64, so it picks up an &lt;a href="https://github.com/awslabs/amazon-eks-ami/releases"&gt;EKS optimized AMI &lt;/a&gt;for arm64 during node provisioning. The IAM role of each node has SSM policy so we can use session manager and pre-installed SSM agents to connect to each node. One the m7g node, I would like to check a few things:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;The node CPU&lt;/li&gt;&#10;&lt;li&gt;The containerd package &lt;/li&gt;&#10;&lt;li&gt;The kubelet executable. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;They all should be for the right CPU architecture, as the following commands clarifies:&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;$ lscpu | grep -i arch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Architecture: aarch64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ yum list | grep containerd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;containerd.aarch64 1.6.6-1.amzn2.0.2 @amzn2extra-docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;containerd-stress.aarch64 1.6.8-1.amzn2.0.1 amzn2extra-docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ file -b &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;which kubelet&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;ELF 64-bit LSB executable, ARM aarch64, version &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;SYSV&lt;span style="color:#f92672"&gt;)&lt;/span&gt;, dynamically linked &lt;span style="color:#f92672"&gt;(&lt;/span&gt;uses shared libs&lt;span style="color:#f92672"&gt;)&lt;/span&gt;, BuildID&lt;span style="color:#f92672"&gt;[&lt;/span&gt;sha1&lt;span style="color:#f92672"&gt;]=&lt;/span&gt;5c7a059f13f8bece4ce30f3357d57631c28bdde2, &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; GNU/Linux 3.7.0, stripped&#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 check image pulling with containerd. Let&amp;#8217;s check what is the correct image first, by examining the image index with &lt;a href="https://github.com/estesp/manifest-tool"&gt;manifest-tool&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;$ manifest-tool inspect digihunch/colorapp:v0.1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The index in the response tells us that:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;the digest of the whole image index starts with 0fa335;&lt;/li&gt;&#10;&lt;li&gt;the manifest digest for the arm64 variant starts with 7479df;&lt;/li&gt;&#10;&lt;li&gt;the manifest digest for the amd64 variant start with 1bd198;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now if we use &lt;a href="https://iximiuz.com/en/posts/containerd-command-line-clients/"&gt;ctr&lt;/a&gt; utility to pull image (&amp;#8220;sudo ctl pull image&amp;#8221;), we can see the correct digest for arm64:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="179" src="https://www.digihunch.com/wp-content/uploads/2023/05/arm64-digest.webp" alt="" class="wp-image-12917" srcset="https://www.digihunch.com/wp-content/uploads/2023/05/arm64-digest.webp 1024w, https://www.digihunch.com/wp-content/uploads/2023/05/arm64-digest-300x52.webp 300w, https://www.digihunch.com/wp-content/uploads/2023/05/arm64-digest-768x134.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The default behaviour of &amp;#8220;&lt;code&gt;ctr image &lt;a href="https://pkg.go.dev/github.com/ktock/containerd#Client.Pull"&gt;pull&lt;/a&gt;&lt;/code&gt;&amp;#8221; is to pull a platform-specific image, overridable with &lt;code&gt;--platform&lt;/code&gt; or &lt;code&gt;--all-platforms&lt;/code&gt;. On an amd64 node, I get the corresponding results as well.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Workload&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s ensure all workloads in the Namespaces are using correct images. We&amp;#8217;ll &lt;a href="https://github.com/digihunch/cloudkube/blob/main/eks_workshop/appmesh/workload.yaml#L14-L39"&gt;deploy&lt;/a&gt; the colorapp, and then examine that along with some system Pods.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For example, DaemonSet aws-node has one pod per node. To verify the distribution, we can get to pods&amp;#8217; command shell and check CPU architecture with &lt;code&gt;uname&lt;/code&gt; 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 -n kube-system get po -l app.kubernetes.io/name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;aws-node -o name | xargs -I&lt;span style="color:#f92672"&gt;{}&lt;/span&gt; kubectl -n kube-system -c aws-node exec &lt;span style="color:#f92672"&gt;{}&lt;/span&gt; -- uname -m&#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 verifies that pods scheduled to arm64 nodes correctly. It does not however, proof that the arm64-specific image is being used. I find it pretty tricky to validate a container is using intended image on arm64 node. I have not found a working kubectl command. There is a plausible tag named imageID under container status. For kube-proxy Pod in kube-system namespace, there are two values. However, for colorapp pods, there is only one value with different format, even though they are scheduled to nodes of both architectures.&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 kube-system get po -l k8s-app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kube-proxy -o yaml | grep &lt;span style="color:#e6db74"&gt;&amp;#39;imageID:&amp;#39;&lt;/span&gt; | sort | uniq&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; imageID: sha256:04beb3b811d345722d689a70a30bafa27e0edd412613bee76c3648b024b25744&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; imageID: sha256:b9b6705d4ad6be861f0e98b7325e5106715ef21a82692f7e8a005a280f159518&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n default get po -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;color -o yaml | grep &lt;span style="color:#e6db74"&gt;&amp;#39;imageID:&amp;#39;&lt;/span&gt; | sort | uniq&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; imageID: docker.io/digihunch/colorapp@sha256:0fa335fdbcc3b644d57c8debe075775b19011985b6342adfb430e7011456d12e&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This &lt;a href="https://github.com/kubernetes/kubernetes/issues/108689"&gt;issue&lt;/a&gt; reports such inconsistency and the issue unfortunately did not get attention. The reporter also asks to have sha256 of the actual image. However, the Kubernetes developers regard this as an CRI issue. Currently we cannot tell exactly which image is used.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I figured out a workaround, by getting on the node and dump the image on 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;$ sudo ctr -n k8s.io image list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo ctr -n k8s.io image export /tmp/x.tar docker.io/digihunch/colorapp@sha256:0fa335fdbcc3b644d57c8debe075775b19011985b6342adfb430e7011456d12e&#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 export tar file review the manifest.json file which contains layer digests. We should find these layer digests match those of the platform-specific image&amp;#8217;s. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Utilities&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since we can ensure that Pod running on a node can always pull the correct platform-specific image, we do not need to worry about Helm chart. We just need to make sure our container registry references an index digest that points to images of multiple architecture. For the same reason, we do not need to worry about pod autoscaling. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When it comes to &lt;a href="https://www.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;node autoscaling&lt;/a&gt;, all node should support have &lt;code&gt;kubernetes.io/arch&lt;/code&gt; and &lt;code&gt;kubernetes.io/os&lt;/code&gt; labels (e.g. &lt;a href="https://karpenter.sh/preview/faq/#can-karpenter-deal-with-workloads-for-mixed-architecture-cluster-arm-vs-amd"&gt;Karpenter&lt;/a&gt;). However, we generally prefer to expand the arm64 node group since it is cheaper. With cluster autoscaler, we can use &lt;a href="https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/expander/priority/readme.md"&gt;priority based expander.&lt;/a&gt; With Karpenter, we can set &lt;a href="https://karpenter.sh/v0.20.0/concepts/provisioning/#specweight"&gt;weight&lt;/a&gt; so that the provisioner for arm64 node group carries higher weight. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Scheduling&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With multi-arch image, the container runtime will pick up the right version of image. From deployment perspective, we do not worry about the difference between nodes in CPU architectures. However, in some use cases, we still want to schedule certain Pods to nodes with one CPU architecture over the other. I call these platform-specific workload.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We mainly needs to control scheduling behaviour. There are two mechanisms, node affinity, and taints &amp;amp; tolerations. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Node Affinity is based on node labeling. From the &lt;a href="https://kubernetes.io/docs/reference/labels-annotations-taints/"&gt;well-known labels, annotations and taints&lt;/a&gt;, all Kubernetes distribution should label their nodes with the &lt;code&gt;kubernetes.io/arch&lt;/code&gt; and &lt;code&gt;kubernetes.io/os&lt;/code&gt; labels. The value for arch is either &lt;code&gt;arm64&lt;/code&gt; or &lt;code&gt;amd64&lt;/code&gt;. When we add a &lt;a href="https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/"&gt;node affinity&lt;/a&gt; of &lt;code&gt;requiredDuringSchedulingIgnoredDuringExecution&lt;/code&gt; type to Pods, scheduler takes &lt;code&gt;matchExpressions&lt;/code&gt; under &lt;code&gt;nodeSelectorTerms&lt;/code&gt; into consideration, when placing Pods to Nodes. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When a Pod has lots of nodeSelectorTerms, it can be brain twisting to sort through the logic. In that case we can use Taints and Tolerations. The idea is that once we taint a node, the scheduler will not schedule any Pod to the Node, unless the Pod has a matching Toleration.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://cablespaghetti.dev/2021/02/20/managing-multi-arch-kubernetes-clusters/#taints-and-tolerations"&gt;this&lt;/a&gt; post, the author customized the bootstrap script so the node provisioning process automatically taints arm64 nodes with &lt;code&gt;arch=arm64:NoSchedule&lt;/code&gt;. Otherwise, we can manually taint a 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;$ kubectl get no -o wide &lt;span style="color:#75715e"&gt;# and check KERNEL-VERSION column, taint the ones with aarch64&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl taint nodes ip-147-207-3-164.us-west-2.compute.internal arch&lt;span style="color:#f92672"&gt;=&lt;/span&gt;arm64:NoSchedule&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This can be a very useful technique when you&amp;#8217;re not sure if every workload image are capable of multi-arch, and you want to avoid scheduling any Pods without tolerations on the arm64 nodes. A Pod cannot get scheduled on those nodes until you confirm their container images, and add corresponding tolerations.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Given the power efficiency, a lot of workload will gradually migrate to arm64 architecture. However, software will take a while to get ready. For example, &lt;a href="https://github.com/kubernetes/minikube/issues/9224"&gt;hyperkit&lt;/a&gt; has not supported M1 processor and I still cannot use it on newer MacOS for &lt;a href="https://www.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/"&gt;Minikube&lt;/a&gt;. Hybrid architecture is here to stay and we need to have an end-to-end examination of our supply chain.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2023/04/kubernetes-multiple-cpu-architecture-container-image/"&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 1 of 2 – Container Image&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2023/06/connect-kubectl-to-private-kubernetes-cluster-in-eks-and-aks/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next 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;/nav&gt;&#10;</description></item><item><title>Build and Manage Kubernetes Clusters</title><link>https://www.digihunch.com/2022/09/build-a-kubernetes-cluster/</link><pubDate>Fri, 23 Sep 2022 11:50:00 -0400</pubDate><guid>https://www.digihunch.com/2022/09/build-a-kubernetes-cluster/</guid><description>&lt;img src="https://www.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://www.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://www.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://www.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://www.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://www.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://www.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://www.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>Kick the tires on ArgoCD</title><link>https://www.digihunch.com/2022/07/kick-the-tires-on-argocd/</link><pubDate>Sun, 10 Jul 2022 00:10:00 -0400</pubDate><guid>https://www.digihunch.com/2022/07/kick-the-tires-on-argocd/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-argocd-tire.webp" alt="Featured image of post Kick the tires on ArgoCD" /&gt;&lt;h2 class="wp-block-heading" id="h-background"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In January, I wrote about &lt;a href="https://www.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;FluxCD&lt;/a&gt;, and adopted it in the &lt;a href="https://github.com/digihunch/korthweb"&gt;Korthweb&lt;/a&gt; project. I like the simple design of FluxCD, and I am comfortable with commands without using a web UI. Half a year later, I am re-visiting this choice, with ArgoCD in mind.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After reading numerous recent posts that compare the two (such as this &lt;a href="https://thenewstack.io/gitops-on-kubernetes-deciding-between-argo-cd-and-flux"&gt;one&lt;/a&gt; from the new stack), I started to give more thoughts on ArgoCD, for a couple reasons. First, ArgoCD has more contributing companies and public references. Red Hat adopted Argo CD as the underlying technology for &lt;a href="https://docs.openshift.com/container-platform/4.7/cicd/gitops/understanding-openshift-gitops.html#about-redhat-openshift-gitops_understanding-openshift-gitops"&gt;OpenShift GitOps&lt;/a&gt; (and Tekton for pipeline). Second, ArgoCD has a mature UI. In corporate collaboration, especially with those not well-versed with command line, a UI is extremely helpful. Bundled with the UI, Argo CD also uses its own RBAC independent of Kubernetes RBAC, making it a great choice for continuous deployment for enterprise applications.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;User Interface&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can choose to install just the &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/installation/#core"&gt;core&lt;/a&gt; components, without UI, SSO and multi-cluster features. To me, it does not make sense because those features are exactly the reason to choose Argo CD. Argo CD also has an eponymous CLI tool, similar to flux for Flux CD. Since Argo CD uses its own RBAC independent of Kubernetes RBAC, Argo CD will have its own credential. The default username is admin and password needs to be retrieved from Secrets. In the tutorial we use this user&amp;#8217;s credential to interact with the target cluster. This is different from Flux where the client simply uses kubectl configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &amp;#8220;&lt;a href="https://argo-cd.readthedocs.io/en/latest/getting_started/#1-install-argo-cd"&gt;Getting Started&lt;/a&gt;&amp;#8221; guide, we create a namespace &amp;#8220;argocd&amp;#8221; and install it with &lt;a href="https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/core-install.yaml"&gt;manifests&lt;/a&gt;. Alternatively, we can use &lt;a href="https://argo-cd.readthedocs.io/en/stable/cli_installation/"&gt;CLI tool&lt;/a&gt; to install it. With Argo CD, we can consider hosting it on a different port than the workload&amp;#8217;s port, such as 8443. This port serves as &amp;#8220;GitOps management port&amp;#8221; separated from the application port (e.g. 443) for business workload. Just like any application on Kubernetes, we usually need to configure an Ingress on the management port.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the argocd namespace, we can see a few &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/server-commands/argocd-server/"&gt;services&lt;/a&gt;. The application set controller acts as the main controller that reconciles between actual state and desired state. There is also a redis server, for storing data in Argo CD. Another service to note is the &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/server-commands/argocd-dex/"&gt;dex&lt;/a&gt; server, indicating that the SSO capability is provided by the &lt;a href="https://dexidp.io/docs/kubernetes/"&gt;Dex&lt;/a&gt; project.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Features&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In FluxCD, they use a CRD Kustomization with kustomize.toolkit.fluxcd.io/v1beta2 as version. This name is confusing. Luckily, in ArgoCD, the CRDs are Application (with argoproj.io/v1alpha1 version) and ApplicationSet (with argoproj.io/v1alpha1 version). The controllers monitors CRs created with these CRDs. ArgoCD defines Application as a group of Kubernetes resources as defined by a manifest. Argo CD Application resource deploys resources from a single Git repository to a single destination cluster/namespace. On the other hand, &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/"&gt;ApplicationSet&lt;/a&gt; uses templated automation to create, modify, and manage multiple Argo CD applications at once. ApplicationSet controllers monitors ApplicationSet resources. We can define a template within the declaration of an &lt;a href="https://argo-cd.readthedocs.io/en/latest/operator-manual/applicationset/#the-applicationset-resource"&gt;ApplicationSet&lt;/a&gt; and it allows for parameter substitution.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another key capability for a GitOps utility is the support of multiple templating tools. FluxCD supports Helm and &lt;a href="https://kustomize.io/"&gt;Kustomize&lt;/a&gt;. So do Argo CD. In addition, ArgoCD also supports &lt;a href="https://argo-cd.readthedocs.io/en/stable/user-guide/jsonnet/"&gt;jsonnet&lt;/a&gt; (&lt;a href="https://argo-cd.readthedocs.io/en/release-2.3/user-guide/ksonnet/"&gt;ksonnet&lt;/a&gt; is not supported anymore). Jsonnet is a templating tool with many useful operators. Kubectl can directly consume Jsonnet&amp;#8217;s JSON output as if it were YAML. In FluxCD, there are different CRDs for Helm and Kustomize (HelmRelease and Kustomization). In ArgoCD, the Application CRD covers &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/helm/#declarative"&gt;Helm&lt;/a&gt;, &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/kustomize/"&gt;Kustomize&lt;/a&gt;, and &lt;a href="https://argo-cd.readthedocs.io/en/latest/user-guide/jsonnet/"&gt;Jsonnet&lt;/a&gt; by embedding them as attributes in the declaration. This makes it even simpler than Flux CD. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Secret Management&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It&amp;#8217;s a big no-no to put secret in a code repository. Therefore any GitOps solution must solve the problem with secret logistics. What baffles me is &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/secret-management/"&gt;ArgoCD remains un-opinionated&lt;/a&gt; on this matter. There appears to be some &lt;a href="https://github.com/argoproj/argo-cd/issues/1364"&gt;context&lt;/a&gt; for this stance. As a result, ArgoCD only points to a few third-party secret management solutions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Secret logistics is a tricky problem with GitOps workflow. One of the techniques is &lt;a href="https://sealed-secrets.netlify.app/"&gt;sealed secret&lt;/a&gt;. In the repo we store the secret as encrypted by public key. The controller keeps the private key which decrypts the secret at the time of deployment. A few engineers don&amp;#8217;t find it feasible for operation, as explained in &lt;a href="https://betterprogramming.pub/why-you-should-avoid-sealed-secrets-in-your-gitops-deployment-e50131d360dd"&gt;this&lt;/a&gt; post and &lt;a href="https://itnext.io/goodbye-sealed-secrets-hello-sops-3ee6a92662bb"&gt;this&lt;/a&gt; post.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An alternative is to use &lt;a href="https://external-secrets.io/latest"&gt;external secret operator&lt;/a&gt;. Such operators will be able to sync a secret from external secret store such as Hashicorp Vault, AWS secret manager, Azure Key Vault, etc. This is much neater because we only store reference to secret in the repository. However, when the cluster assesses external secret store, it still requires either a secret, or a permission. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another technique that drawing attentions is using &lt;a href="https://github.com/mozilla/sops#encrypting-using-azure-key-vault"&gt;sops&lt;/a&gt; for secret and &lt;a href="https://github.com/viaduct-ai/kustomize-sops#argo-cd-integration"&gt;kustomize-sops&lt;/a&gt; to integrate with ArgoCD. Mozilla&amp;#8217;s &lt;a href="https://github.com/mozilla/sops#encrypting-using-azure-key-vault"&gt;sops&lt;/a&gt; (SecretOPerationS) project releases a binary utility called sops to encrypt and decrypt YAML manifest. It can encrypt only the values in YAML manifest and not the attributes (keys). We use sops to encrypt YAML files in order to store them in Git repo. We also use sops to decrypt YAML right before we deploy it with kubectl. The key pair for encryption and decryption can be stored in Azure Key Vault, AWS KMS, GCP KMS, and even &lt;a href="https://github.com/FiloSottile/age"&gt;age&lt;/a&gt; and pgp. &lt;a href="https://www.thorsten-hans.com/encrypt-your-kubernetes-secrets-with-mozilla-sops"&gt;Here&lt;/a&gt; is a simple tutorial. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Example&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a few command we can configure a minimal example. I first use KinD or Minikube to create a cluster following the steps in &lt;a href="https://github.com/digihunch/real-quicK-cluster"&gt;real-quicK-cluster&lt;/a&gt; repo. Then we configure an application using YAML manifest.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl create namespace argocd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -n argocd -f https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n argocd get secret argocd-initial-admin-secret -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;{.data.password}&amp;#34;&lt;/span&gt; | base64 -d; echo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl port-forward svc/argocd-server -n argocd 8443:443&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From MacOS, I can now browse to https://localhost:8443/ for ArgoCD UI, and login with admin user and the password as printed above. We can then use the UI to create an Application. Alternatively, we can configure an application using CLI utility:&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 argocd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ argocd login localhost:8443&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ argocd app create guestbook --repo https://github.com/argoproj/argocd-example-apps.git --path guestbook --dest-server https://kubernetes.default.svc --dest-namespace default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ argocd app sync guestbook&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;On the GUI we should see the application is sync&amp;#8217;ed. &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="840" height="671" src="https://www.digihunch.com/wp-content/uploads/2022/06/image-8.png" alt="" class="wp-image-5452"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the left panel, click on the gear icon for settings. Under Clusters, we can see the cluster URL, which we use as dest-server value above. We can add more clusters here. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Pick the right open-source project&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;My experience with ArgoCD and FluxCD raised an issue to reflect on. For the same problem, there are multiple choices of open-source project. What would be a good methodology to choose the right open-source technology? I would first look at the following factors:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;History&lt;/li&gt;&#10;&lt;li&gt;Community size&lt;/li&gt;&#10;&lt;li&gt;License model&lt;/li&gt;&#10;&lt;li&gt;Enterprise support&lt;/li&gt;&#10;&lt;li&gt;Governing model&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The history and community size give a good idea of the technical maturity level. The License model has legal implications as to where you can use the technology. License model can change even after the project has been launched and even become not open-source any more. One example is &lt;a href="https://www.elastic.co/blog/why-license-change-aws"&gt;Elasticsearch license change&lt;/a&gt; in Jan 2021, from the very open &lt;a href="https://www.apache.org/licenses/LICENSE-2.0"&gt;APLv2&lt;/a&gt; to under dual license of both &lt;a href="https://www.elastic.co/licensing/elastic-license"&gt;Elastic License&lt;/a&gt; (by Elastic) and &lt;a href="https://en.wikipedia.org/wiki/Server_Side_Public_License"&gt;Server Side Public License&lt;/a&gt; (SSPL, introduced by MongoDB). Neither license is certified by &lt;a href="https://opensource.org/licenses"&gt;OSI&lt;/a&gt; as compliant with &lt;a href="https://en.wikipedia.org/wiki/The_Open_Source_Definition"&gt;open source definition&lt;/a&gt;. For enterprises, apart from licensing, I&amp;#8217;d also consider if any organization provides commercial support for the open-source product. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;What is often neglected is the governing model. The governing model determines how product decisions are made that will shape the future of the project. For example, CNCF is a vendor neutral foundation for cloud computing and if an organization contributes a project to CNCF, then the member projects have to align with CNCF&amp;#8217;s governing practices. Google also introduced an &lt;a href="https://www.infoworld.com/article/3566274/what-is-googles-open-usage-commons-and-why.html"&gt;OUC&lt;/a&gt; (Open Usage Common) governing model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another good way is to just follow Red Hat&amp;#8217;s choice. Red Hat built its business model around open-source technologies. They have to pick open-source project to provide enterprise level support so presumably they have a rigour selection process that one can piggyback off. Products supported by Red Hat are composed of open source components often vetted by multiple upstream communities, and changes made to these components are pushed to their respective upstream projects, often before they land in a supported product from Red Hat. &lt;a href="https://cs.stanford.edu/people/eroberts/cs201/projects/open-source/econ.htm"&gt;Here&lt;/a&gt; are more insights in this business model.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Had I gone with the Red Hat rule, I would have picked Argo CD in the first place because that is the base of &lt;a href="https://docs.openshift.com/container-platform/4.7/cicd/gitops/understanding-openshift-gitops.html"&gt;OpenShift GitOps&lt;/a&gt;! &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Verdict&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ArgoCD and FluxCD are tools for GitOps. However, just using a GitOps tool does not guarantee that all workload are deployed continuously. First, these GitOps controllers cannot replace &lt;a href="https://www.digihunch.com/2022/04/kubernetes-operator/"&gt;Operators&lt;/a&gt;, which has domain knowledge about how to orchestrate their workloads. Second, resources not directly managed by these operators (such as those installed by Helm) are not being monitored continuously. For continuous deployment over all workloads, I recommend use operator to install third party applications, and use Argo CD or Flux CD to manage the operators. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;GitOps makes use of the controller pattern to manage deployment in a continuous matter. Controller works closely with CRDs. ArgoCD uses similar set of CRDs to manage continuous deployment. What sets it apart is the user friendly web UI, the IAM integration and multi-cluster capabilities. These capabilities make ArgoCD well adapted in enterprise IT eco-system.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2022/06/chaos-mesh-cloud-native-chaos-engineering/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous 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;a rel="next" href="https://www.digihunch.com/2022/07/intro-to-ceph-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Ceph storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Knative Eventing Introduction</title><link>https://www.digihunch.com/2022/04/knative-introduction/</link><pubDate>Fri, 29 Apr 2022 10:16:00 -0400</pubDate><guid>https://www.digihunch.com/2022/04/knative-introduction/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-knative-eventing.webp" alt="Featured image of post Knative Eventing Introduction" /&gt;&lt;p class="wp-block-paragraph"&gt;In the previous &lt;a href="https://www.digihunch.com/2022/04/knative-introduction-serving/"&gt;post&lt;/a&gt;, I mentioned that Knative Serving and Knative Eventing should be seen as two different projects. The former is supposed to be widely used as a serving layer for microservices, whereas the latter has a narrower customer base. There are a dozen companies who need to build Platform as a Service, and will benefit from event-driven architecture. They are the niche customer for Knative Eventing. In this post, we go through two demos for Eventing.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-event-driven-architecture"&gt;Event Driven Architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Before jumping into Knative eventing, let&amp;#8217;s first understand the value of event-driven architecture. As covered in Knative &lt;a href="https://www.cncf.io/online-programs/event-driven-architecture-with-knative-events/"&gt;presentation&lt;/a&gt; in 2020, a growing micro-service architecture involves lots of service-to-service communications that looks like a spider web in a diagram. Two services communicating with each other are tightly coupled: changing one requires a change of the other. Event-driven architecture evolves to decouple those interdependent services. Knative eventing tries to standardize the event data with Cloud Event and also proposes a framework to drive this architecture. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The documentation uses different words to express the same concepts. So let&amp;#8217;s take a note first: &lt;strong&gt;source&lt;/strong&gt; = &lt;strong&gt;producer&lt;/strong&gt; which emits events, and &lt;strong&gt;sink&lt;/strong&gt; = &lt;strong&gt;subscriber&lt;/strong&gt; = &lt;strong&gt;consumer&lt;/strong&gt; which is the destination of events. Knative Eventing supports two models of in even-driven architecture.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Brokers and Triggers provides an &amp;#8220;event mesh&amp;#8221;. Event producers delivers events to a Broker. Triggers then distribute them uniformly by consumers. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Broker is a &amp;#8220;hub&amp;#8221; for events. It is a central location to receive and send events for delivery. We can choose from the following broker types:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;MT (multi-tenant) channel-based broker (which a channel implementation, such as Kaka channel or an in-memory channel)&lt;/li&gt;&#10;&lt;li&gt;GCP broker&lt;/li&gt;&#10;&lt;li&gt;Apache Kafka broker&lt;/li&gt;&#10;&lt;li&gt;RabbitMQ broker&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Triggers represents a desire to subscribe to events from a specific broker. It can act as a filter&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://knative.dev/docs/eventing/channels/images/channel-workflow.png" alt="Sources send events to a Channel. The Channel fans out events to Subscriptions. The Subscriptions send events to Sinks."/&gt;&lt;figcaption class="wp-element-caption"&gt;channel-subscriber model&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Channels and Subscriptions provide a &amp;#8220;event pipe&amp;#8221; model which transforms and routes events between Channels using Subscriptions. This model makes more sense when events out of one system needs to be transformed and then routed to another process.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A channel can be either a generic channel object, or a custom channel implementation (such as Kafka channel or in-memory channel)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The subscription consists of a Subscription object, which specifies the Channel and the Sink (aka the Subscriber) to deliver events to. You can also specify some Sink-specific options, such as how to handle failures.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The key difference is Broker-Trigger model allows you to filter events, where as Channel-Subscription model allows you to transform events.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that a Knative Service can act as both a Source and a Sink for events, and for good reason. You may want to consume events from the Broker and send modified events back to the Broker, as you would in any pipeline use-case. Between these components, Knative Eventing uses&amp;nbsp;&lt;a href="https://github.com/cloudevents/spec/blob/v1.0.1/primer.md"&gt;CloudEvents&lt;/a&gt;&amp;nbsp;to send information back and forth. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-knative-eventing-lab"&gt;Knative Eventing Lab&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can create a lab cluster using Kind as per the &lt;a href="https://github.com/digihunch/real-quicK-cluster#kind"&gt;instruction &lt;/a&gt;in real-quicK-cluster. In &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/#settings-for-docker-desktop"&gt;docker-desktop settings&lt;/a&gt;, I configured 6 CPU and 8GB memory as the lab involves a number of nodes. The creation takes a couple minutes. This Knative eventing lab has four paths one can take, based on different model and different implementations:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Lab A demonstrates the configuration of broker-trigger model using Kafka based broker.&lt;/li&gt;&#10;&lt;li&gt;Lab B demonstrates the configuration of channel-subscription model, using in-memory channel.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The two labs share some common steps. We first install Knative Eventing using the provided manifests:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/eventing-crds.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/eventing-core.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get pods -n knative-eventing&#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 should come to READY fairly quickly. In the mean time, we can configure the consumer as well using the manifests 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:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Namespace&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;apps&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Deployment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/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;replicas&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;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchLabels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;template&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;gcr&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;knative&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;releases&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;event_display&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;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;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ports&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;protocol&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;targetPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;apps&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Deployment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/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;replicas&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;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchLabels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;template&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;gcr&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;knative&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;releases&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;event_display&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;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;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ports&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;protocol&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;targetPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The consumers (aka sinks) will be able to process events at the validation step at the end.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-lab-a-install-kafka-and-configure-kafka-based-broker"&gt;Lab A &amp;#8211; Install Kafka and configure Kafka-based Broker&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This lab is for broker-trigger model. We first configure a &lt;a href="https://knative.dev/docs/eventing/broker/kafka-broker/"&gt;Kafka Broker&lt;/a&gt;, which requires a Kafka cluster. The document uses &lt;a href="https://strimzi.io/quickstarts/"&gt;Strimzi operator&lt;/a&gt; to quickly install Kafka cluster. Instead, here we use Helm to install the Kafka 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;helm install my-cluster-kafka bitnami/kafka -n kafka --create-namespace --set volumePermissions.enabled&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true --set replicaCount&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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;At the end of installation, the Helm notes indicate that the bootstrap server is &lt;em&gt;my-cluster-kafka.kafka.svc.cluster.local:9092&lt;/em&gt;. All Kafka pods should come ready in about 5 minutes. After cluster install, we need to install Kafka controller and the Broker-layer data plane which allow you to map a Kafka instance to Broker CRD later:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/knative-sandbox/eventing-kafka-broker/releases/download/knative-v1.3.0/eventing-kafka-controller.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/knative-sandbox/eventing-kafka-broker/releases/download/knative-v1.3.0/eventing-kafka-broker.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get pods -n knative-eventing&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n knative-eventing edit cm kafka-broker-config&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;As the command above suggests, once data plane is installed, we edit the default broker config by updating the bootstrap.servers with the bootstrap server address noted above. We also need to make sure the replication factor is no greater than number of partitions, which should not be a problem in our lab as the replica count was set to 3. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we can create a Broker in the same namespace with consumer:&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;apiVersion: eventing.knative.dev/v1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind: Broker&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;metadata:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; namespace: event-example&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; annotations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; eventing.knative.dev/broker.class: Kafka&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;spec:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apiVersion: v1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kind: ConfigMap&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: kafka-broker-config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; namespace: knative-eventing&#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 manifest above is applied, we should see the status of broker (named default) to be READY. We can also validate broker creation with kn 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 -n event-example get broker default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME URL AGE READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;default http://kafka-broker-ingress.knative-eventing.svc.cluster.local/event-example/default 19m True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn broker list -n event-example&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME URL AGE CONDITIONS READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;default http://kafka-broker-ingress.knative-eventing.svc.cluster.local/event-example/default 19m &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; OK / &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; True&#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 create Triggers that connects both to the broker with filters, and to the consumers by referencing service name. The manifest to create triggers is provided 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:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Trigger&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/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;broker&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;filter&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;attributes&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;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;greeting&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&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;ref&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;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/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;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Trigger&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/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;broker&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;filter&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;attributes&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;source&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sendoff&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&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;ref&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;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/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;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can confirm the setups above by monitoring the resources (like we did for broker), or use knative 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;$ kn trigger list -n event-example&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME BROKER SINK AGE CONDITIONS READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;goodbye-display default service:goodbye-display 3m24s &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; OK / &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-display default service:hello-display 3m24s &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; OK / &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; True&#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 go to the validation step to send event and validate result.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-lab-b-install-in-memory-channel-and-configure-channel"&gt;Lab B &amp;#8211; Install in-memory channel and configure Channel&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This lab is for channel-subscription model. we first install an in-memory channel:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/in-memory-channel.yaml&#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 map this channel to a Channel resource in Eventing. We can do so by applying the manifest 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:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&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;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&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;The creation of Channel resources can be verified 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;$ kn -n event-example channel list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME TYPE URL AGE READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-channel-1 InMemoryChannel http://demo-channel-1-kn-channel.event-example.svc.cluster.local 6m34s True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-channel-2 InMemoryChannel http://demo-channel-2-kn-channel.event-example.svc.cluster.local 6m34s True&#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 need to create Subscription which connects Channels to Consumers. Subscriptions can be created with the manifest 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:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Subscription&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;subscription&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;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/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;channel&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;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/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;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&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;subscriber&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;ref&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;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/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;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Subscription&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;subscription&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/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;channel&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;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/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;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&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;ref&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;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/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;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can confirm subscription:&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;$ kn -n event-example subscription list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME CHANNEL SUBSCRIBER REPLY DEAD LETTER SINK READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-subscription-1 Channel:demo-channel-1 service:hello-display True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-subscription-2 Channel:demo-channel-2 service:goodbye-display True&#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 go to validation step.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-validation"&gt;Validation&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For validation, we need to first configure an event source. The source in our lab is a throw-away Pod with curl utility. I use my favourite &lt;a href="https://github.com/nicolaka/netshoot"&gt;nicolaka netshoot&lt;/a&gt; to fire off the events using. To launch the Pod and get to command shell:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl run tmp-shell -n event-example --rm -i --tty --image nicolaka/netshoot -- /bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;If you don&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;t see a command prompt, try pressing enter.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bash-5.1# &#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 POST message to the corresponding endpoint URL to fire events. The endpoint is different for each lab:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lab&lt;/th&gt;&lt;th&gt;URL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;A (Kafka-based broker)&lt;/td&gt;&lt;td&gt;http://kafka-broker-ingress.knative-eventing.svc.cluster.local/event-example/default&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;B (channel)&lt;/td&gt;&lt;td&gt;http://demo-channel-1-kn-channel.event-example.svc.cluster.local&lt;br&gt;http://demo-channel-2-kn-channel.event-example.svc.cluster.local&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With the correct URL, we can build a payload for POST method. Below is three curl commands with different payload POST to the same endpoint (taking lab A as an 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;display:grid;"&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;# curl -v &amp;#34;http://broker-ingress.knative-eventing.svc.cluster.local/event-example/default&amp;#34; \&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-X POST &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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Id: say-hello&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Specversion: 1.0&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Type: greeting&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Source: not-sendoff&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; background-color:#3c3d38"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&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; background-color:#3c3d38"&gt;&lt;span&gt;-d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;msg&amp;#34;:&amp;#34;Hello Knative!&amp;#34;}&amp;#39;&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;# curl -v &amp;#34;http://broker-ingress.knative-eventing.svc.cluster.local/event-example/default&amp;#34; \&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-X POST &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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Id: say-goodbye&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Specversion: 1.0&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Type: not-greeting&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Source: sendoff&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; background-color:#3c3d38"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&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; background-color:#3c3d38"&gt;&lt;span&gt;-d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;msg&amp;#34;:&amp;#34;Goodbye Knative!&amp;#34;}&amp;#39;&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;# curl -v &amp;#34;http://broker-ingress.knative-eventing.svc.cluster.local/event-example/default&amp;#34; \&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-X POST &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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Id: say-hello-goodbye&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Specversion: 1.0&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Type: greeting&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;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Source: sendoff&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; background-color:#3c3d38"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&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; background-color:#3c3d38"&gt;&lt;span&gt;-d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;msg&amp;#34;:&amp;#34;Hello Knative! Goodbye Knative!&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;All the requests should receive 202 return code. As highlighted above, we specified type and source for each event fired to the broker. On the consumer side, we can simply check the pod log to verify the events received. Below is an example of events received as a result of the events sent above (take broker-trigger model as 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;$ kubectl -n event-example logs -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hello-display --tail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2022/03/09 17:05:59 Failed to read tracing config, using the no-op default: empty json tracing config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: not-sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:19.196589801Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hello Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-hello-goodbye&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:42.977803608Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hello Knative! Goodbye Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n event-example logs -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;goodbye-display --tail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2022/03/09 17:05:58 Failed to read tracing config, using the no-op default: empty json tracing config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: not-greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-goodbye&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:27.430702588Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Goodbye Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-hello-goodbye&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:42.977803608Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hello Knative! Goodbye Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#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 the gist of Knative evening. Note that the format of HTTP payload when we fire off events needs to conform to &lt;a href="https://cloud.google.com/eventarc/docs/cloudevents"&gt;CloudEvents format&lt;/a&gt;. The container image (&lt;a href="https://github.com/knative/eventing/tree/main/cmd/event_display"&gt;event_display&lt;/a&gt;) used in the container is also created for the purpose of processing events received and display them in stdout.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Persistent storage allows for stateful applications on Kubernetes. It also enables architectural patterns that requires persistent storage. Knative eventing is a framework that enables event driven architecture. It supports many &lt;a href="https://knative.dev/docs/eventing/channels/channels-crds/"&gt;messaging channels&lt;/a&gt; (including &lt;a href="https://nats.io/"&gt;NATs&lt;/a&gt;, a cloud-native messaging system) and &lt;a href="https://knative.dev/docs/eventing/broker/"&gt;broker&lt;/a&gt; types (MT channel based, GCP, RabbitMQ). &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2022/04/knative-introduction-serving/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Serving Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2022/05/fsx-ontap-enterprise-storage-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;FSx ONTAP – Enterprise storage on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Knative Serving Introduction</title><link>https://www.digihunch.com/2022/04/knative-introduction-serving/</link><pubDate>Sun, 17 Apr 2022 11:33:00 -0400</pubDate><guid>https://www.digihunch.com/2022/04/knative-introduction-serving/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-knative-serving.webp" alt="Featured image of post Knative Serving Introduction" /&gt;&lt;h2 class="wp-block-heading" id="h-background"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As per &lt;a href="https://www.ibm.com/cloud/learn/knative"&gt;IBM&lt;/a&gt;&amp;#8216;s definition, Knative enables serverless workloads to run on Kubernetes clusters, and makes building and orchestrating containers with Kubernetes faster and easier. It has drawn a lot of attention recently. It released version 1.0 in November 2021, and was accepted as a &lt;a href="https://www.cncf.io/blog/2022/03/02/knative-accepted-as-a-cncf-incubating-project/"&gt;CNCF incubating&lt;/a&gt; project in March 2022. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Glories aside, the value it delivers is the ability to go serverless on a Kubernetes platform. Originally, &lt;a href="https://cloud.google.com/knative"&gt;Knative&lt;/a&gt; was built with three components: build, serving and eventing. The &lt;a href="https://github.com/knative/build"&gt;build&lt;/a&gt; component was deprecated in favour of the &lt;a href="https://tekton.dev/"&gt;Tekton&lt;/a&gt; project. Tekton is cloud native CI/CD pipeline. It connects to source code repo and build artifacts. It can also deploy applications with pipeline as code. Tekton&amp;#8217;s documentation includes a quality tutorial &lt;a href="https://tekton.dev/docs/getting-started/"&gt;here&lt;/a&gt; linked to interactive terminal. On the other hand, we&amp;#8217;ll focus on Knative Serving in this post, and Knative Eventing in the &lt;a href="https://www.digihunch.com/2022/04/knative-introduction/"&gt;next&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://knative.dev/docs/"&gt;Knative&lt;/a&gt; can be seen as a serverless framework with a number of open-source technologies as building blocks, such as Istio for ingress gateway, Kafka or Google Pub/Sub as event-streaming engine, and Prometheus for observability to name a few. Despite of using the same CLI tool (named &lt;a href="https://knative.dev/docs/install/"&gt;kn&lt;/a&gt;), Knative Serving and Eventing are considered separate capabilities using different groups of CRDs. They are installed separately, using their respective &lt;a href="https://knative.dev/docs/install/yaml-install/"&gt;YAML&lt;/a&gt; files, or &lt;a href="https://knative.dev/docs/install/operator/knative-with-operators/"&gt;operator&lt;/a&gt;. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-serving-vs-eventing"&gt;Serving vs Eventing ?&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After reading the article &amp;#8220;&lt;a href="https://ahmet.im/blog/knative-positioning/"&gt;Did we market Knative wrong&lt;/a&gt;&amp;#8221; from Ahmet Balkan, my impression is the two are not related. They could have been two separate projects without sharing a common name. As the author puts, these two shared some core logics. But beyond that, they don&amp;#8217;t have anything in common. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Serving is lightweight and requires Istio. It provides the capability to scale from N to 0 when the system is idle, and from 0 to N when requests come in. It is the missing serving layer for running microservices on Kubernetes and Ahmet position it as the first thing that people installed after creating a cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The target user of Eventing is much smaller. Eventing is for event-driven architecture and is more complex than Serving. Ahmet admits that they over-estimated how many people on the planet want to build a Heroku-like PaaS layer on top of Knative. There are a couple of dozen companies who would work through the complexity and build their own Kubernetes-based internal PaaS or even public-facing FaaS using Knative. They are the niche audience of Knative Eventing. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For most platform builders who just need to run micro-service, Knative Serving is all they need.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-a-demo-on-serving"&gt;A Demo on Serving&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The definition of term &amp;#8220;serverless&amp;#8221; is very loose. Instead, Knative documentation promotes the ability to &lt;a href="https://knative.dev/docs/getting-started/first-autoscale/#watch-your-knative-service-scale-to-zero"&gt;scale to zero&lt;/a&gt; and refer to it as &amp;#8220;some people call this Serverless&amp;#8221;. This suggests that &amp;#8220;the ability to scale to zero&amp;#8221; should be the better term to describe this capability. To deploy it, we need the Service object with apiVersion serving.knative.dev/v1. Below is a guide for a quick hands-on, with some steps modified from the Knative Serving &lt;a href="https://knative.dev/docs/getting-started/first-service/"&gt;tutorial&lt;/a&gt; and installation &lt;a href="https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/"&gt;guide&lt;/a&gt;, with optional steps (extensions, DNS) skipped.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Follow the first part of &lt;a href="https://www.digihunch.com/2021/11/istio-ingress-egress/"&gt;this post&lt;/a&gt; or &lt;a href="https://github.com/digihunch/real-quicK-cluster#minikube"&gt;this guide&lt;/a&gt; in real-quicK-cluster project, to install minikube with metal LB and configure istio (using istioctl as per instruction). Then Knative serving can be installed with two YAML manifests:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-crds.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-core.yaml&#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 install Knative istio controller to integrate them so we can see the knative gateways:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.2.0/net-istio.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n knative-serving get gateway&#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 install the dummy service, either &lt;a href="https://knative.dev/docs/getting-started/first-service/#knative-service-hello-world"&gt;using kn CLI tool or using YAML manifest&lt;/a&gt;. The manifest is given 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-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;serving.knative.dev/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hello&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;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;template&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hello-world&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;annotations&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;autoscaling.knative.dev/class&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;kpa.autoscaling.knative.dev&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/metric&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;rps&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/target&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;50&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/min-scale&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/max-scale&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containerConcurrency&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;env&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TARGET&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;value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;World&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gcr.io/knative-samples/helloworld-go&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;user-container&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can notice that in the annotations there are some settings to overwrite the default configuration. They are explained in the &lt;a href="https://knative.dev/docs/serving/autoscaling/#additional-resources"&gt;autoscaling&lt;/a&gt; section of the Knative serving documentation. Here we use request per second (rps) as metric, and have 50 as target. The scale range is between 3 and 10 replicas. Once the resource is created, we can validate it with one of the two 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;$ kubectl get services.serving.knative.dev&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn service describe hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The creation of knative service also created a number of Kubernetes resources, such as deployment and services:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get svc&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME TYPE CLUSTER-IP EXTERNAL-IP PORT&lt;span style="color:#f92672"&gt;(&lt;/span&gt;S&lt;span style="color:#f92672"&gt;)&lt;/span&gt; AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello ExternalName &amp;lt;none&amp;gt; knative-local-gateway.istio-system.svc.cluster.local 80/TCP 52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world ClusterIP 10.110.127.231 &amp;lt;none&amp;gt; 80/TCP 108s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-private ClusterIP 10.111.27.73 &amp;lt;none&amp;gt; 80/TCP,9090/TCP,9091/TCP,8022/TCP,8012/TCP 108s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubernetes ClusterIP 10.96.0.1 &amp;lt;none&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get deploy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY UP-TO-DATE AVAILABLE AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 71s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Before testing, let&amp;#8217;s mock the DNS entry to resolve to the Istio ingress service IP like below in /etc/hosts:&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;192.168.64.16 hello.default.example.com&#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 confirm that the service returns the expected result (after a brief pause): &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 hello.default.example.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hello World!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Wait for a short period without curl or connection from browser, the service should scale down to zero:&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 deploy -l serving.knative.dev/service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hello -w&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY UP-TO-DATE AVAILABLE AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/0 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 78s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From a separate terminal, emulate HTTP request with &lt;a href="https://github.com/rakyll/hey"&gt;hey&lt;/a&gt; command, so the service receives more than 50 rps per pod:&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;$ hey -n &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; -z 20s -m GET http://hello.default.example.com&#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 watch the deployment get scaled up to 10. Once the hey command completes its task, the deployment will scale back down:&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 deploy -l serving.knative.dev/service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hello -w&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY UP-TO-DATE AVAILABLE AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/0 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 78s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m2s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m4s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m4s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m4s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m5s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 4m9s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 3/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 4/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 7/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 9/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 10/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 4m11s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 10/5 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 5m18s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 10/5 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 5m18s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 5/5 &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 5m18s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 5/2 &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 5m20s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 5/2 &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 5m20s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/2 &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 5m20s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/1 &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 5m22s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/1 &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 5m22s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 5m22s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/0 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 5m52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/0 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 5m52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/0 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 5m52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Autoscaling in Knative serving is backed by KPA (&lt;a href="https://knative.dev/docs/serving/autoscaling/"&gt;Knative Pod Autoscaler&lt;/a&gt;, used in the example above) or HPA. Also note that the trigger to scale up and down is client connection (RPS). This is different than KEDA. KEDA also supports the capability to scale to zero. However, with KEDA, it is an event that scales a deployment up from zero. In Knative serving, it is the connection to the Service itself that wakes up the service. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-cost-of-scale-to-zero"&gt;Cost of scale-to-zero&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As discussed, both KEDA and Knative serving supports the ability to scale to zero and wake up from zero. The trigger to wake up from zero is different. With KEDA, workload wakes up by an event. With Knative Serving, workload has to wake up upon receiving a connection to the service. In the design to solve this problem, &lt;a href="https://knative.dev/docs/serving/"&gt;Knative&lt;/a&gt; has four sub-components :&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Activator &amp;#8211; When a service is scaled to zero, its request are routed to the&amp;nbsp;&lt;em&gt;activator&lt;/em&gt;&amp;nbsp;which waits for a Pod to become ready (wake up) and proxies the traffic while editing the underlying&amp;nbsp;&lt;code&gt;Endpoints&lt;/code&gt;&amp;nbsp;to route the traffic directly to the Pod(s)&lt;/li&gt;&#10;&lt;li&gt;Autoscaler &amp;#8211; KPA&lt;/li&gt;&#10;&lt;li&gt;Controller &amp;#8211; The main component responsible for watching API objects for Knative CRDs (KService, Configuration, Route, Revision) and manage their lifecycle, create the underlying Kubernetes resources and garbage-collect old objects.&lt;/li&gt;&#10;&lt;li&gt;Webhook &amp;#8211; a Kubernetes Admission Webhook acting both as validating admission controller and mutating admission controller.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The key player is activator, which receives requests when a service is scaled to zero. The wake-up process is summarized as follows:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;After receiving request, activator will buffer (hold onto) the request&lt;/li&gt;&#10;&lt;li&gt;Then, it will look at request’s hostname to find which KService it is for.&lt;/li&gt;&#10;&lt;li&gt;Then, it will scale up the Kubernetes Deployment and wait for a Pod to become ready.&lt;/li&gt;&#10;&lt;li&gt;In the meanwhile, it updates Kubernetes Service to point to Pod IP addresses (so that activator gets out of the network path if the KService is awake).&lt;/li&gt;&#10;&lt;li&gt;Finally, activator proxies the request to the started pod&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So the &amp;#8220;plumbing&amp;#8221; of the service connection managed by the Activator pod is the cost of scale to zero. This is also called &lt;a href="https://knative.dev/docs/serving/load-balancing/"&gt;Load Balancing&lt;/a&gt;, and the behaviour can be tweaked using two parameters: activator capacity and target burst capacity:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://knative.dev/docs/serving/load-balancing/target-burst-capacity/"&gt;Target burst capacity&lt;/a&gt;: once the target deploy has one more more Pod, service requests may still route via activator or bypassing the activator. Target burst capacity determines at what point service request should start to bypass activator. &lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://knative.dev/docs/serving/load-balancing/activator-capacity/"&gt;Activator capacity&lt;/a&gt;: determines how many requests can activator hold on to. Considering the service-to-service connection also routes via Service CRD, there can be a lot of connections via Activator so its capacity needs to be adjusted.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The additional overhead of managing and optimizing the &amp;#8220;Load Balancer&amp;#8221; is also part of operational cost for the ability to scale to zero.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In comparison, KEDA now has an &lt;a href="https://github.com/kedacore/http-add-on"&gt;HTTP add-on&lt;/a&gt; still at beta but allows connection-based wake-up. The &lt;a href="https://github.com/kedacore/http-add-on/blob/main/docs/design.md"&gt;design&lt;/a&gt; is a little different. As discussed in this &lt;a href="https://www.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;previous &lt;/a&gt;post.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Knative consists of two disparate components: Serving and Eventing. Serving uses KPA to provide scaling based on service request, and the ability to scale to zero. It is often compared with KEDA, a single-purpose lightweight tool for autoscaling. &lt;a href="https://github.com/kedacore/http-add-on/blob/main/docs/faq.md#how-is-this-project-similar-or-different-from-knative"&gt;Here&lt;/a&gt; is a blurb on their difference by KEDA. The takeaways is that KEDA is more focused on scalability, whereas Knative serving covers more aspects. For example, the Service object of Knative also supports &lt;a href="https://knative.dev/docs/getting-started/first-traffic-split/"&gt;Traffic Splitting&lt;/a&gt;. Knative serving can integrate with istio.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2022/04/kubernetes-operator/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Operator&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2022/04/knative-introduction/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Eventing Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Service Proxy – from Nginx to Envoy</title><link>https://www.digihunch.com/2022/03/from-nginx-to-envoy-proxy/</link><pubDate>Wed, 09 Mar 2022 21:36:00 -0400</pubDate><guid>https://www.digihunch.com/2022/03/from-nginx-to-envoy-proxy/</guid><description>&lt;img src="https://www.digihunch.com/wp-content/uploads/2025/04/feature-nginx2envoy.webp" alt="Featured image of post Service Proxy – from Nginx to Envoy" /&gt;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Update&lt;/strong&gt; (Nov 20, 2022): 1. Envoy&amp;#8217;s configuration schema can be hard to get used to. It is lacking examples because the documentation is mostly generated. Use its &lt;a href="https://github.com/envoyproxy/examples"&gt;examples&lt;/a&gt; directory to find real-life configuration examples. 2. the Envoy implementation in the example project has been reverted in favour of Nginx.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Envoy proxy is the underlying technology for Istio, as well as a number of other service mesh products, such as AppMesh (AWS), Consul (Hashicorp) and OpenServiceMesh (Azure). Most of the capabilities of Isito is ultimately provided by Envoy proxy. Envoy has a &lt;a href="https://www.envoyproxy.io/docs/envoy/v1.10.0/intro/comparison"&gt;page&lt;/a&gt; outlining its differences with similar technologies. I decided to take a look into Envoy by replacing Nginx with it.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-rate-limiting-and-circuit-breaker"&gt;Rate limiting and Circuit Breaker&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In most SDLC, it is application developers that create backend APIs or server applications. Most developers specializes in application features, and cannot fathom all the nuances with the TCP/IP network stack. Nginx allows them to push network concerns (non-business features) to a dedicated proxy to handle the dynamics in network connection. Nginx can be configured as both a reverse proxy (handling incoming connection on behalf of the process) and a forward proxy (handling outgoing connection on behalf of the process). This is the prototype of sidecar pattern, an important idea behind service mesh. For example, when a sudden increase in connection to the server-side application, the server process could be either unresponsive (refer to &amp;#8220;the &lt;a href="https://queue.acm.org/detail.cfm?id=1854041"&gt;queuing knee&lt;/a&gt;&amp;#8220;, and &lt;a href="https://en.wikipedia.org/wiki/Little%27s_law"&gt;Little&amp;#8217;s Law&lt;/a&gt;), or OOM killed. When such interruptions are not automatically recovered, a downtime is caused. This traditionally requires some congestion control strategy for TCP/IP queue but two features provided by a network proxy can help circumvent this situation: rate limiting, and circuit breaking. Rate limiting keeps more requests above threshold from entering the queue. Circuit breaker releases downstream pressure by cutting out existing in-queue request. Nginx added both over the years but &lt;a href="https://www.nginx.com/blog/microservices-reference-architecture-nginx-circuit-breaker-pattern/"&gt;Circuit breaker&lt;/a&gt; still remains a premium feature exclusive to Nginx Plus. Envoy on the other hand has them free when it was launched.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Envoy also supports other advanced traffic management such as traffic shaping, and mirroring. It is on top of those features that Istio introduces its own abstraction such as virtual service, destination rules to its users. In that sense, we can think of Istio as a configurator (control plane) for Envoy proxy (data plane), similar to Ansible to Nginx proxy instances.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-dynamic-configuration-via-api"&gt;Dynamic Configuration via API&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I used Nginx previously with traditional environment and loved its flexibility. As the system grows, I started to feel the pain of management overhead. With one of the production system, there were 25 + instances of Nginx each running on a VM and I managed configuration files with Ansible. Ansible pushes out configuration files and triggers a reload from each Nginx instance. In the cloud-native era where Pods are ephemeral, this kind of overhead would snowball to an unmanageable level. Envoy was designed for cloud-native applications, with all these kinds of problems in mind. Envoy has dynamic configuration. The majority of the configurations can be pulled from &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/intro/arch_overview/operations/dynamic_configuration"&gt;xDS API&lt;/a&gt;, or file system. Updating configuration drains connections gracefully without runtime having to reload the file. The idea of centrally managing Nginx instances with Ansible, also evolved into the concept of control plane.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-tls-origination"&gt;TLS origination&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the Orthweb project, I used Nginx to proxy TLS and HTTP traffic, and performed TLS termination on both ports. This is know as TLS offloading. The traffic between the proxy and the upstream service takes place in the clear, even though they do not travel across different network interfaces in most cases. For a true end-to-end encryption, it is helpful to also encrypt the traffic between proxy and upstream server. This requires the capability of securing TCP traffic to upstream server. With Nginx, the ability to &lt;a href="https://dzone.com/articles/nginx-rate-limiting"&gt;secure HTTP traffic to upstream&lt;/a&gt; server is offered in open-source. The ability to &lt;a href="https://docs.nginx.com/nginx/admin-guide/security-controls/securing-tcp-traffic-upstream/"&gt;secure TCP traffic&lt;/a&gt; is available in Nginx Plus, or with self-compiled binary. In Envoy, both are available using the UpstreamTlsContext configuration.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-more-pros"&gt;More pros&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In another &lt;a href="https://www.digihunch.com/2020/01/nginx-as-a-reverse-proxy-for-nifi/"&gt;post&lt;/a&gt;, I also discussed Nginx as a LDAP proxy to front services such as Kibana and Nifi. It requires a proxy service (ldap-auth in this case), to defer auth to third party. Envoy has this capability using a &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/configuration/http/http_filters/ext_authz_filter"&gt;filter&lt;/a&gt; with extension for external authorization. Istio also exposes this capability, an enabler for the configuration proposed in my previous &lt;a href="https://www.digihunch.com/2022/02/istio-external-authorization/"&gt;post&lt;/a&gt;. Envoy also uses WebAssembly for its extensibility.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another useful feature is protocol detection. It can use filters to detect protocol (TLS or regular TCP) and route traffic to predefined destination.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Performance wise, this &lt;a href="https://www.loggly.com/blog/benchmarking-5-popular-load-balancers-nginx-haproxy-envoy-traefik-and-alb/"&gt;benchmark&lt;/a&gt; from 2018 ran a comparison among the popular options where Envoy leads by a margin.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Observability (logging, metrics and tracing) are well supported in Envoy. User can configure format of logs that takes effect immediately. There are many metrics that works with Prometheus and they are expandable using filters. On the tracing side, Envoy supports integration with jaeger, zipkin and datadog.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-basics-of-envoy"&gt;Basics of Envoy&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The configuration of Envoy is more involving. There is an Envoy course by &lt;a href="https://academy.tetrate.io/"&gt;Tetrate&lt;/a&gt;, as well as two blog entries for envoy 101: Envoy as &lt;a href="https://www.tetrate.io/blog/envoy-101-configuring-envoy-as-a-gateway/"&gt;gateway proxy&lt;/a&gt; and File-based &lt;a href="https://www.tetrate.io/blog/envoy-101-file-based-dynamic-configurations/"&gt;dynamic configuration&lt;/a&gt;. Another good way to get started is the Sandboxes &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/start/sandboxes/#start-sandboxes"&gt;projects&lt;/a&gt;, which covers a number of different areas of configuration. The admin port (by default at port 9901. Istio&amp;#8217;s default is 15000) provides helpful information. If we need to turn on debug on some features, we can do so with curl:&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 -X POST http://localhost:9901/logging?client&lt;span style="color:#f92672"&gt;=&lt;/span&gt;debug&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Stats are exposed at the same port:&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 -X GET http://localhost:9901/stats&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When packets are received at a listener, the is first processed by listener filters. Then, depending on filter match, one or more network filter chains will further process the packet, including further actions, as illustrated below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="726" src="https://www.digihunch.com/wp-content/uploads/2024/07/envoy-listener-filters-1024x726.png" alt="" class="wp-image-11357" srcset="https://www.digihunch.com/wp-content/uploads/2024/07/envoy-listener-filters-1024x726.png 1024w, https://www.digihunch.com/wp-content/uploads/2024/07/envoy-listener-filters-300x213.png 300w, https://www.digihunch.com/wp-content/uploads/2024/07/envoy-listener-filters-768x544.png 768w, https://www.digihunch.com/wp-content/uploads/2024/07/envoy-listener-filters-1536x1089.png 1536w, https://www.digihunch.com/wp-content/uploads/2024/07/envoy-listener-filters.png 1920w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Envoy supports dynamic configuration, which uses a set of discovery services (xDS) APIs. Some of the important xDS APIs include:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;LDS (Listener Discovery Service) &amp;#8211; allows you to add listeners dynamically while Envoy is running&lt;/li&gt;&#10;&lt;li&gt;RDS (Route Discovery Service) &amp;#8211; allows you to dynamically update routes for HTTP connection managers&lt;/li&gt;&#10;&lt;li&gt;CDS (Cluster Discovery Service) &amp;#8211; allows you to update cluster definitions dynamically&lt;/li&gt;&#10;&lt;li&gt;EDS (Endpoint Discovery Service) &amp;#8211; allows you to add or remove endpoints dynamically&lt;/li&gt;&#10;&lt;li&gt;Secret DS&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The relation can be illustrated in this diagram below:&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://i0.wp.com/www.tetrate.io/wp-content/uploads/2020/11/Screen-Shot-2020-11-18-at-12.17.08-PM-1.png?resize=1044%2C638&amp;amp;ssl=1" alt="Envoy - xDS configuration API overview"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This &lt;a href="https://www.tetrate.io/blog/envoy-101-file-based-dynamic-configurations/"&gt;post&lt;/a&gt; from Tetrate has more examples.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-nginx-to-envoy"&gt;Nginx to Envoy &lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For the advantages of Envoy, I decided to migrate from Nginx to Envoy on my &lt;a href="https://github.com/digihunch/orthweb"&gt;Orthweb&lt;/a&gt; project. Using Envoy as service proxy is not where Envoy is mostly used (as sidecar), but it is how Envoy was originally used at Lyft to replace ELB in 2015.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The original Nginx configuration was referenced in this &lt;a href="https://www.digihunch.com/2020/11/medical-imaging-web-server-deployment-pipeline/"&gt;old blog post&lt;/a&gt;. The Envoy setup also covers both TCP (&lt;a href="https://www.digihunch.com/2020/11/how-imaging-devices-talk-to-each-other-tip-in-dicom/"&gt;DICOM&lt;/a&gt;) and HTTP (HTTPS) traffic. For HTTP traffic, it also encrypts the traffic to upstream. Below is what it looks like:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;admin&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;address&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;socket_address&lt;/span&gt;: { &lt;span style="color:#f92672"&gt;address&lt;/span&gt;: &lt;span style="color:#f92672"&gt;0.0.0.0, port_value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;9901&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;static_resources&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;listeners&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https_listener&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;address&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;socket_address&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;address&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0.0.0.0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port_value&lt;/span&gt;: &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; &lt;span style="color:#f92672"&gt;filter_chains&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;filters&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy.filters.network.http_connection_manager&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;typed_config&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;&amp;#34;@type&amp;#34;: &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager&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;codec_type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;AUTO&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;stat_prefix&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ingress_http&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;route_config&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;local_route&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;virtual_hosts&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;app&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;domains&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#e6db74"&gt;&amp;#34;*&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;routes&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;match&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;prefix&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;/&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;route&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;cluster&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service-https&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;http_filters&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy.filters.http.router&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;transport_socket&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy.transport_sockets.tls&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;typed_config&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;&amp;#34;@type&amp;#34;: &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext&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;common_tls_context&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;tls_certificates&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;certificate_chain&lt;/span&gt;: {&lt;span style="color:#f92672"&gt;&amp;#34;filename&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/etc/ssl/certs/site.pem&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;private_key&lt;/span&gt;: {&lt;span style="color:#f92672"&gt;&amp;#34;filename&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/etc/ssl/certs/site.pem&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;dicomtls_listener&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;address&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;socket_address&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;address&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0.0.0.0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port_value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;11112&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;filter_chains&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;filters&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy.filters.network.tcp_proxy&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;typed_config&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;&amp;#34;@type&amp;#34;: &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy&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;stat_prefix&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;downstream_cx_total&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;cluster&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service-dicomtls&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;transport_socket&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy.transport_sockets.tls&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;typed_config&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;&amp;#34;@type&amp;#34;: &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext&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;common_tls_context&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;tls_certificates&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;certificate_chain&lt;/span&gt;: {&lt;span style="color:#f92672"&gt;&amp;#34;filename&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/etc/ssl/certs/site.pem&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;private_key&lt;/span&gt;: {&lt;span style="color:#f92672"&gt;&amp;#34;filename&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/etc/ssl/certs/site.pem&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;validation_context&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;allow_expired_certificate&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:#f92672"&gt;trusted_ca&lt;/span&gt;: {&lt;span style="color:#f92672"&gt;&amp;#34;filename&amp;#34;: &lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;/etc/ssl/certs/site.pem&amp;#34;&lt;/span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;require_client_certificate&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:#f92672"&gt;clusters&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service-https&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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;STRICT_DNS&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;lb_policy&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ROUND_ROBIN&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;load_assignment&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;cluster_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service-https&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;endpoints&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;lb_endpoints&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;endpoint&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;address&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;socket_address&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;address&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthanc-backend&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port_value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8042&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;transport_socket&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;envoy.transport_sockets.tls&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;typed_config&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;&amp;#34;@type&amp;#34;: &lt;/span&gt;&lt;span style="color:#ae81ff"&gt;type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.UpstreamTlsContext&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service-dicomtls&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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;STRICT_DNS&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;lb_policy&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ROUND_ROBIN&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;load_assignment&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;cluster_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;service-dicomtls&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;endpoints&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;lb_endpoints&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;endpoint&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;address&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;socket_address&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;address&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthanc-backend&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;port_value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;4242&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;layered_runtime&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;layers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;static_layer_0&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;static_layer&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;envoy&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;resource_limits&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;listener&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;https_listener&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;connection_limit&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1000&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;overload&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;global_downstream_max_connections&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;5000&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;From Nginx to Envoy, to achieve nearly the same functionalities, it takes 100 lines of configuration instead of less than 30. The configuration also appears more abstract, which is one of the cons of Envoy to consider before the migration.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Update from Jan 2025 &amp;#8211; To align with what most uses in Orthanc community, the project switched back to using Nginx. The envoy configuration is kept &lt;a href="https://gist.github.com/digihunch/e3192481f0a54e018442d9629562d40f"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2022/02/istio-external-authorization/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Istio External Authorization via OIDC&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2022/03/istio-operation-gotchas/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Istio Operation Gotchas&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Certified Kubernetes Administrator (CKA) Exam</title><link>https://www.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/</link><pubDate>Fri, 30 Apr 2021 09:50:00 -0400</pubDate><guid>https://www.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://www.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://www.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://www.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://www.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://www.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>Docker components</title><link>https://www.digihunch.com/2020/10/docker-under-the-hood/</link><pubDate>Wed, 28 Oct 2020 20:23:00 -0400</pubDate><guid>https://www.digihunch.com/2020/10/docker-under-the-hood/</guid><description>&lt;p class="wp-block-paragraph"&gt;The previous &lt;a href="https://www.digihunch.com/2020/08/virtualization-3-of-3-containers/" class="rank-math-link"&gt;post&lt;/a&gt; about virtualization and containerization brought up some underlying technologies which Docker build containers on, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;namespaces &amp;#8211; a Linux kernel mechanism to isolate resources. It allows a process to run within an isolated environment (mnt, pid, net, ipt, uts, user, cgroup)&lt;/li&gt;&#10;&lt;li&gt;cgroups &amp;#8211; a Linux kernel mechanism to limit resource usage of a process or process group&lt;/li&gt;&#10;&lt;li&gt;unionFS (this will be further discussed under Docker storage)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post we further discuss the components in Docker, the dominant and popular player in container technology, as shown in the diagram below:&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="629" src="https://www.digihunch.com/wp-content/uploads/2024/07/docker-component-1024x629.png" alt="" class="wp-image-11422" srcset="https://www.digihunch.com/wp-content/uploads/2024/07/docker-component-1024x629.png 1024w, https://www.digihunch.com/wp-content/uploads/2024/07/docker-component-300x184.png 300w, https://www.digihunch.com/wp-content/uploads/2024/07/docker-component-768x472.png 768w, https://www.digihunch.com/wp-content/uploads/2024/07/docker-component-1536x943.png 1536w, https://www.digihunch.com/wp-content/uploads/2024/07/docker-component.png 1938w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The component names can be seen under docker install directory. It consists of three groups:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Docker related: docker, dockerd, docker-init and docker-proxy&lt;/li&gt;&#10;&lt;li&gt;Containerd related: containerd, containerd-shim and ctr&lt;/li&gt;&#10;&lt;li&gt;Container runtime: runc&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we discuss each group:&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Docker-related components&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;docker is just an implementation of docker client, it supports commands to achieve all functions between client and server. Alternatively, user may use REST API, or Docker SDK to communicate with Docker server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;dockerd is the server process, to receive requests from docker (client), SDK library or REST API caller. It executes the request and returns status to client. There are three ways for docker (client) to communicate with dockerd.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;By Unix Socket (unix://socket_path). The default socket path used by dockerd is /var/run/docker.sock, which is why only root can use docker after installation.&lt;/li&gt;&#10;&lt;li&gt;TCP request (tcp://host:port). It is recommended to configure TLS communication in production environment.&lt;/li&gt;&#10;&lt;li&gt;By file descriptor (fd://) used in systemd service.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Unix socket is the default communication method. To allow remote access to dockerd, use -H to specify HOST and PORT when starting dockerd.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;docker-init is used by Docker as PID 1 process for containers, in case it needs to recycle zombie containers. To use this, specify &amp;#8211;init when running container.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;docker-proxy is used for port mapping. When you use -p switch with docker run, this docker-proxy is the service that maps the container port to host port. It does so by modifying the iptables nat rule.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Containerd related components&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;containerd component was separated from dockerd since Docker 1.11, in compliance with OCI standard. It is responsible for life cycle management of containers, it also manages images (e.g. pulling from repo), request from dockerd to call runc, storage and network resources.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;dockerd uses UNIX socket to send request to containerd. The default socket path for containerd is /run/containerd/containerd.sock. containerd execute the task and return status to dockerd. You may also directly use containerd to manage containers.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ctr (containderd-ctr) is the client of containerd, mostly used only in development and testing. If the environment does not have dockerd, then you can use ctr as client, to send request directly to containerd.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;containerd-shim is used to decouple containerd from the containers. containerd-shim is the parent process of containers. This is so that restarting containerd does not impact the running containers.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Container runtime&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;runc is a standard implementation of OCI container runtime. It is a command-line tool to create and run containers.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Host legacy application in Docker 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/11/docker-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Host legacy application in Docker 2 of 2</title><link>https://www.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/</link><pubDate>Thu, 22 Oct 2020 17:54:00 -0400</pubDate><guid>https://www.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;My &lt;a href="https://www.digihunch.com/2020/09/host-legacy-application-with-docker-compose/"&gt;previous notes &lt;/a&gt;include some tricks in hosting legacy application in docker. This is a continuation from that work, after 1.5 months&amp;#8230;&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Use Case&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I decided to use docker to host application for a good reason, and let me start with what this Java-based application does as a single process. When it is up it listens to more than 70 TCP ports for different business services. Here is a simplified list:&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;Application service&lt;/td&gt;&lt;td&gt;TCP port to bind&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Business service A&lt;/td&gt;&lt;td&gt;8030&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Business service B&lt;/td&gt;&lt;td&gt;8040&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Business service C&lt;/td&gt;&lt;td&gt;8050&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;#8230;&amp;#8230;&lt;/td&gt;&lt;td&gt;&amp;#8230;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;TCP port requirement&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The application also communicates with database and search engine on the same server. Since I am building a training environment where multiple instances of our application needs to run on a single server host. All these instances of application share the same underlying database and search engine services. With multiple instances, additional constraints are introduced. For example:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Each instance requires more than 120 configuration files. A small number of them defines what ports the process binds to. The rest of configuration files are the same across all instances.&lt;/li&gt;&#10;&lt;li&gt;The OS needs to host 6 processes of the same application all running at the same time;&lt;/li&gt;&#10;&lt;li&gt;The OS does not allow multiple processes to bind to a single TCP port (duh!);&lt;/li&gt;&#10;&lt;li&gt;It is extremely labourious to change the path for application to read configuration files from. This bad configuration also breaks the upgrade process going forward. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From the statements of constraints, I determine that we need a mechanism to bring running application process into an isolated environment. This is exactly the definition of container and a perfect use case for docker. The following table represents an example of how the multiple instances can be orchestrated.&lt;/p&gt;&#10;&lt;table id="tablepress-11" class="tablepress tablepress-id-11 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;th class="column-1"&gt;OS&lt;/th&gt;&lt;th class="column-2"&gt;Container ID&lt;/th&gt;&lt;th class="column-3"&gt;Application Service&lt;/th&gt;&lt;th class="column-4"&gt;container port&lt;/th&gt;&lt;th class="column-5"&gt;published port&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td rowspan="9" class="column-1"&gt;Host&lt;br /&gt;&#10;CentOS&lt;/td&gt;&lt;td rowspan="3" class="column-2"&gt;Container 1&lt;br /&gt;&#10;(Instance #1)&lt;/td&gt;&lt;td class="column-3"&gt;Business Service A&lt;/td&gt;&lt;td class="column-4"&gt;8030&lt;/td&gt;&lt;td class="column-5"&gt;9301&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service B&lt;/td&gt;&lt;td class="column-4"&gt;8040&lt;/td&gt;&lt;td class="column-5"&gt;9401&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service C&lt;/td&gt;&lt;td class="column-4"&gt;8050&lt;/td&gt;&lt;td class="column-5"&gt;9501&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td rowspan="3" class="column-2"&gt;Container 2&lt;br /&gt;&#10;(Instance #2)&lt;/td&gt;&lt;td class="column-3"&gt;Business Service A&lt;/td&gt;&lt;td class="column-4"&gt;8030&lt;/td&gt;&lt;td class="column-5"&gt;9302&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service B&lt;/td&gt;&lt;td class="column-4"&gt;8040&lt;/td&gt;&lt;td class="column-5"&gt;9402&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-7"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service C&lt;/td&gt;&lt;td class="column-4"&gt;8050&lt;/td&gt;&lt;td class="column-5"&gt;9502&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-8"&gt;&#10;&#9;&lt;td rowspan="3" class="column-2"&gt;Container 3&lt;br /&gt;&#10;(Instance #3)&lt;/td&gt;&lt;td class="column-3"&gt;Business Service A&lt;/td&gt;&lt;td class="column-4"&gt;8030&lt;/td&gt;&lt;td class="column-5"&gt;9601&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-9"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service B&lt;/td&gt;&lt;td class="column-4"&gt;8040&lt;/td&gt;&lt;td class="column-5"&gt;9602&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-10"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service C&lt;/td&gt;&lt;td class="column-4"&gt;8050&lt;/td&gt;&lt;td class="column-5"&gt;9603&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-11 from cache --&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This way of orchestration allows the different instances of applications to share as much configuration files as possible, so that each process thinks that they bind to TCP ports (8030, 8040, 8050, etc), by taking advantage of Docker&amp;#8217;s ability to map ports for publishing.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Below is an example of the docker compose file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#39;3.6&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dapp1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: docker.digihunch.com/dapp:${DAPP_VER}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: dapp1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entrypoint: [&amp;#34;/opt/docker-entrypoint.sh&amp;#34;,&amp;#34;dapp&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9301:8030 # BUSINESS SERVICE A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9401:8040 # BUSINESS SERVICE B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9501:8050 # BUSINESS SERVICE C&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac_address: 2c:1f:4e:c5:9e:cf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - INSTANCE_TAG=dapp1 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MAX_JVM_HEAP=${DAPP_HEAP:-3892M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - vcnet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /opt/dapp/etc:/opt/dapp/etc:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp1/dapp.lic:/opt/dapp/etc/dapp.lic:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp1/variables:/opt/dapp/etc/variables:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deploy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resources:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limits:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpus: &amp;#39;0.5&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reservations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tty: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dapp2:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: docker.digihunch.com/dapp:${DAPP_VER}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: dapp2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entrypoint: [&amp;#34;/opt/docker-entrypoint.sh&amp;#34;,&amp;#34;dapp&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9302:8030 # BUSINESS SERVICE A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9402:8040 # BUSINESS SERVICE B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9502:8050 # BUSINESS SERVICE C&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac_address: 2c:1f:4e:c5:9e:d0 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - INSTANCE_TAG=dapp2 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MAX_JVM_HEAP=${DAPP_HEAP:-3892M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - vcnet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /opt/dapp/etc:/opt/dapp/etc:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp2/dapp.lic:/opt/dapp/etc/dapp.lic:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp2/variables:/opt/dapp/etc/variables:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deploy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resources:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limits:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpus: &amp;#39;0.5&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reservations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tty: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dapp3:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: docker.digihunch.com/dapp:${DAPP_VER}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: dapp3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entrypoint: [&amp;#34;/opt/docker-entrypoint.sh&amp;#34;,&amp;#34;dapp&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9601:8030 # BUSINESS SERVICE A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9602:8040 # BUSINESS SERVICE B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9603:8050 # BUSINESS SERVICE C&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac_address: 2c:1f:4e:c5:9e:d1 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - INSTANCE_TAG=dapp3 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MAX_JVM_HEAP=${DAPP_HEAP:-3892M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - vcnet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /opt/dapp/etc:/opt/dapp/etc:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp3/dapp.lic:/opt/dapp/etc/dapp.lic:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp3/variables:/opt/dapp/etc/variables:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deploy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resources:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limits:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpus: &amp;#39;0.5&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reservations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tty: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vcnet:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; driver: bridge&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; driver_opts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; com.docker.network.enable_ipv6: &amp;#34;false&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In this compose file, the environment variables are stored in .env file in the same directory and if they are not declared, the default is specified (syntax: ${VAR:-default}). &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Helper scripts&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The docker commands are fairly long so I had to organize them into several helper scripts. For example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;docker-entrypoint.sh: this script is the ENTRYPOINT script for container. It is responsible for:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Initialization work that cannot be done in Dockerfile, such as setting environment variable&lt;/li&gt;&#10;&lt;li&gt;Launch the application, including pointing log file to stdout&lt;/li&gt;&#10;&lt;li&gt;Adding host entry for host.docker.internal to /etc/hosts, as a workaround to &lt;a href="https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach"&gt;this&lt;/a&gt; issue with Docker on Linux&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;build_image.sh: this script makes the image build process smoother&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;check if image to build already exist, and ask permission to delete the existing image if so;&lt;/li&gt;&#10;&lt;li&gt;build the image with Dockerfile, and create directory structure for Dockerfile to use during COPY instruction&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;start_dapp_all.sh: this script starts all containers using docker-compose up and also add required iptables rules. We need to edit PREROUTING rules in IP tables to allow traffic between host NIC interface and the docker bridge interface, created each time service is up, as pointed out in &lt;a href="https://www.digihunch.com/2020/09/host-legacy-application-with-docker-compose/"&gt;previous post&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;stop_dapp_all.sh: this script removes the relevant iptables rules and stop all containers using docker-compose. Note that when deleting routing rules by number, start from the highest rule number and work your way down, since each deletion will cause the rules to be re-numbered.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Permission&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The container uses a non-root user to run application (e.g. with su dhunch -c &amp;#8220;command&amp;#8221; from entry point script to run application as dhunch user), because the legacy application uses the same (non-root) user to perform its actions, and it is generally not advised to use root user. To ensure consistency, we need to create the dhunch user in container (in Dockerfile) so it&amp;#8217;s uid and gid aligns with those of the host. The file and directory on the host to be access by the process in container also needs to allow dhunch user to read and write. Otherwise, entry point script will fail.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the docker-compose file, we mount a file or a directory on the host to the container, and specify 😮 if it is read only mount, under volumes. We can alternatively use bind mount (check &lt;a href="https://medium.com/devops-dudes/docker-volumes-and-bind-mounts-2fb4bd9df09d"&gt;here&lt;/a&gt; for comparison). In either case, we need to keep in mind of the permission &amp;#8211; owner alignment. For example, we have the following mount statement under volumes:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&amp;#8211; /var/lib/dapp/dcontainer/archive:/var/lib/dapp/dhost/archive&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We also need the entire directory hierarchy accessible to dhunch user. To configure this correctly, we need to create the entire directory hierarchy and set proper owner to it. Here is the comparison between the bad configuration and good configuration:&lt;/p&gt;&#10;&lt;figure class="wp-block-table"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Dockerfile instruction for container&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Permission issue during mount by docker-compose&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bad config&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;RUN mkdir -p /var/lib/dapp &amp;amp;&amp;amp; chown -R dhunch:dhunch /var/lib/dapp&lt;br&gt;&lt;/td&gt;&lt;td&gt;The directory &amp;#8220;dcontainer&amp;#8221; was not created until mount time and it is created implicitly with root as owner (since there is no user section in docker-compose, so root as default is used). The application running as dhunch user in container will have permission issue going into dcontainer directory after mount.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Good config&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;RUN mkdir -p /var/lib/dapp/dcontainer/archive &amp;amp;&amp;amp; chown -R dhunch:dhunch /var/lib/dapp&lt;/td&gt;&lt;td&gt;The directory &amp;#8220;dcontainer&amp;#8221; was already created with proper permission prior to mount and the main application process running as dhunch user will not have permission issue.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For application process running as dhunch, it also needs to write logs to stdout, so the result can be viewed from outside the container using docker logs command. The docker-entrypoint.sh script makes this happen by:&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;su dhunch -c &amp;#34;ln -sf /dev/stdout $DHUNCH_LOG_DIR/dhunch.log&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;However, this command itself will run into permission issues. To fix, we need to add user dhunch to tty group (e.g. in Dockerfile as it&amp;#8217;s needed on every container):&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;usermod -a -G tty dhunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For application process to write to a shared volume on host (e.g. NFS), we can either allow access through volume mapping, or for performant access, mount the NFS share directly to container with proper driver. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Java application&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Java applications, only use the needed package (openjdk, openjdk-devel, openjdk-headless) as the Docker image size must be kept as small as possible. The headless package is for non-UI components, the devel package is for development stuff.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is also worth-noting that the upper limit of heap size (Xmx) should be set based on the reserved memory of container (specified under docker-compose under resource limit and reservation). If heap is larger than container&amp;#8217;s available memory, OOM will be triggered and the container will be killed. &lt;a href="https://developers.redhat.com/blog/2017/03/14/java-inside-docker/"&gt;This article&lt;/a&gt; has some good explanation on this.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 3 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/docker-under-the-hood/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker components&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Automated Deployment Pipeline 1 of 3</title><link>https://www.digihunch.com/2020/09/automated-deployment-pipeline-1-2/</link><pubDate>Wed, 30 Sep 2020 22:04:00 -0400</pubDate><guid>https://www.digihunch.com/2020/09/automated-deployment-pipeline-1-2/</guid><description>&lt;h3 class="wp-block-heading"&gt;The business case&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You launched a software application. You installed it on two customer sites. You support the application mostly by SSH to customer server and run Bash commands, or slightly better, Bash scripts. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The product is a hit to the market. You hired 20 support specialists in a customer service department. The dream client came through: an enterprise giving you a fleet of 100 servers to deploy your application on.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;More staff, more business, more installations, more incidents, but the same old command driven steps. Problems:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Non-standard support procedures. Every one takes notes and everyone&amp;#8217;s notes are slightly different.&lt;/li&gt;&lt;li&gt;Information sharing among team members are ad hoc, and at high level.&lt;/li&gt;&lt;li&gt;Post-mortem discussion is driven by memory and command fragments, instead of evidence end-to-end&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If that looks like your organization, chances are you also suffer from some secondary damages over the long term, such as:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Downtime resolutions rely on the knowledgeable few&lt;/li&gt;&lt;li&gt;Documentation helps. But it never catches up to the latest version of application&lt;/li&gt;&lt;li&gt;Lack of auditing of commands during support&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I propose an automation scheme to existing support and deployment practice. This automation scheme combines a suite of common technologies, such as Bash, Python, Ansible, OpenSSH and Jenkins. The automation allows the department to, either fully or partially, operationalize the steps in support and deployment, and eventually shift towards agile practice. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Bash, Python and Ansible&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Bash script is based on shell command, perfect for running critical system tasks such as volume management. When it turns into a script, it can be cumbersome, especially with complex data structure. Python, as a tool for system administration, is a good complement to that.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Python 2 comes with most Linux distributions, and is also a dependency of other built-in tools such as YUM. Python3 can be installed easily from default YUM repositories. Both Python2 and Python3 can exist on the same operating system, although new module development are now shifted to Python3. Python&amp;#8217;s syntax is very simple and offers object-oriented programming ability. Moreover, there is an entire open-source community behind Python, which offers modules in every aspect of IT (for example, Datastax has a &lt;a href="https://www.digihunch.com/2020/06/iterate-through-cassandra-table-with-datastax-python-driver/"&gt;driver module&lt;/a&gt; for connecting to Cassandra). Those modules are installed with PIP tool, or PIP3 for python3.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both bash and Python executes on local machine. To run them on remote servers over SSH. You want to have a list of target hosts, and specify which one to execute the script against. This is where Ansible comes in handy. Ansible is superior in the following aspects:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Free and open-source, with commercial alternative (Towers);&lt;/li&gt;&lt;li&gt;Inventory management (inventory);&lt;/li&gt;&lt;li&gt;Desire state engine (roles) &lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ansible is built on Python and is agent-less. Connectivity to remote host is done via secure shell so it can take advantage of existing SSH configurations. Job execution on the target machine is done through Python. With Python you can also develop custom module in Ansible. For some use cases in customer support with Ansible, refer to my two &lt;a href="https://www.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/"&gt;previous postings&lt;/a&gt; about Ansible at scale.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Jenkins&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The tools above forms a package for automation. The issue is that all of them are command-line based. Any task that requires Ansible requires the IT professional craft up long command, such as running playbook, executing a role, or ad-hoc command. This is inconvenient when a task needs to be done during an incident. Such tasks also require trained professional with the relevant skills.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These tasks can be stored in, or initiated by &lt;a href="https://www.jenkins.io/"&gt;Jenkins&lt;/a&gt;. Although Jenkins is well known for build automation in continuous integration, it is automation engine for any command-line based IT tasks. The button to start such tasks in Jenkins UI is called &amp;#8220;Build&amp;#8221;, which is also a misnomer that underplays Jenkins&amp;#8217; versatility: building application from source code is just one of the many IT tasks that involves multiple long running commands. In this and next article we introduce Jenkins as an engine for deployment automation. &lt;/p&gt;&#10;&lt;p class="has-text-align-center has-background wp-block-paragraph" style="background-color:#d5edf7"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="587" viewBox="-0.5 -0.5 587 567" style="max-width:100%;max-height:567px;"&gt;&lt;defs&gt;&lt;filter id="dropShadow"&gt;&lt;feGaussianBlur in="SourceAlpha" stdDeviation="1.7" result="blur"&gt;&lt;/feGaussianBlur&gt;&lt;feOffset in="blur" dx="3" dy="3" result="offsetBlur"&gt;&lt;/feOffset&gt;&lt;feFlood flood-color="#3D4574" flood-opacity="0.4" result="offsetColor"&gt;&lt;/feFlood&gt;&lt;feComposite in="offsetColor" in2="offsetBlur" operator="in" result="offsetBlur"&gt;&lt;/feComposite&gt;&lt;feBlend in="SourceGraphic" in2="offsetBlur"&gt;&lt;/feBlend&gt;&lt;/filter&gt;&lt;/defs&gt;&lt;g filter="url(#dropShadow)"&gt;&lt;rect x="220" y="0" width="130" height="190" rx="19.5" ry="19.5" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" 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 flex-start; justify-content: unsafe center; width: 128px; height: 1px; padding-top: 7px; margin-left: 221px;"&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: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="19" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Vendor&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="420" y="390" width="160" height="160" rx="24" ry="24" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" 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 flex-start; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 397px; margin-left: 421px;"&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: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer B&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="500" y="409" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer B&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="400" width="160" height="160" rx="24" ry="24" fill="#f5f5f5" stroke="#666666" stroke-dasharray="3 3" 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 flex-start; justify-content: unsafe center; width: 158px; height: 1px; padding-top: 407px; 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: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Customer A&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="419" fill="#333333" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer A&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="240" y="110" width="90" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" 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: 88px; height: 1px; padding-top: 125px; margin-left: 241px;"&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;SSH Proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="129" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;SSH Proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="240" y="30" width="90" height="60" rx="9" ry="9" fill="#ffffff" stroke="#000000" 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: 88px; height: 1px; padding-top: 60px; margin-left: 241px;"&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;Jenkins&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="64" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Jenkins&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="285" cy="190" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&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: 58px; height: 1px; padding-top: 190px; margin-left: 256px;"&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;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="194" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="150" cy="410" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&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: 58px; height: 1px; padding-top: 410px; margin-left: 121px;"&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;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="414" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;ellipse cx="430" cy="400" rx="30" ry="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/ellipse&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: 58px; height: 1px; padding-top: 400px; margin-left: 401px;"&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;VPN&lt;br&gt;Gateway&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="430" y="404" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;VPN&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 247.5 280 C 217.5 280 210 310 234 316 C 210 329.2 237 358 256.5 346 C 270 370 315 370 330 346 C 360 346 360 322 341.25 310 C 360 286 330 262 303.75 274 C 285 256 255 256 247.5 280 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&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 center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 310px; margin-left: 211px;"&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;Internet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="285" y="314" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Internet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="20" y="470" width="120" height="60" fill="#ffffff" stroke="#000000" 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: 118px; height: 1px; padding-top: 500px; margin-left: 21px;"&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;Server Fleet Managed by Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="504" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Server Fleet Managed&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="440" y="470" width="120" height="60" fill="#ffffff" stroke="#000000" 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: 118px; height: 1px; padding-top: 500px; margin-left: 441px;"&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;Server Fleet Managed by Vendor&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="500" y="504" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Server Fleet Managed&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 286.02 255.63 L 285.18 226.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 286.17 260.88 L 282.47 253.99 L 286.02 255.63 L 289.47 253.79 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285.03 221.12 L 288.73 228.01 L 285.18 226.37 L 281.73 228.21 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 176.2 384.82 L 224.52 346.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 172.09 388.09 L 175.39 380.99 L 176.2 384.82 L 179.74 386.47 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 228.63 343.1 L 225.33 350.19 L 224.52 346.37 L 220.97 344.72 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 403.86 374.76 L 358.93 338.03" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 407.92 378.08 L 400.29 376.36 L 403.86 374.76 L 404.72 370.94 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 354.87 334.71 L 362.5 336.43 L 358.93 338.03 L 358.07 341.85 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 84.98 466.04 L 123.8 435.18" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 80.88 469.3 L 84.18 462.21 L 84.98 466.04 L 88.53 467.69 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 127.91 431.91 L 124.61 439 L 123.8 435.18 L 120.25 433.53 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 455.72 425.72 L 495.5 465.5" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 452 422 L 459.43 424.48 L 455.72 425.72 L 454.48 429.43 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 499.21 469.21 L 491.78 466.73 L 495.5 465.5 L 496.73 461.78 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 153.63 L 285 146.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 285 158.88 L 281.5 151.88 L 285 153.63 L 288.5 151.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 141.12 L 288.5 148.12 L 285 146.37 L 281.5 148.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 103.63 L 285 96.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 285 108.88 L 281.5 101.88 L 285 103.63 L 288.5 101.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 285 91.12 L 288.5 98.12 L 285 96.37 L 281.5 98.12 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://desk.draw.io/support/solutions/articles/16000042487" target="_blank" rel="noopener noreferrer"&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;The infrastructure architecture is diagramed as above, and with the connection across Internet, the target hosts must be hardened properly in the following aspects:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Connectivity to remote host is via SSH chaining, through an SSH proxy;&lt;/li&gt;&lt;li&gt;Root login must be disabled for remote session or by password;&lt;/li&gt;&lt;li&gt;Service user may be shared, but must be authenticated by individual RSA key pair;&lt;/li&gt;&lt;li&gt;Service user connected remotely needs to escalate privilege by su if needed;&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I want to make a theoretical distinction between our topic here and continuous deployment. We simply focus on the technical side of deployment automation. Essentially automating a few bash scripts. On the other hand, a continuous deployment process is an extension to an existing continuous integration pipeline, with the vision to streamline the process end-to-end from code commit to production rollout. Implementing CI/CD pipelines should be approached as an organizational program rather than an individual technical initiative. &lt;a href="https://www.redhat.com/en/blog/integrating-ansible-jenkins-cicd-process"&gt;Here&lt;/a&gt; is a good technical overview on CI/CD pipeline with Jenkins and Ansible.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Security&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The security mechanism of this system is based on OpenSSH because the connectivity between servers are through SSH chaining. RSA key authentication must be used in order to encrypt traffic with password-less login. Connection to an SSH host can be done through a proxy server. Below is an example of SSH configuration:&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;Include customer1.config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Include customer2.config&#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;Host *&#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;span style="display:flex;"&gt;&lt;span&gt; ServerAliveInterval &lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ServerAliveCountMax &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; Compression yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ControlPersist 3h&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ControlPath ~/.ssh/sockets/%r@%h-%p&#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;Host gateway&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname support.digihunch.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User jdoe&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Port &lt;span style="color:#ae81ff"&gt;2223&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;Host customer-server-0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Hostname 192.168.201.12&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; User support&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ProxyCommand ssh -W %h:%p gateway&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Open SSH configuration file (~/.ssh/config) needs to be configured properly with useful host names and aliases. To prevent the config files from growing too long, include statement can be used to reference other configuration file (available with OpenSSH v7.3 sp1 and up). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The host names (as well as aliases) listed in SSH configuration can be directly referenced in Ansible inventory, allowing Ansible (and Jenkins) to reference site by alias and connect to target host through proxy.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Plugins&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins has a community that develops a variety of plugins, which makes Jenkins the most powerful automation platform. Here are some examples of useful plugins:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Audit Trail: output job execution history to file or Elasticsearch;&lt;/li&gt;&lt;li&gt;Credentials: stores credentials in Jenkins;&lt;/li&gt;&lt;li&gt;Pipeline: build declarative (new) or scripted (old) pipeline for Jenkins jobs;&lt;/li&gt;&lt;li&gt;Simple Theme: just a theme but allows console output to be dark (using CSS);&lt;/li&gt;&lt;li&gt;Job Configuration History: job configuration audit;&lt;/li&gt;&lt;li&gt;Mask password: mask variables (including password) from console output &lt;/li&gt;&lt;li&gt;Ansible: invokes ad-hoc commands and playbooks&lt;/li&gt;&lt;li&gt;SSH agent, SSH pipeline steps, SSH credentials: features related in SSH in Jenkins pipelines.&lt;/li&gt;&lt;li&gt;Purge Job History: purge all of build history, or purge&lt;a href="https://support.cloudbees.com/hc/en-us/articles/215549798-Best-Strategy-for-Disk-Space-Management-Clean-Up-Old-Builds"&gt; by time &lt;/a&gt;and number of old builds.&lt;/li&gt;&lt;li&gt;Parameterized Scheduler: schedule to run a job and provide parameter&lt;/li&gt;&lt;li&gt;Workspace cleanup: clean up workspace when invoked.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the next article, we will go over some common job configurations.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/09/log-file-navigator-lnav/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Log file navigator (lnav)&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-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;Automated Deployment Pipeline 2 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Log file navigator (lnav)</title><link>https://www.digihunch.com/2020/09/log-file-navigator-lnav/</link><pubDate>Wed, 23 Sep 2020 21:03:00 -0400</pubDate><guid>https://www.digihunch.com/2020/09/log-file-navigator-lnav/</guid><description>&lt;p class="wp-block-paragraph"&gt;I&amp;#8217;ve used a number of log viewers in command terminal, on MacOS and Linux server. I read system logs, log4j formats, as well as json formats. Unfortunately, I have not found an ideal (free) log viewer, either on UI or in command terminal.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Out of those I tried lnav is one of the better ones. It&amp;#8217;s been around for more than 10 years and is configurable for a variety of formats. It is available in EPEL-repository for Linux and home brew for Mac.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For example, if our log (produced by log4j) looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-10-06 20:59:25,471,DEBUG,org.dcm4che2.net.Association - &lt;span style="color:#f92672"&gt;[&lt;/span&gt;platform-dicomServer-44104-574917&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Association&lt;span style="color:#f92672"&gt;(&lt;/span&gt;552550&lt;span style="color:#f92672"&gt;)&lt;/span&gt;: start ARTIM 5000ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-10-06 20:59:25,471,DEBUG,org.dcm4che2.net.Association - &lt;span style="color:#f92672"&gt;[&lt;/span&gt;platform-dicomServer-44104-574917&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Association&lt;span style="color:#f92672"&gt;(&lt;/span&gt;552550&lt;span style="color:#f92672"&gt;)&lt;/span&gt;: Client closed connecti&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;on without sending data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-10-06 20:59:25,471,DEBUG,org.dcm4che2.net.Association - &lt;span style="color:#f92672"&gt;[&lt;/span&gt;platform-dicomServer-44104-574917&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Association&lt;span style="color:#f92672"&gt;(&lt;/span&gt;552550&lt;span style="color:#f92672"&gt;)&lt;/span&gt; enter state: Sta1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2020-10-06 20:59:25,471,INFO,org.dcm4che2.net.Association - &lt;span style="color:#f92672"&gt;[&lt;/span&gt;platform-dicomServer-44104-574917&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Association&lt;span style="color:#f92672"&gt;(&lt;/span&gt;552550&lt;span style="color:#f92672"&gt;)&lt;/span&gt;: close Socket&lt;span style="color:#f92672"&gt;[&lt;/span&gt;addr&lt;span style="color:#f92672"&gt;=&lt;/span&gt;/10.100.101.10,port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;24976,localport&lt;span style="color:#f92672"&gt;=&lt;/span&gt;44104&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;2020-10-06 20:59:25,471,DEBUG,org.dcm4che2.net.AssociationReaper - &lt;span style="color:#f92672"&gt;[&lt;/span&gt;platform-dicomServer-44104-574917&lt;span style="color:#f92672"&gt;]&lt;/span&gt; Stop check &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; idle Association&lt;span style="color:#f92672"&gt;(&lt;/span&gt;552550&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can introduce custom formatting, 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-java" data-lang="java"&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:#e6db74"&gt;&amp;#34;dapp&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:#e6db74"&gt;&amp;#34;title&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;dapp log4j format&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:#e6db74"&gt;&amp;#34;description&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;dapp log4j format&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:#e6db74"&gt;&amp;#34;regex&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:#e6db74"&gt;&amp;#34;dapp&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:#e6db74"&gt;&amp;#34;pattern&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;^(?&amp;lt;timestamp&amp;gt;\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2},\\d{3}),(?&amp;lt;level&amp;gt;\\w+),(?&amp;lt;component&amp;gt;[\\w-.]+) - \\[(?&amp;lt;thread&amp;gt;[^ ]+)\\] (?&amp;lt;body&amp;gt;.*)$&amp;#34;&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; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;level-field&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;level&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:#e6db74"&gt;&amp;#34;level&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:#e6db74"&gt;&amp;#34;error&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;ERROR&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:#e6db74"&gt;&amp;#34;warning&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;WARN&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:#e6db74"&gt;&amp;#34;info&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;INFO&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:#e6db74"&gt;&amp;#34;debug&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;DEBUG&amp;#34;&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:#e6db74"&gt;&amp;#34;value&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:#e6db74"&gt;&amp;#34;level&amp;#34;&lt;/span&gt; : { &lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;identifier&amp;#34;&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:#e6db74"&gt;&amp;#34;component&amp;#34;&lt;/span&gt; : { &lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;identifier&amp;#34;&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:#e6db74"&gt;&amp;#34;thread&amp;#34;&lt;/span&gt; : { &lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;identifier&amp;#34;&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:#e6db74"&gt;&amp;#34;body&amp;#34;&lt;/span&gt; : { &lt;span style="color:#e6db74"&gt;&amp;#34;kind&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;string&amp;#34;&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:#e6db74"&gt;&amp;#34;highlights&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:#e6db74"&gt;&amp;#34;DIMSE&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:#e6db74"&gt;&amp;#34;pattern&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;A-(ASSOCIATE-(RQ|AC)|RELEASE-(RQ|RP)|ABORT)|C-(STORE|MOVE|FIND|ECHO)-(RQ|RSP)&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:#e6db74"&gt;&amp;#34;color&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;Red&amp;#34;&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; },&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;sample&amp;#34;&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; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;line&amp;#34;&lt;/span&gt; : &lt;span style="color:#e6db74"&gt;&amp;#34;2020-10-06 12:00:28,500,INFO,dicom.dicom-main - [main] Start listening on port 44104&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;]&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;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Save the content above as ~/.lnav/formats/installed/dapp.json, then load the log file with lnav, lnav will display the log by presenting columns in different colours.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1622" height="237" src="https://www.digihunch.com/wp-content/uploads/2020/10/image.png" alt="" class="wp-image-1616"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One of the default behaviours is highlighting the IPv4 address, as shown above. This behaviour is however, not optional and currently cannot be turned off, which is reported &lt;a href="https://github.com/tstack/lnav/issues/783"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2020/09/spark-cassandra-and-python/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Spark, Cassandra and Python&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/09/automated-deployment-pipeline-1-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 1 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Nginx as a reverse proxy for Nifi web UI and Kibana</title><link>https://www.digihunch.com/2020/01/nginx-as-a-reverse-proxy-for-nifi/</link><pubDate>Thu, 16 Jan 2020 22:22:51 -0400</pubDate><guid>https://www.digihunch.com/2020/01/nginx-as-a-reverse-proxy-for-nifi/</guid><description>&lt;p class="wp-block-paragraph"&gt;Nginx can act as a application neutral proxy. One example is to front Nifi. The nifi default configuration provides an HTTP access point, specified in the following entries in nifi.properties:&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;nifi.web.http.host=192.168.133.5&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nifi.web.http.port=8080&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Nifi can provide secure port by commenting out the lines above and provide the followings:&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;nifi.web.https.host=192.168.133.5&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;nifi.web.https.port=8083&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;However, it does require configuring JKS keystore for Java, as well as authentication. Customers with existing AD servers are likely to require authentication via LDAP. While Nifi does support LDAP integration according to its &lt;a href="https://nifi.apache.org/docs/nifi-docs/html/administration-guide.html#ldap_login_identity_provider"&gt;administration guide&lt;/a&gt;. The configuration is quite involving. You need to configure the identity provider, as well as authorizes. I have personally spent a couple days on this without much progress. The information in the logging isn&amp;#8217;t to the point. Restarting nifi also is a long process, making it painful to troubleshoot. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I then moved to Nginx (open-source) as an alternative and it is quite enlightening. I already knew that the SSL termination in nginx is super easy to configure. This time I learned that the opensource community even has a support for LDAP integration. Here is a diagram of how it works:&lt;/p&gt;&#10;&lt;p class="has-text-align-center 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="542px" viewBox="-0.5 -0.5 542 272" style="max-width:100%;max-height:272px;"&gt;&lt;defs&gt;&lt;linearGradient x1="0%" y1="0%" x2="0%" y2="100%" id="mx-gradient-ffffff-1-33ebff-1-s-0"&gt;&lt;stop offset="0%" style="stop-color:#FFFFFF"&gt;&lt;/stop&gt;&lt;stop offset="100%" style="stop-color:#33EBFF"&gt;&lt;/stop&gt;&lt;/linearGradient&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="540" height="270" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;rect x="10" y="75" width="60" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" 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: 58px; height: 1px; padding-top: 90px; margin-left: 12px;"&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;Client&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="40" y="94" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Client&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="169.25" y="230" width="118.5" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" 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: 116px; height: 1px; padding-top: 245px; margin-left: 171px;"&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;Active Directory&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="229" y="249" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Active Directory&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="430" y="10" width="85" height="70" fill="#ffffff" stroke="#000000" stroke-dasharray="3 3" 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 flex-start; justify-content: unsafe center; width: 83px; height: 1px; padding-top: 17px; margin-left: 432px;"&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;Container1&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="473" y="29" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Container1&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="445" y="40" width="55" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" 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: 53px; height: 1px; padding-top: 55px; margin-left: 447px;"&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;Nifi&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="473" y="59" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Nifi&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="430" y="120" width="85" height="70" fill="#ffffff" stroke="#000000" stroke-dasharray="3 3" 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 flex-start; justify-content: unsafe center; width: 83px; height: 1px; padding-top: 127px; margin-left: 432px;"&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;Container2&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="473" y="139" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Container2&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="445" y="150" width="55" height="30" rx="4.5" ry="4.5" fill="#ffffff" stroke="#000000" 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: 53px; height: 1px; padding-top: 165px; margin-left: 447px;"&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;Nifi&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="473" y="169" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Nifi&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="140" y="20" width="177" height="170" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&gt;&lt;rect x="140" y="20" width="177" height="170" fill="#ffffff" stroke="#000000" stroke-dasharray="3 3" 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 flex-start; justify-content: unsafe center; width: 175px; height: 1px; padding-top: 27px; margin-left: 142px;"&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; text-decoration: underline; white-space: normal; word-wrap: normal; "&gt;Container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="229" y="39" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" text-decoration="underline"&gt;Container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="155" y="50" width="144" height="60" rx="9" ry="9" fill="#ffffff" stroke="#000000" 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 flex-start; justify-content: unsafe center; width: 142px; height: 1px; padding-top: 57px; margin-left: 157px;"&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; text-decoration: underline; white-space: normal; word-wrap: normal; "&gt;Nginx process&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="227" y="69" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" text-decoration="underline"&gt;Nginx process&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="165" y="76" width="120" height="28" rx="4.2" ry="4.2" fill="url(#mx-gradient-ffffff-1-33ebff-1-s-0)" 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 flex-start; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 83px; margin-left: 167px;"&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; text-decoration: underline; white-space: normal; word-wrap: normal; "&gt;http_auth_request&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="225" y="95" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" text-decoration="underline"&gt;http_auth_request&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="155" y="150" width="145" 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: 143px; height: 1px; padding-top: 165px; margin-left: 157px;"&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;ldap-auth daemon&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="228" y="169" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;ldap-auth daemon&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 228 104.81 L 227.57 143.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 227.51 148.88 L 224.09 141.84 L 227.57 143.63 L 231.09 141.92 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&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 center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 125px; margin-left: 228px;"&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; background-color: #ffffff; white-space: nowrap; "&gt;http&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="228" y="129" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;http&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 285 83 L 438.73 56.1" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 443.9 55.19 L 437.61 59.85 L 438.73 56.1 L 436.4 52.95 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&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 center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 55px; margin-left: 370px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #FFFFCC; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;d&lt;font color="#000000"&gt;http&lt;/font&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="370" y="58" fill="#FFFFCC" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;dhttp&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 285 97 L 439.14 162.51" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 443.97 164.56 L 436.16 165.05 L 439.14 162.51 L 438.9 158.6 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&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 center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 120px; margin-left: 370px;"&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: #FFFFCC; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;&lt;font color="#000000"&gt;http&lt;/font&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="370" y="124" fill="#FFFFCC" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;http&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 228.37 223.63 L 227.63 186.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 228.48 228.88 L 224.84 221.95 L 228.37 223.63 L 231.84 221.81 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 227.52 181.12 L 231.16 188.05 L 227.63 186.37 L 224.16 188.19 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&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 center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 205px; margin-left: 228px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 11px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; background-color: #ffffff; white-space: nowrap; "&gt;LDAP&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="228" y="208" fill="#000000" font-family="Helvetica" font-size="11px" text-anchor="middle"&gt;LDAP&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 76.37 90 L 158.63 90" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 71.12 90 L 78.12 86.5 L 76.37 90 L 78.12 93.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 163.88 90 L 156.88 93.5 L 158.63 90 L 156.88 86.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&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 center; justify-content: unsafe center; width: 1px; height: 1px; padding-top: 90px; margin-left: 106px;"&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; background-color: #ffffff; white-space: nowrap; "&gt;https&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="106" y="94" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;https&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This approach is outlined in a &lt;a href="https://www.nginx.com/blog/nginx-plus-authenticate-users/"&gt;blog post&lt;/a&gt; on &lt;a href="https://www.nginx.com/"&gt;Nginx &lt;/a&gt;website. The &lt;a href="https://github.com/nginxinc/nginx-ldap-auth"&gt;ldap-auth daemon&lt;/a&gt; is implemented in Python can can be wrapped up as a systemd service. Once a client sends a request in https, security layer is terminated in nginx, and an authentication request in http is sent to the ldap-auth daemon, which proxies converts the request into LDAP searches and proxies it over to customer&amp;#8217;s Active Directory server, for authentication. Once authenticated, the http request can make to one of the backend container or server which hosts Nifi. Below is an example of how this can be configure on RedHat.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Install python3 and python-ldap&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;RedHat may have both python2 and python3 pre-installed, python2 being the default. We do not want to change the default because other applications such as yum still depends on python2 as of early 2020.&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;yum -y install python3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;yum -y install gcc python3-devel openldap-devel&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pip3 install python-ldap&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once python3 is installed, pip3 will be available and we use that to install python-ldap. This is a module in Python3 that will be used by the script that act as ldap daemon.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Configure ldap-auth daemon as systemd service&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the github project for &lt;a href="https://github.com/nginxinc/nginx-ldap-auth"&gt;ldap-auth&lt;/a&gt;, download nginx-ldap-auth-daemon.py to local location such as /usr/bin, then we create &lt;a href="https://github.com/nginxinc/nginx-ldap-auth/blob/master/nginx-ldap-auth-daemon.py"&gt;nginx-ldap-auth.service&lt;/a&gt; in /etc/systemd/system/ with the following content.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;[Unit]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Description&lt;span style="color:#f92672"&gt;=&lt;/span&gt;LDAP authentication helper &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; Nginx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;After&lt;span style="color:#f92672"&gt;=&lt;/span&gt;network&lt;span style="color:#f92672"&gt;.&lt;/span&gt;target network&lt;span style="color:#f92672"&gt;-&lt;/span&gt;online&lt;span style="color:#f92672"&gt;.&lt;/span&gt;target&#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;[Service]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;simple&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;User&lt;span style="color:#f92672"&gt;=&lt;/span&gt;root&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Group&lt;span style="color:#f92672"&gt;=&lt;/span&gt;root&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WorkingDirectory&lt;span style="color:#f92672"&gt;=/&lt;/span&gt;var&lt;span style="color:#f92672"&gt;/&lt;/span&gt;run&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;ExecStart&lt;span style="color:#f92672"&gt;=/&lt;/span&gt;usr&lt;span style="color:#f92672"&gt;/&lt;/span&gt;bin&lt;span style="color:#f92672"&gt;/&lt;/span&gt;python3 &lt;span style="color:#f92672"&gt;/&lt;/span&gt;usr&lt;span style="color:#f92672"&gt;/&lt;/span&gt;bin&lt;span style="color:#f92672"&gt;/&lt;/span&gt;nginx&lt;span style="color:#f92672"&gt;-&lt;/span&gt;ldap&lt;span style="color:#f92672"&gt;-&lt;/span&gt;auth&lt;span style="color:#f92672"&gt;-&lt;/span&gt;daemon&lt;span style="color:#f92672"&gt;.&lt;/span&gt;py&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KillMode&lt;span style="color:#f92672"&gt;=&lt;/span&gt;process&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;KillSignal&lt;span style="color:#f92672"&gt;=&lt;/span&gt;SIGINT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Restart&lt;span style="color:#f92672"&gt;=&lt;/span&gt;on&lt;span style="color:#f92672"&gt;-&lt;/span&gt;failure&#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;[Install]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WantedBy&lt;span style="color:#f92672"&gt;=&lt;/span&gt;multi&lt;span style="color:#f92672"&gt;-&lt;/span&gt;user&lt;span style="color:#f92672"&gt;.&lt;/span&gt;target&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then, run the following command to load, start and check nginx-ldap-auth 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;systemctl reload-daemon&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl start nginx-ldap-auth&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;systemctl status nginx-ldap-auth&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This service will be up and listening to port 8888 for http traffic.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading"&gt;Configure Nginx&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt; Then configure nginx with the following entries in its default.conf file, typically located in /etc/nginx/conf.d.&amp;nbsp; &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-xml" data-lang="xml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;upstream nifibackend {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; # default: round robin&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server container1.nifi.digihunch.com:8080;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; server container2.nifi.digihunch.com:8080;&#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;proxy_cache_path cache/ keys_zone=auth_cache:10m;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# nifi proxy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;server {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; listen 8083ssl;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; include /etc/nginx/ssl/default.conf;&#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; location / {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; auth_request /auth-proxy;&#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; proxy_pass http://nifibackend;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header Host $host:$server_port;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-ProxyScheme https;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-ProxyHost $1;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-ProxyPort 8083;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-ProxyContextPath /;&#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; &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; location /auth-proxy {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; internal;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_pass http://127.0.0.1:8888;&#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; proxy_pass_request_body off;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header Content-Length &amp;#34;&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_cache auth_cache;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_cache_valid 200 10m;&#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; proxy_cache_key &amp;#34;$http_authorization$cookie_nginxauth&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-Ldap-URL &amp;#34;ldaps://ldap.digihunch.com:636&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-Ldap-BaseDN &amp;#34;OU=Corporate User Accounts,DC=digihunch,DC=org&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-Ldap-BindDN &amp;#34;CN=Digi Hunch Service Account,OU=Digi,OU=ServiceAccounts,OU=Digi,OU=Digi Applications,DC=digihunch,DC=org&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-Ldap-BindPass &amp;#34;myownpasswordtricks&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-CookieName &amp;#34;nginxauth&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header Cookie nginxauth=$cookie_nginxauth;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-Ldap_Starttls &amp;#34;true&amp;#34;;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; proxy_set_header X-Ldap-Template &amp;#34;(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;amp;&lt;/span&gt;(sAMAccountName=%(username)s)(objectClass=organizationalPerson)(memberOf=CN=GH_SYSADMIN,OU=GHCO,OU=Groups,OU=Digi,OU=Digi Applications,DC=digihunch,DC=org))&amp;#34;;&#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;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We need the full distinguished name of bind user to get this to work. Once configured properly, and user attempts to connect through a browser, Nginx will pop up a prompt for username and password. The username will be plugged into the X-Ldap-Template for further queries. The same HTTP header also allows you to filter by membership that the user is associated with.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://www.digihunch.com/2019/12/networking-basics-3-of-3-common-network-technologies/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Networking Basics 3 of 3 – common network protocols and technologies&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/01/several-ways-to-ensure-high-availability/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;High Availability and Load Balancer&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>