<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Version control system on Digi Hunch</title><link>https://www.digihunch.com/tag/version-control-system/</link><description>Recent content in Version control system on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 09:47:04 -0400</lastBuildDate><atom:link href="https://www.digihunch.com/tag/version-control-system/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>Automated Deployment Pipeline 3 of 3</title><link>https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/</link><pubDate>Wed, 14 Oct 2020 17:27:00 -0400</pubDate><guid>https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/</guid><description>&lt;h3 class="wp-block-heading" id="h-background"&gt;Background&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We have previously covered a pipeline example with Jenkins calling Ansible to leverage OpenSSH configuration and Ansible inventory. We also discussed a use case with declarative pipeline.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this posting, I will provide another advanced example, built on declarative pipeline. The pipeline file will be pulled from Git repository. Also, the script is executed on a remote agent, instead of the Jenkins master server. The reason this example is important, is that:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Jenkinsfile is now version controlled (pipeline as code);&lt;/li&gt;&#10;&lt;li&gt;Service script (e.g. python) is also version controlled from a central repository;&lt;/li&gt;&#10;&lt;li&gt;Computing resource is provided by a remote agent. Since the script is pulled before running, the agent is still fungible;&lt;/li&gt;&#10;&lt;li&gt;The result from service script execution is archived (similar to the way build artifact is stored in Jenkins) in master.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The architecture of this pipeline now becomes the followings:&lt;/p&gt;&#10;&lt;p class="has-pale-cyan-blue-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="591px" viewBox="-0.5 -0.5 591 671" style="max-width:100%;max-height:671px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&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="430" y="390" width="160" height="280" 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: 431px;"&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="510" 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="270" 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 (Master)&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 (Master&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 215 285 C 163 285 150 320 191.6 327 C 150 342.4 196.8 376 230.6 362 C 254 390 332 390 358 362 C 410 362 410 334 377.5 320 C 410 292 358 264 312.5 278 C 280 257 228 257 215 285 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: 258px; height: 1px; padding-top: 320px; margin-left: 151px;"&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;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="280" y="324" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Internet&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="470" width="100" height="90" 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: 98px; height: 1px; padding-top: 515px; 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: all; white-space: normal; word-wrap: normal; "&gt;agent.jar&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;br&gt;Git&lt;br&gt;Python&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="519" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;agent.jar&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="460" y="470" width="100" height="90" 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: 98px; height: 1px; padding-top: 515px; margin-left: 461px;"&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;agent.jar&lt;br&gt;Ansible&lt;br&gt;OpenSSH&lt;br&gt;Git&lt;br&gt;Python&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="510" y="519" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;agent.jar&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 282.5 257.65 L 284.58 226.35" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 282.15 262.88 L 279.13 255.67 L 282.5 257.65 L 286.11 256.13 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 284.93 221.12 L 287.95 228.33 L 284.58 226.35 L 280.97 227.87 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 173.61 382.89 L 181.4 363.7" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 171.63 387.75 L 171.03 379.95 L 173.61 382.89 L 177.51 382.58 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 183.38 358.84 L 183.99 366.64 L 181.4 363.7 L 177.5 364 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 406.97 372.68 L 401.42 354.1" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 408.47 377.72 L 403.11 372.01 L 406.97 372.68 L 409.82 370.01 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 399.92 349.07 L 405.28 354.78 L 401.42 354.1 L 398.57 356.78 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 456.11 425.28 L 505.1 465.93" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 452.07 421.93 L 459.7 423.7 L 456.11 425.28 L 455.23 429.09 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 509.14 469.29 L 501.52 467.51 L 505.1 465.93 L 505.99 462.12 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;rect x="20" y="590" 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: 620px; 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;Customer Inventory&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="80" y="624" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer Inventory&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="450" y="590" 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: 620px; margin-left: 451px;"&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;Customer Inventory&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="510" y="624" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Customer Inventory&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 80 583.63 L 80 566.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 80 588.88 L 76.5 581.88 L 80 583.63 L 83.5 581.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 80 561.12 L 83.5 568.12 L 80 566.37 L 76.5 568.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 510 583.63 L 510 566.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 510 588.88 L 506.5 581.88 L 510 583.63 L 513.5 581.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 510 561.12 L 513.5 568.12 L 510 566.37 L 506.5 568.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;ellipse cx="290" cy="345" rx="50" ry="35" 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: 98px; height: 1px; padding-top: 345px; 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;Git &lt;br&gt;Repository&lt;br&gt;Jenkisfile&lt;br&gt;scripts&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="290" y="349" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Git&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 133.46 509.65 L 236.54 350.35" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 130.61 514.06 L 131.47 506.28 L 133.46 509.65 L 137.35 510.09 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 239.39 345.94 L 238.53 353.72 L 236.54 350.35 L 232.65 349.91 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 343.67 350.2 L 456.33 509.8" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 340.64 345.91 L 347.54 349.61 L 343.67 350.2 L 341.82 353.65 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 459.36 514.09 L 452.46 510.39 L 456.33 509.8 L 458.18 506.35 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;h3 class="wp-block-heading" id="h-configure-agent"&gt;Configure Agent&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins has a plugin called &lt;a href="https://plugins.jenkins.io/ssh-slaves/"&gt;SSH Build Agent&lt;/a&gt;, that allows you to configure a Linux agent, communicating with Jenkins master in SSH. For Window agent, it uses JNLP to communicate with master, which is outside of our scope of discussion. As I touched on in &lt;a href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/"&gt;previous post&lt;/a&gt;, Jenkins uses its own implementation of SSH protocol to achieve this. This means that it cannot re-use the configurations in ~/.ssh/config and thus the ability to do SSH chaining is eliminated. This is incompatible with our architecture so I have to register Jenkins agent using a different launch method &amp;#8220;Launch agent via execution of command on the master&amp;#8221;. The execution of command on master can still leverage OpenSSH config file. In order to do so, we must copy the agent.jar file to the remote agent first (URL is ${JENKINS_URL}/jnlpJars/agent.jar). Then use SSH command to call the jar file from agent (aka slave) machine. You may add some java argument for troubleshooting. Below is an example configuration for the node.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="2658" height="1650" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-6.png" alt="" class="wp-image-1736" style="width:0px"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I shall also note that if the remote agent is a different operating system where the path of bash might be different, then you need to include the directory of bash executable in PATH environment variable. That can be done as in the screenshot above (PATH=${PATH:/usr/bin}). If this is incorrect, you might run into issues when running sh step in Jenkins pipeline. Here is an &lt;a href="https://qiita.com/xishan/items/881f1e02628170801f4a"&gt;article&lt;/a&gt; about this. To translate that page, the symptom of this issue includes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Error from pipeline execution that says the following, which is very generic:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;process apparently never started in /home/dhunch/jenkins/workspace/site-remote-job@tmp/durable-b997d26c &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;(running Jenkins temporarily with -Dorg.jenkinsci.plugins.durabletask.BourneShellScript.LAUNCH_DIAGNOSTICS=true might make the problem clearer)&#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;Job status shows:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hudson.AbortException: script returned exit code -2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.handleExit(DurableTaskStep.java:659)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.check(DurableTaskStep.java:605)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at org.jenkinsci.plugins.workflow.steps.durable_task.DurableTaskStep$Execution.run(DurableTaskStep.java:549)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#9;at java.base/java.lang.Thread.run(Thread.java:834)&#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;Threaddump (only available during execution) shows:&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-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Thread #6&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at DSL.sh(awaiting process completion in /home/dhunch/jenkins/workspace/site-remote-job/durable-b997d26c; recurrence period: 9543ms; check task scheduled; cancelled? false done? false)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at WorkflowScript.run(WorkflowScript:9)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; at DSL.script(Native Method)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This issue is tricky because none of the symptoms above make you think about the environment variable defined for the agent, and that the issue does not occur until you actually execute a Jenkins pipeline, usually well after the node agent is registered, and only impacts shell step (sh). It is recommended to test this with sh steps in pipeline.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-repository"&gt;Configure Repository&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Our example job executes service script pulled from SCM, on a remote agent. Then the master pulls the result file to itself. The git repository can be set in Jenkins job, where you specify that the script named Jenkinsfile (from the repo) is the pipeline file that needs to be executed.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1428" height="756" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-5.png" alt="" class="wp-image-1734"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is fairly simple, but what makes the situation more complex is the following few requirements:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;it&amp;#8217;s the agent node that needs git (installed) and clone to repo;&lt;/li&gt;&#10;&lt;li&gt;the master does not need (and should not attempt to) clone from repo;&lt;/li&gt;&#10;&lt;li&gt;the master needs to do its job without having to pull SCM;&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We need to be able to specify whether each step needs to pull from SCM. The following pipeline syntax shows how this is done:&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-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;def&lt;/span&gt; agent_dir &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#39;initial_value&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline &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; agent none&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&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; stages &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; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Execute Job&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&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; agent &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; label &lt;span style="color:#e6db74"&gt;&amp;#39;remote-agent-customer1&amp;#39;&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; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;false&lt;/span&gt;&lt;span style="color:#f92672"&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; steps &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; echo &lt;span style="color:#e6db74"&gt;&amp;#39;Executing job on node&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#39;whoami &amp;amp;&amp;amp; pwd&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; script &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; agent_dir &lt;span style="color:#f92672"&gt;=&lt;/span&gt; sh&lt;span style="color:#f92672"&gt;(&lt;/span&gt;returnStdout: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt; script: &lt;span style="color:#e6db74"&gt;&amp;#39;echo -n ${WORKSPACE}&amp;#39;&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:#75715e"&gt;// the variable should not include carriage return&#10;&lt;/span&gt;&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; sh &lt;span style="color:#e6db74"&gt;&amp;#34;echo ${agent_dir}&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; &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; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Pull Result&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&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; agent &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; label &lt;span style="color:#e6db74"&gt;&amp;#39;master&amp;#39;&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; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&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:#75715e"&gt;// no need to pull scm to this agent&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &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; echo &lt;span style="color:#e6db74"&gt;&amp;#39;Pulling job below&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;echo ${agent_dir}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;scp dhunch@site1:\&amp;#34;${agent_dir}\&amp;#34;/result.csv ./&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; &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;&#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;In this example pipeline file, we declare agent none for the pipeline, then an agent for each specific stage. we also specify the option skipDefaultCheckout as true at the step where pulling from SCM is not needed. This allows us to finish job with multiple agent, and only pull from SCM as needed. This snippet also exemplifies how to declare a variable, assign it from stdout from one agent, and persist the value across ensuing stages.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-store-result"&gt;Store Result&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The reason we run pipeline jobs on this remote agent is because it is sitting in customer network and has local direct access to data. So this is perfect for situation such as data analytical jobs, which access to database on local network and store result. We need to pull the result file back to agent and make it available on Jenkins. This is so similar to &amp;#8220;archive artifact&amp;#8221; task (commonly seen in CI process) that we can simply use its plugin to achieve what we need. Before archive artifact, we need to pull it to local (master), as shown in the example code above. After that, we need another stage to archive the result.&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-groovy" data-lang="groovy"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;Archive Result&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&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; agent &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; label &lt;span style="color:#e6db74"&gt;&amp;#39;master&amp;#39;&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; options &lt;span style="color:#f92672"&gt;{&lt;/span&gt; skipDefaultCheckout&lt;span style="color:#f92672"&gt;(&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;true&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:#75715e"&gt;// no need to pull scm to this agent&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps &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; archiveArtifacts artifacts: &lt;span style="color:#e6db74"&gt;&amp;#39;*.csv&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;onlyIfSuccessful: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&lt;span style="color:#f92672"&gt;,&lt;/span&gt;fingerprint: &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;}&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;I believe there is plugins to compress artifacts as well.&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;At the end I&amp;#8217;d like to reiterate my perception about Jenkins. It is a very generic and adaptive automation platform that originally evolved from use cases in build automation. Due to this original root, many components in Jenkins are named around Continuous Integration use cases, such as the &amp;#8220;build&amp;#8221; button, and the &amp;#8220;archiveArtifacts&amp;#8221; step. These misnomers underplays what Jenkins can potentially do in continuous deployment or other automation scenarios. It is important for automation engineers to understand Jenkins components and plugins, through their functionalities and not by the name, and therefore make creative use of Jenkins as automation engine in all scenarios. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;An alternative to this proposed pipeline would be Ansible Tower, a commercial project based on open-source Ansible, but with nice UI support. Ansible Tower is Ansible oriented, and it does not have everything that Jenkins can do. It should still be a decent alternative given the proposes pipeline uses Ansible a lot.&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-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;Automated Deployment Pipeline 2 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/host-legacy-application-in-docker-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;Host legacy application in Docker 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Automated Deployment Pipeline 2 of 3</title><link>https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/</link><pubDate>Tue, 06 Oct 2020 22:05:00 -0400</pubDate><guid>https://www.digihunch.com/2020/10/automated-deployment-pipeline-2-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;In this posting, we continue to discuss Jenkins&amp;#8217; ability to automate deployment routines. Jenkins supports freestyle project out of the box, as well as Pipeline with several plugins. Freestyle project allows user to specify multiple steps on UI. This does not scale well when your entire process involves many steps. As explained on Jenkins&amp;#8217; website:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;While standard Jenkins “freestyle” jobs support simple continuous integration by allowing you to define sequential tasks in an application lifecycle, they do not create a &lt;strong&gt;persistent record of execution&lt;/strong&gt;, enable one script to address all the steps in a complex workflow, or confer the other advantages of pipelines.&lt;br&gt;In contrast to freestyle jobs, pipelines enable you to define the whole application lifecycle. Pipeline functionality helps Jenkins to support continuous delivery (CD). The Pipeline plugin was built with requirements for a flexible, extensible, and script-based CD workflow capability in mind.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So although freestyle projects are easy to set up, and can do technically everything that Jenkins pipeline can do, the major advantage of Jenkins Pipeline is the ability to manage multiple-step as code, and version control the pipeline-as-code. Here is some more &lt;a href="https://www.jenkins.io/blog/2017/01/19/converting-conditional-to-pipeline/"&gt;information&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will start with a freestyle project to understand Jenkins&amp;#8217; ability and then advance to building pipelines.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-freestyle-projects"&gt;Freestyle projects&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As mentioned, Jenkins calls a task a &amp;#8220;build&amp;#8221;, and the build can be triggered in a variety of ways:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Time schedule (with or without parameter)&lt;/li&gt;&#10;&lt;li&gt;Remotely via API&lt;/li&gt;&#10;&lt;li&gt;On completion of other projects&lt;/li&gt;&#10;&lt;li&gt;Poll SCM for changes&lt;/li&gt;&#10;&lt;li&gt;Commit to SCM&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are also several ways to execute a job:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Execute Shell command, batch command or groovy script&lt;/li&gt;&#10;&lt;li&gt;Invoke Ansible adhoc command, playbook or vault&lt;/li&gt;&#10;&lt;li&gt;Conditional on specified boolean value, file existence, etc&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After the job one can specify post-build jobs, for example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;start other build projects&lt;/li&gt;&#10;&lt;li&gt;notification of various means&lt;/li&gt;&#10;&lt;li&gt;publishing result file&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In deployment, it is common task to execute a task over SSH, using SSH command or invoke Ansible command with Ansible plugin. The former fits simple command line tasks. For example:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="2036" height="738" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-1.png" alt="" class="wp-image-1658" style="width:812px;height:auto"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Ansible plugin is good for more steps and more complicated inventory hierarchies. This &lt;a href="https://www.digihunch.com/2020/05/revamp-ansible-directory-for-scalability-1-of-2/"&gt;post&lt;/a&gt; includes an example of an inventory involving multiple layers. The other limitation with SSH command is lack of a straightforward configuration to escalate privilege and run remote command. On the other hand, Ansible addressed this with become method. Below is a screenshot &lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="950" height="837" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-2.png" alt="" class="wp-image-1659" style="width:785px;height:auto"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In order to use Ansible command, you will also need to specify where the Ansible binary in &lt;a href="https://medium.com/devops-process-and-tools/configure-jenkins-job-with-ansible-jenkins-plugin-to-setup-ci-for-ansible-playbooks-3ed23137d314"&gt;Global Tool Configuration&lt;/a&gt;. You will also need to store vault credential in Jenkins credential store so it&amp;#8217;s not being prompted.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-jenkins-pipeline"&gt;Jenkins Pipeline&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins pipeline allows one to describe actions in a pipeline in groovy &lt;a href="http://docs.groovy-lang.org/docs/latest/html/documentation/core-domain-specific-languages.html"&gt;Domain Specific Language (DSL)&lt;/a&gt;. There are two styles of pipelines:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Declarative pipeline&lt;/strong&gt; is identified by a block named &amp;#8216;pipeline&amp;#8217;, it is relatively new and supports the pipeline-as-code concept. It can be stored as Jenkinsfile in code repository or edited in Jenkins&amp;#8217; UI.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Scripted pipeline&lt;/strong&gt; is identified by a block named &amp;#8216;node&amp;#8217;, it is the conventional format and can only be edited in Jenkins&amp;#8217; UI.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To fully understand the two styles of pipelines you also need to know &lt;a href="https://en.wikipedia.org/wiki/Declarative_programming"&gt;declarative programming&lt;/a&gt; and &lt;a href="https://en.wikipedia.org/wiki/Imperative_programming"&gt;imperative programming&lt;/a&gt;. Read &lt;a href="https://www.jenkins.io/doc/book/pipeline/syntax/"&gt;this&lt;/a&gt; instruction for a better explanation. &lt;a href="https://e.printstacktrace.blog/jenkins-scripted-pipeline-vs-declarative-pipeline-the-4-practical-differences/"&gt;Here&lt;/a&gt; is another good one that focus on their differences.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Jenkins&amp;#8217; plugin provides UI components for user to input information (e.g. Invoke Ansible Command), now with Jenkins pipelines, plugins can provide step functions in order to help Jenkins user. &lt;a href="https://www.jenkins.io/doc/pipeline/steps/"&gt;This&lt;/a&gt; page from Jenkins lists the most common ones, many of which requires plugin installation. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;One example of step function is &lt;a href="https://www.jenkins.io/doc/pipeline/steps/ssh-steps/"&gt;SSH Pipeline Steps&lt;/a&gt;. It allows one to issue SSH connection from Jenkinsfile. However, in our deployment scenarios, it has some limitations:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Unable to match a group of host by specific pattern&lt;/li&gt;&#10;&lt;li&gt;Cannot use host name aliases&lt;/li&gt;&#10;&lt;li&gt;SSH tunneling is not supported&lt;/li&gt;&#10;&lt;li&gt;No means of privilege escalation&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Out of these 1 through 3 are due to the fact that SSH step function does not use the OpenSSH configuration on the machine. The&amp;nbsp;&lt;a href="https://wiki.jenkins-ci.org/display/JENKINS/SSH+Slaves+plugin"&gt;jenkins-ssh-slaves plugin&lt;/a&gt;&amp;nbsp;uses&amp;nbsp;&lt;a href="https://github.com/jenkinsci/trilead-ssh2"&gt;trilead SSH2 implementation&lt;/a&gt;&amp;nbsp;written in Java. Only OpenSSH implementation uses ~/.ssh/config file. This creates problem whenever SSH tunneling is needed. For example, to register a remote slave node via SSH tunneling, we need to select &amp;#8220;Launch agent via &lt;a href="https://support.cloudbees.com/hc/en-us/articles/234491548-Run-SSH-Agent-using-a-command-on-Master"&gt;execution of command on the master&lt;/a&gt;&amp;#8221; instead of &amp;#8220;Launch agent via SSH&amp;#8221; as launch method. &lt;a href="https://support.cloudbees.com/hc/en-us/articles/234491548-Run-SSH-Agent-using-a-command-on-Master"&gt;Here&lt;/a&gt; is an instruction and below is what it looks like:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="727" height="405" src="https://www.digihunch.com/wp-content/uploads/2020/10/image-3.png" alt="" class="wp-image-1698" style="width:755px;height:auto"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similarly, if we use ssh-agent plugin as tool for deployment, we cannot use any configuration made by OpenSSH. Therefore Ansible in Jenkins Pipeline is a better tool for deployment because it can use OpenSSH.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-pipelines-with-ansible"&gt;Pipelines with Ansible&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this section I demonstrate the use of Ansible playbook and adhoc command in pipeline through two examples. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In example 1, the job pulls authorized key file from SCM, and then use a playbook from SCM, to push the key file to all servers in the specified inventory. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here is the playbook file for example 1:&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;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# Example:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;# ansible-playbook -l all -i ~/ansible/inventories/bh.yml push-key.yml --ask-vault-pass&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- name: push key to target&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hosts: all&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tasks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - name: sync key to host&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; copy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; src: &amp;#34;{{authorized_keys_src}}&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dest: &amp;#34;/home/dhunch/.ssh/authorized_keys&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; force: yes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mode: 0600&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Here is the pipeline script (note ampersand is mistakenly displayed as &amp;amp;amp; in the box 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-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;import&lt;/span&gt; java.net.URLEncoder;&#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;pipeline {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent any&#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; options {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; skipDefaultCheckout(&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; }&#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; BITBUCKET_CREDS &lt;span style="color:#f92672"&gt;=&lt;/span&gt; credentials(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;bitbucket&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&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;//BITBUCKET_CREDS_USR and BITBUCKET_CREDS_PSW are set&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BITBUCKET_CREDS_USR &lt;span style="color:#f92672"&gt;=&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;${BITBUCKET_CREDS_USR}&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; BITBUCKET_CREDS_PSW &lt;span style="color:#f92672"&gt;=&lt;/span&gt; URLEncoder.&lt;span style="color:#a6e22e"&gt;encode&lt;/span&gt;(&lt;span style="color:#e6db74"&gt;&amp;#34;${BITBUCKET_CREDS_PSW}&amp;#34;&lt;/span&gt;, &lt;span style="color:#e6db74"&gt;&amp;#34;UTF-8&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:#75715e"&gt;// if password contains special character we need to url encode it. e.g. @-&amp;gt;%40&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; stages {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Start&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Starting pipeline ...&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deleteDir()&#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; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Prep&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wrap(&lt;span style="color:#f92672"&gt;[&lt;/span&gt;$class: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;MaskPasswordsBuildWrapper&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;, varPasswordPairs: &lt;span style="color:#f92672"&gt;[[&lt;/span&gt;password: &lt;span style="color:#e6db74"&gt;&amp;#34;${BITBUCKET_CREDS_PSW}&amp;#34;&lt;/span&gt;, var: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;RANDOM&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&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:#75715e"&gt;// MaskPasswordsBuildWrapper requires Mask Passwords Plugin and is to mask specific string in console output. &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;// Otherwise BITBUCKET_CREDS_PSW will display in the clear&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;git init &amp;amp;&amp;amp; git config core.sparsecheckout true&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;git remote add origin https://$BITBUCKET_CREDS_USR:${BITBUCKET_CREDS_PSW}@bitbucket.org/vendorcompoany/configmanagerepo.git&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;echo &amp;#39;public_keys/*&amp;#39; &amp;gt;&amp;gt; .git/info/sparse-checkout&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#e6db74"&gt;&amp;#34;Downloading key file&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh &lt;span style="color:#e6db74"&gt;&amp;#34;git pull --depth=1 origin master&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; }&#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; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Deploy &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 1&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deploying &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 1 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansiblePlaybook (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; installation: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Ansible on Mac&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; playbook: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;${WORKSPACE}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;public_keys&lt;span style="color:#f92672"&gt;/&lt;/span&gt;push&lt;span style="color:#f92672"&gt;-&lt;/span&gt;key.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;~/&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;/&lt;/span&gt;inventories&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site1.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;-&lt;/span&gt;vault&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pass&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extraVars: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;authorized_keys_src: &lt;span style="color:#e6db74"&gt;&amp;#34;$WORKSPACE/public_keys/authorized_keys&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; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deployed &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 1 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&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; }&#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; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Deploy &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 2&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deploying &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 2 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansiblePlaybook (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; installation: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Ansible on Mac&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; playbook: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;${WORKSPACE}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;public_keys&lt;span style="color:#f92672"&gt;/&lt;/span&gt;push&lt;span style="color:#f92672"&gt;-&lt;/span&gt;key.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;~/&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;/&lt;/span&gt;inventories&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site2.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;-&lt;/span&gt;vault&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pass&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extraVars: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;authorized_keys_src: &lt;span style="color:#e6db74"&gt;&amp;#34;$WORKSPACE/public_keys/authorized_keys&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; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deployed &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 2 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&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; }&#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; stage(&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Deploy &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 3&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deploying &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 3 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansiblePlaybook (&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; installation: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;Ansible on Mac&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; playbook: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;${WORKSPACE}&lt;span style="color:#f92672"&gt;/&lt;/span&gt;public_keys&lt;span style="color:#f92672"&gt;/&lt;/span&gt;push&lt;span style="color:#f92672"&gt;-&lt;/span&gt;key.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;~/&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;/&lt;/span&gt;inventories&lt;span style="color:#f92672"&gt;/&lt;/span&gt;site3.&lt;span style="color:#a6e22e"&gt;yml&lt;/span&gt;&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;ansible&lt;span style="color:#f92672"&gt;-&lt;/span&gt;vault&lt;span style="color:#f92672"&gt;-&lt;/span&gt;pass&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; extraVars: &lt;span style="color:#f92672"&gt;[&lt;/span&gt;authorized_keys_src: &lt;span style="color:#e6db74"&gt;&amp;#34;$WORKSPACE/public_keys/authorized_keys&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; echo &lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;&lt;span style="color:#f92672"&gt;&amp;gt;&lt;/span&gt; Deployed &lt;span style="color:#66d9ef"&gt;to&lt;/span&gt; Site 3 ...&lt;span style="color:#960050;background-color:#1e0010"&gt;&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; }&#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; post {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; always {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cleanWs()&#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;}&#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 example, we pull a sub-directory from git repo. We use two tricks to minimize amount of traffic. First, we use git pull with depth=1 so only the required recent commits are pulled, not the entire history. Second we use sparse checkout to get result from a sub-directory, not the entire repo.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When connecting to repo, password is required. We pull the credential from Jenkins&amp;#8217; credential store and they are masked by default. However, the password must be converted to URL string when used in git remote add origin. Otherwise if the password contains special character the URL will not work. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This modification to password brings about another challenge, password masking during the job execution. We will have to build our own wrapper function using class &lt;em&gt;MaskPasswordsBuildWrapper&lt;/em&gt; in order to mask any variable.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In example 2, we wrap an Ansible step function to call adhoc command to check version on all hosts. Here is the pipeline script:&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;def HunchVersionQuery(pattern, siteinventory){&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; step([&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $class: &amp;#39;AnsibleAdHocCommandBuilder&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ansibleName: &amp;#39;Ansible on Mac&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; inventory: [$class: &amp;#39;InventoryPath&amp;#39;,path: siteinventory],&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; hostPattern: pattern,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; module: &amp;#39;shell&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; command: &amp;#39;cat /etc/*release&amp;#39;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; forks: 1,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vaultCredentialsId: &amp;#39;ansible-vault-pass&amp;#39;&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;pipeline {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; agent any&#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; stages {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; stage(&amp;#39;Start&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo &amp;#39;Staring Pipeline&amp;#39;&#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; stage(&amp;#39;Query Site1&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps { HunchVersionQuery(&amp;#39;*app&amp;#39;,&amp;#39;~/ansible/inventories/site1.yml&amp;#39;)&#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; stage(&amp;#39;Query Site2&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps { HunchVersionQuery(&amp;#39;*app&amp;#39;,&amp;#39;~/ansible/inventories/site2.yml&amp;#39;)&#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; stage(&amp;#39;Query Site3&amp;#39;) {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; steps {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;HunchVersionQuery(&amp;#39;*app&amp;#39;,&amp;#39;~/ansible/inventories/site3.yml&amp;#39;)&#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; }&#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;Because &lt;a href="https://www.jenkins.io/doc/pipeline/steps/ansible/"&gt;Ansible plugin&lt;/a&gt; does not provide a warpper function for adhoc command step, we will have to build our own wrapper function in the above code.&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/automated-deployment-pipeline-1-2/"&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 1 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://www.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/"&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 3 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>