<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Kubernetes API on Digi Hunch</title><link>https://static.digihunch.com/tag/kubernetes-api/</link><description>Recent content in Kubernetes API on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Tue, 08 Apr 2025 14:59:35 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/kubernetes-api/index.xml" rel="self" type="application/rss+xml"/><item><title>EKS impression</title><link>https://static.digihunch.com/2022/12/eks-impression/</link><pubDate>Fri, 23 Dec 2022 18:18:19 -0400</pubDate><guid>https://static.digihunch.com/2022/12/eks-impression/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/eks-impression-feature.webp" alt="Featured image of post EKS impression" /&gt;&lt;p class="wp-block-paragraph"&gt;I&amp;#8217;ve worked on a few &lt;a href="https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/"&gt;AKS projects&lt;/a&gt; previously. Since I joined AWS I wanted to put aside some time to check out EKS (Elastic Kubernetes Service). Here in this post, I put down my first impression on EKS, and also share my Terraform template in &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project to create an EKS cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to AKS, EKS exposes API endpoint and the control plane components are hidden from AWS users. When creating EKS cluster it does not create the underlying VPC and subnets. Therefore, you have create an existing VPC and at least two subnets ahead of time, and specify them during EKS creation. Bear in mind that there is a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/network_reqs.html"&gt;list of requirement&lt;/a&gt; for the VPC and subnets.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the cluster, the CNI that EKS officially supports is Amazon VPC CNI plugin. It is available as an add-on. Similar to Azure CNI, each Pod gets its own IP address. In addition, EKS supports other &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/alternate-cni-plugins.html"&gt;compatible CNI plugins&lt;/a&gt; such as Calico, Cilium, Weave Net and Antrea.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-computing-nodes-in-eks"&gt;Computing Nodes in EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are three modes to address computing capacity: self-managed nodes, EKS managed node groups and AWS Fargate. The documentation has a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/eks-compute.html"&gt;comparison table&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With self-managed nodes, users create EC2 instances separately and then register them to the control plane. The instances must use the same IAM role and AMI. You can use Auto Scaling groups of &lt;a href="https://aws.amazon.com/bottlerocket/"&gt;Bottlerocket&lt;/a&gt; (AWS-sponsored purpose-built Linux distro for container host) nodes. The self-managed node option is mostly for AWS outpost customers who bring in their own computing capacity from data centre.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you provision computing capacity from AWS, it makes sense to assign EKS managed node groups when creating EKS cluster. We can turn on &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/autoscaling.html"&gt;Cluster Autoscaler&lt;/a&gt;, a Kubernetes construct to manage the auto scaling of node groups. Sometimes we want to have more than one node groups. For example, to build a multi-architecture cluster, we need one node group with amd64 nodes and the other with arm64 nodes (e.g. instances with &lt;a href="https://aws.amazon.com/ec2/graviton/"&gt;Graviton&lt;/a&gt; processor). In general, arm-based CPU delivers better performance with less power consumption and the industry is slowly moving towards more arm-based CPU architecture.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Fargate is what I call managed computing service for EKS. With Fargate you do not need to tweak Cluster Autoscaler to self-manage computing capacity. The Fargate documentation has a long list of &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate.html"&gt;considerations&lt;/a&gt;. For example, Pods must match a Fargate profile (&lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/eks/cluster-fargate.yaml"&gt;here&lt;/a&gt;&amp;#8216;s an example) at the time that they&amp;#8217;re scheduled to run on Fargate. So we need to build Fargate profile and Pod labelling properly. Also, Fargate does not support DaemonSet. Another big consideration is that Fargate does not support non-VPC CNI. In my opinion these are pretty significant limitations. Many workloads (system-level or application-level) would need Daemonset (e.g. kube-proxy, some CNI or CSI drivers, &lt;a href="https://www.dynatrace.com/support/help/setup-and-configuration/setup-on-container-platforms/kubernetes/get-started-with-kubernetes-monitoring/set-up-k8s-monitoring-daemonset"&gt;Dynatrace&lt;/a&gt; monitoring). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The pro of Fargate is the serverless computing model. The construct of a Fargate profile isn&amp;#8217;t complicated. You just specify subnets, namespace and labels. However, the downside is the long list of considerations. Some teams may consider these restrictions too much. The other overhead is the need to manage Fargate profile to ensure all Pods are scheduled somewhere. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To me, using Fargate alone impairs portability of workload. The good thing is that Fargate and Managed Node Group are not mutually exclusive on a cluster. In most cases, we can go partially serverless, and reap the benefits of both of them. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Node AutoScaling&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For workloads that don&amp;#8217;t have a matching Fargate profile, we have to figure out node autoscaling ourselves. I touched on Cluster Autoscaler in &amp;#8220;&lt;a href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;Autoscaling on Kubernetes Platform&lt;/a&gt;&amp;#8220;. CA works on AWS as well and is triggered upon a Pod coming to &lt;em&gt;unschedulable&lt;/em&gt; status in Scheduler. There is some limitations though. For example, CA interacts with Autoscaling Group (instead of EC2 instances directly). When it determines it&amp;#8217;s time to scale up, it bumps up the desired capacity by one at a time in the Autoscaling group. The configurations in Autoscaling group may also be at play and CA do not have direct control. For example, the &amp;#8220;&lt;a href="https://docs.aws.amazon.com/autoscaling/ec2/userguide/ec2-auto-scaling-scaling-cooldowns.html"&gt;scaling cooldown&lt;/a&gt;&amp;#8220;. The pool of nodes is homogenous as per the pre-configured launch template and CA has no control. If a Pod requires a different type of node (e.g. ARM64 CPU, spot instance, etc), then we&amp;#8217;d first have to create a node group with the desired node type. Moreover, in the worst cases, one-at-a-time scale-up does not meet the increase of demand driven by Pod increases, causing nuances such as racing conditions. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because the Cluster Autoscaler doesn&amp;#8217;t really deal with the nodes themselves, this kind of integration is clunky and slow. Nearly half of Kubernetes customers on AWS report that configuring cluster auto scaling using the Kubernetes Cluster Autoscaler is challenging and restrictive, according to &lt;a href="https://aws.amazon.com/blogs/aws/introducing-karpenter-an-open-source-high-performance-kubernetes-cluster-autoscaler/"&gt;this&lt;/a&gt; blog post. As a result, AWS launched an open-source cluster autoscaler project, &lt;a href="https://karpenter.sh/"&gt;Karpenter&lt;/a&gt;. Karpenter first only supported EKS but now the support includes other CSPs. For EKS, Karpenter directly interact with different types of EC2 instances.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Karpenter makes node scaling work in a more cloud-native manner. In the presence of unschedulable Pods, Karpenter &lt;span style="text-decoration: underline" class="underline"&gt;bypasses the Kubernetes scheduler&lt;/span&gt; and works directly with the Cloud provider, to launch the minimal compute resources needed to fit those Pods and immediately binds the Pods to the newly provisioned Nodes without waiting for scheduler. As Pods are removed or rescheduled to other nodes, Karpenter looks for opportunities to terminate under-utilized nodes. Karpender defines a CR called Provisioner to specify node provisioning configuration, such as instance size, zone, CPU architecture, etc. It is a manifest that describes a node group so the node scaler is aware of all the available node types. You can have multiple Provisioners for different needs, just like node groups. The Provisioner CR can also set TTL for empty Nodes, such that once a Node has no pods other than DaemonSet, Karpenter will terminate the Node on TTL expiry.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Karpenter&amp;#8217;s idea is similar to the idea of AutoPilot cluster in GKE. The new EKS workshop has an &lt;a href="https://www.eksworkshop.com/docs/autoscaling/compute/karpenter/"&gt;section&lt;/a&gt; on how to set up CA and Karpenter in practice.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-identity-management-for-eks"&gt;Identity Management for EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For IAM, we need to be concerned with three aspects. The management traffic to the cloud service, the management traffic for Kubernetes cluster and business traffic. &lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background has-fixed-layout"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;&lt;strong&gt;Traffic type&lt;/strong&gt;&lt;/th&gt;&lt;th&gt;AWS&lt;/th&gt;&lt;th&gt;Azure&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;I. Cloud Service Endpoint (Management Traffic for Cloud Service)&lt;/td&gt;&lt;td&gt;AWS IAM identity&lt;/td&gt;&lt;td&gt;Azure RBAC&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;II. Kubernetes API (Management Traffic for K8s Cluster)&lt;/td&gt;&lt;td&gt;IAM mapping or OIDC&lt;/td&gt;&lt;td&gt;Azure RBAC (implementation of OIDC)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;III. Business traffic&lt;/td&gt;&lt;td&gt;Up to Kubernetes Ingress&lt;/td&gt;&lt;td&gt;Up to Kubernetes Ingress&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For business traffic (type III), it is all up to the Ingress. I&amp;#8217;ve written another article on &lt;a href="https://medium.com/slalom-build/managing-ingress-traffic-on-kubernetes-platforms-ebd537cdfb46"&gt;managing ingress traffic on Kubernetes platforms&lt;/a&gt;. We interact with cloud service endpoint (type II) with either AWS CLI or Terraform, to create any object, including resources needed for a cluster. This is generally how we work with cloud service, not specific to Kubernetes. Usually the IAM identity assumes another IAM role, which empowers it with a lot of permissions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For access to Kubernetes API (type III), EKS supports OIDC and IAM mapping. AWS documentation refers to this as &amp;#8220;&lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/cluster-auth.html"&gt;Cluster Authentication&lt;/a&gt;&amp;#8220;. There is one special scenario where your identity for type II access inherits your identity for type I access. As the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html"&gt;document&lt;/a&gt; puts:&lt;/p&gt;&#10;&lt;blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow"&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When you create an Amazon EKS cluster, the AWS Identity and Access Management (IAM) entity user or role, such as a&amp;nbsp;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_providers.html"&gt;federated user&lt;/a&gt;&amp;nbsp;that creates the cluster, is automatically granted&amp;nbsp;&lt;code&gt;system:masters&lt;/code&gt;&amp;nbsp;permissions in the cluster&amp;#8217;s role-based access control (RBAC) configuration in the Amazon EKS control plane. This IAM entity doesn&amp;#8217;t appear in any visible configuration, so make sure to keep track of which IAM entity originally created the cluster.&amp;nbsp;&lt;/p&gt;&#10;&lt;/blockquote&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This special scenario (I call it the &amp;#8220;&lt;strong&gt;implicit master&lt;/strong&gt; &lt;strong&gt;user&lt;/strong&gt;&amp;#8220;) allows us to perform critical activities on the cluster, such as creating IAM mapping, or OIDC configuration. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The above addressed how AWS resource access Kubernetes resource. On the other hand, to address how a Kubernetes resource access AWS resources, we need IRSA (&lt;a href="https://docs.aws.amazon.com/emr/latest/EMR-on-EKS-DevelopmentGuide/setting-up-enable-IAM.html"&gt;IAM Roles for Service Account&lt;/a&gt;). We have a service account in Kubernetes and map it to an IAM role.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;AppMesh&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://aws.amazon.com/blogs/compute/introducing-aws-app-mesh-service-mesh-for-microservices-on-aws/"&gt;AppMesh&lt;/a&gt; is AWS&amp;#8217; Envoy based service-mesh offering supporting Kubernetes cluster, ECS service and even EC2 instance. AppMesh&amp;#8217;s control plane is a managed AWS service, with a &lt;a href="https://aws.github.io/aws-app-mesh-controller-for-k8s/"&gt;controller&lt;/a&gt; running on the Kubernetes cluster. To install AppMesh on the cluster:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;On the EKS cluster, install AppMesh Controller using Helm&lt;/li&gt;&#10;&lt;li&gt;Associate the cluster with IAM OIDC provider&lt;/li&gt;&#10;&lt;li&gt;Create an IAM role for the appmesh-controller service account&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After these steps, you can create a mesh using CloudFormation, Terraform, etc. The data plane (Envoy proxy) can run on Kubernetes (as sidecar). Traffic between control plane and data plane can go through private link (Interface VPC &lt;a href="https://docs.aws.amazon.com/app-mesh/latest/userguide/infrastructure-security.html"&gt;endpoint&lt;/a&gt;) for added security. Like Istio, AppMesh enables mTLS. For observability, you can export Envoy metrics with Prometheus. Coupled with XRay, AppMesh also supports distributed tracing.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="832" height="354" src="https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway.webp" alt="" class="wp-image-12883" srcset="https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway.webp 832w, https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway-300x128.webp 300w, https://static.digihunch.com/wp-content/uploads/2022/12/appmesh-virtualgateway-768x327.webp 768w" sizes="auto, (max-width: 832px) 100vw, 832px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AppMesh uses a different set of CRDs than Istio. Key CRDs are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Mesh&lt;/strong&gt;: represents an entire service mesh. At mesh level you can configure Egress filter (to allow or deny external traffic) and set IP version (v4 vs v6)&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualGateway&lt;/strong&gt;: a CRD that represents an &lt;a href="https://aws.amazon.com/blogs/containers/introducing-ingress-support-in-aws-app-mesh/"&gt;Ingress&lt;/a&gt; in to the Mesh. A virtual gateway allows resources that are outside of your mesh to communicate to resources that are inside of your mesh. A virtual gateway references Envoy proxy deployment by podSelector. It references GatewayRoutes by namespaceSelector, and optionally gatewayRouteSelector. You also specify listeners in the manifest to reference Envoy proxy Service (LoadBalancer Type).&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;GatewayRoute&lt;/strong&gt;: A gateway route is attached to a virtual gateway and routes traffic to an existing virtual service. If a route matches a request, it can distribute traffic to a target virtual service. In the manifest, you specify a list of httpRoute, each with matching condition and action. In the action section you can specify virtualService as target.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualService&lt;/strong&gt;: an abstraction of a real service provided by a virtual node directly or indirectly by means of a virtual router. Dependent services call your virtual service by its virtualServiceName, and those requests are routed to the VirtualNode or VirtualRouter that is specified as the provider for the VirtualService.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualRouter&lt;/strong&gt;: Virtual routers handle traffic for virtual services. In a virtual router manifest, you can define Route to direct incoming requests to virtual nodes as target.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;VirtualNode&lt;/strong&gt;: A virtual node acts as a logical pointer to a particular task group (i.e. ECS service, Kubernetes deployment). It represent a Service in the AppMesh. In the manifest, you reference Pods by podSelector, specify listeners for any inbound traffic that your virtual node expects, and specify serviceDiscovery for your task group.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can configure those Custom Resources using YAML manifests (and check the &lt;a href="https://docs.aws.amazon.com/app-mesh/latest/APIReference/Welcome.html"&gt;API reference&lt;/a&gt; a lot). Alternatively, you can configure them from AWS CLI or AWS console. The console will help you visualize what can be configured. For further details on how these CRs play together, there is a &lt;a href="https://www.appmeshworkshop.com/"&gt;workshop&lt;/a&gt; for AppMesh.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;EKS cluster using Terraform&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Officially, there is an &lt;a href="https://github.com/aws-ia/terraform-aws-eks-blueprints"&gt;EKS blueprint&lt;/a&gt; project for provisioning EKS cluster in Terraform.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I also keep my own Terraform code in the AWS directory of &lt;a href="https://github.com/digihunch/cloudkube"&gt;cloudkube&lt;/a&gt; project. It works out to be a little more complex than my Terraform template to create Azure Kubernetes Cluster (Azure directory). Because I had to create Cognito resources with initial credential to allow users to connect to cluster without using the implicit master account.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Below is the diagram of the processes.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="776" height="496" src="https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod.webp" alt="" class="wp-image-12881" srcset="https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod.webp 776w, https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod-300x192.webp 300w, https://static.digihunch.com/wp-content/uploads/2022/12/eks-tf-mod-768x491.webp 768w" sizes="auto, (max-width: 776px) 100vw, 776px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Create EKS cluster with Terraform module&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The template configures kubectl access on a Bastion host, which assumed the same role that our IAM user uses to create the Kubernetes cluster. Therefore, the IAM role is the master identity. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that the IAM user (power-user) has very powerful permissions. Usually it is ideal to assign lots of permission to IAM Roles (temporary credential) instead of IAM user (long-term credential). So the &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html"&gt;role chaining&lt;/a&gt; would look like:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The IAM user that Terraform uses has no permission other than assuming a &amp;#8220;PowerUser&amp;#8221; role&lt;/li&gt;&#10;&lt;li&gt;The PowerUser role trusts the IAM user. It also has the permission to assume the &amp;#8220;EKS-Manager&amp;#8221; role&lt;/li&gt;&#10;&lt;li&gt;The EKS-Manager role trusts PowerUser&amp;#8217;s role session.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, the role chaining scenario above is not currently supported in &lt;a href="https://github.com/hashicorp/terraform-provider-aws/issues/22728"&gt;Terraform&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use a Bastion host because the cluster endpoint is on private subnet. The bastion host is on a public subnet. However, if we do not like public subnet and public IP, we can place the bastion host on a private subnet, and use SSM system manager agent with &lt;a href="https://aws.amazon.com/premiumsupport/knowledge-center/systems-manager-ssh-vpc-resources/"&gt;SSH tunnel plugin &lt;/a&gt;to have SSH access to private bastion host.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I first came across &lt;a href="https://medium.com/@andreidascalu/the-awfulness-of-aws-eks-d7700c1eccdc"&gt;this&lt;/a&gt; article about EKS and its awfulness and then decided to check out EKS. I&amp;#8217;m not sure all points are still valid but it&amp;#8217;s generally real-life experiences. There are also many peripheral services, such as AMP (AWS Managed Prometheus), AMG (AWS Managed Grafana), ADOT (AWS Distro for Open Telemetry), AppMesh (Another &lt;a href="https://www.appmeshworkshop.com/introduction/appmesh_components/"&gt;Envoy-based Service Mesh&lt;/a&gt;, &lt;a href="https://vedcraft.com/architecture/aws-appmesh-vs-istio-comparison-of-service-mesh/"&gt;easier to manage than Istio&lt;/a&gt; but less Powerful), with a lot to explore.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/12/landing-zone-in-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Landing Zone in AWS – An Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Optimize CPU and Memory for Kubernetes Pod&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Kubernetes Operator</title><link>https://static.digihunch.com/2022/04/kubernetes-operator/</link><pubDate>Thu, 07 Apr 2022 09:39:00 -0400</pubDate><guid>https://static.digihunch.com/2022/04/kubernetes-operator/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-operator.webp" alt="Featured image of post Kubernetes Operator" /&gt;&lt;p class="wp-block-paragraph"&gt;Kubernetes has a number of tools to automate the deployment of a single workload. In previous posts, we had covered &lt;a href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;Helm&lt;/a&gt; and &lt;a href="https://static.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;Kustomize&lt;/a&gt;. What are left unresolved is how to maintain the status of workload after deployment is completed. In this post, I will give an introduction to Kubernetes Operator. Compared with Helm (templating approach) and Kustomize (patching approach), Kubernetes Operator follows the &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/"&gt;operator pattern&lt;/a&gt;. Operators are usually provided by the developer of the application.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-operator-pattern"&gt;Operator Pattern&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, we know that a controller takes care of routine tasks to ensure that desired state expressed by Kubernetes resource types matches the current state. One example is that the Deployment controller ensures the number of pods running matches the amount specified in the replica field. Controller is the key to ensure that resources can be managed by declarative manifests for Kubernetes resources. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes makes use of controller pattern throughout its own design. One of its key component, Controller Manager, is a collection of many controllers. Each controller is in charge of a control loop, responsible for listening the object it manages. Another component, Kube-scheduler, is also a special type of Controller. The kube-scheduler monitors unscheduled Pod and health of nodes and determines the best Node to schedule the new Pod to. Then it writes the decision to etcd store for kubelet to execute.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This controller pattern is fairly successful in what it does and we can extend the use of it. Beyond the built-in resource types, we can create our own custom resource definitions (CRDs), and create controllers that watches for the manifest that declares custom resources (CRs). The controller ensures that the resource status matches their specifications. This is also known as reconciliation, which is implemented as a control loop. Operator pattern can be illustrated in the diagram below:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/cncf/tag-app-delivery/raw/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/img/02_1_operator_pattern.png" alt="Operator Design Pattern"/&gt;&lt;figcaption class="wp-element-caption"&gt;Operator Pattern&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Technically, there is no difference between a controller and an operator. What makes an Operator (used to install workload) different than a native Kubernetes controller, are two things. First, an Operator usually needs CRDs because the built-in resource types are insufficient. Second, the operator reflects the domain knowledge to keep the target workload running. For example, stateful workloads such as database needs their operational steps executed in certain orders.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On &lt;a href="https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md"&gt;Operator Pattern&lt;/a&gt;, CNCF published a &lt;a href="https://www.cncf.io/wp-content/uploads/2021/07/CNCF_Operator_WhitePaper.pdf"&gt;whitepaper&lt;/a&gt; with a deeper review. This white paper is the best reference for a good understanding of the Operator Pattern.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Custom Resource Definition&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The built-in controllers work with built-in objects (pre-defined APIs). Custom operators usually need their own APIs to function. To extend Kubernetes API, we define the schema of these APIs in the form of CRDs (&lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules"&gt;custom resource definitions&lt;/a&gt;) using &lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation"&gt;OpenAPIv3&lt;/a&gt; standard. Then, we can declare Custom Resources (CRs) in compliance with the schema. The OpenAPIv3 schema in the CRD resource tells validating web hook (&lt;a href="https://static.digihunch.com/2022/01/kubernetes-admission-control/"&gt;admission control&lt;/a&gt;) how to validate the schema when we send an CR in to API server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we work with third-party operators, they usually provide CRDs along with the operator implementation. For example, in my &lt;a href="https://github.com/digihunch/wordpress-operator"&gt;operator example&lt;/a&gt; project, we have a minimalist CRD &lt;a href="https://github.com/digihunch/wordpress-operator/blob/main/config/crd/bases/wordpress.digihunch.com_wordpresses.yaml"&gt;WordPress&lt;/a&gt; with one property: sqlRootPassword and we can declare a CR as in &lt;a href="https://github.com/digihunch/wordpress-operator/blob/main/config/samples/wordpress_v1_wordpress.yaml"&gt;this&lt;/a&gt; example. For a more realistic use case, we can take a look at &lt;a href="https://github.com/kiali/kiali-operator/blob/master/crd-docs/crd/kiali.io_kialis.yaml"&gt;Kiali CRD&lt;/a&gt;. In the next section, we&amp;#8217;ll use it along with Kiali operator to install Kiali. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operator Usage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Like &lt;a href="https://artifacthub.io/"&gt;Artifact Hub&lt;/a&gt; to Helm, &lt;a href="https://operatorhub.io/"&gt;OperatorHub&lt;/a&gt; is a public registry of most used Kubernetes Operators. In this section, we will take an example of using Operators. We will install Kiali as an add-on to Istio using Kiali CR and operator, which also depends on Prometheus to be installed using Prometheus Operator first. Note that the Kiali installation outlined in this section is not the the &lt;a href="https://istio.io/latest/docs/ops/integrations/kiali/#option-1-quick-start"&gt;quick-start&lt;/a&gt; install manifests from Istio&amp;#8217;s &lt;a href="https://github.com/istio/istio/tree/master/samples/addons"&gt;sample&lt;/a&gt; directory. For Kiali on production system we have to customize the &lt;a href="https://kiali.io/docs/installation/installation-guide/"&gt;installation&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose we have installed Istio, we can then install Prometheus operator using Helm. The Prometheus operator will install Prometheus. Then we use Helm again to install Kiali operator. The Kiali operator will watch for creation of Kiali CRD, to deploy services:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ helm install -f prometheus-values.yaml --namespace istio-system --repo https://prometheus-community.github.io/helm-charts --version 13.6.0 istio-prometheus prometheus --insecure-skip-tls-verify&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ helm install -f kiali-operator-values.yaml --namespace kiali-operator --repo https://kiali.org/helm-charts --version 1.45.0 kiali-op kiali-operator --create-namespace&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f kiali-cr.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;I include example content for each file in the commands above on Github gist (&lt;a href="https://gist.github.com/digihunch/448180c019310a5dadb700c1bcdb0772"&gt;prometheus-values.yalm&lt;/a&gt;, &lt;a href="https://gist.github.com/digihunch/5574aba4aa9fc1aa15257bd6e811bf5b"&gt;kiali-operator-values.yaml&lt;/a&gt; and &lt;a href="https://gist.github.com/digihunch/2fd0884f5999416c8baf4197ee5790f3"&gt;kiali-cr.yaml&lt;/a&gt;). For more options for installing Kiali, refer to &lt;a href="https://kiali.io/docs/installation/installation-guide/install-with-helm/"&gt;their&lt;/a&gt; documentation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use this example to install Kiali and it includes two Operators, the Prometheus Operator and the Kiali Operator. The Prometheus Operator is one of the first ever written Kubernetes Operator. As soon as the operator is deployed, it starts to deploy the operator service. For the Kiali operator, we need to deploy Kiali CR after the Kiali Operator has been deployed. Both are valid patterns.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operator Development&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Operator is powerful. However, authoring an Operator is not a trivial effort. One usually start with a framework. A framework creates a body of boiler plate code that has the pattern implemented and allows developers to enrich the functions following the pattern. The white paper introduced three frameworks:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;CNCF &lt;a href="https://operatorframework.io/"&gt;Operator Framework&lt;/a&gt; &amp;#8211; aims at Operator Developers with an SDK, a scaffolding tool and a test harness. It currently supports three project types: Golang, Helm and Ansible. CNCF Operator framework consists of SDK and OLM. &lt;/li&gt;&#10;&lt;li&gt;Kopf (Kubernetes Operator Pythonic Framework) &amp;#8211; an easy-to-use framework in Python that abstracts away most of the low-level Kubernetes API communications hassle.&lt;/li&gt;&#10;&lt;li&gt;kubebuilder &amp;#8211; helps build a Manager similar to the native kube-controller-manager. For difference with OperatorSDK, read &lt;a href="https://sdk.operatorframework.io/docs/faqs/#what-are-the-the-differences-between-kubebuilder-and-operator-sdk"&gt;here&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Metacontroller: lightweight Kubernetes Controller as a Service&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://www.cncf.io/projects/operator-framework/"&gt;CNCF&lt;/a&gt; Operator Framework, the Operator SDK supports development using &lt;a href="https://sdk.operatorframework.io/docs/building-operators/ansible/"&gt;Ansible&lt;/a&gt;, &lt;a href="https://sdk.operatorframework.io/docs/building-operators/helm/"&gt;Helm&lt;/a&gt; and &lt;a href="https://sdk.operatorframework.io/docs/building-operators/"&gt;Golang&lt;/a&gt;. The author of &lt;a href="https://www.velotio.com/engineering-blog/getting-started-with-kubernetes-operators-helm-based-part-1"&gt;this&lt;/a&gt; post makes a general comparison as follows:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Type &lt;/th&gt;&lt;th&gt;Best use case&lt;/th&gt;&lt;th&gt;Underlying technology&lt;/th&gt;&lt;th&gt;Amt of Effort&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Helm&lt;/td&gt;&lt;td&gt;Stateless workload&lt;/td&gt;&lt;td&gt;Helm Charts&lt;/td&gt;&lt;td&gt;Med&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ansible&lt;/td&gt;&lt;td&gt;Stateless workload&lt;/td&gt;&lt;td&gt;Ansible Roles and Playbooks&lt;/td&gt;&lt;td&gt;Med&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Golang&lt;/td&gt;&lt;td&gt;Stateful workload&lt;/td&gt;&lt;td&gt;Code developed in Golang&lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The aforementioned &lt;a href="https://github.com/kiali/kiali-operator"&gt;Kiali operator&lt;/a&gt; is an example of Operator developed in Ansible. The &lt;a href="https://github.com/prometheus-operator/prometheus-operator"&gt;prometheus operator&lt;/a&gt;, is developed in Golang as the workload can be stateful depending on configuration. One needs to know how to develop operator in Golang in order to tackle the most complicated situations. This is requires some serious development effort. The documentation with a quick start section is available &lt;a href="https://sdk.operatorframework.io/docs/building-operators/golang/quickstart/"&gt;here&lt;/a&gt;. Even that is not very straightforward. RedHat, the maintainer of the CNCF &lt;a href="https://cloud.redhat.com/learn/topics/operators"&gt;Operator&lt;/a&gt; framework has a good blog &lt;a href="https://developers.redhat.com/articles/2021/08/04/managing-stateful-applications-kubernetes-operators-golang#"&gt;post&lt;/a&gt; on how to develop an Operator in Golang. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The example requires some development knowledge to go through. On my MacOS (Intel) I have to configure the following prerequisites:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Install gcc, using command: xcode-select &amp;#8211;install&lt;/li&gt;&#10;&lt;li&gt;Install the right version of golang. You can find the version &lt;a href="https://sdk.operatorframework.io/docs/contribution-guidelines/developer-guide/#prerequisites"&gt;here&lt;/a&gt;. The MacOS has a version of golang installed already so I had to install version 1.17 and link to it: brew install go@1.17 &amp;amp;&amp;amp; brew link &amp;#8211;force go@1.17&lt;/li&gt;&#10;&lt;li&gt;Install operator-sdk with home brew: brew install operator-sdk&lt;/li&gt;&#10;&lt;li&gt;When you run &amp;#8220;operator-sdk version&amp;#8221;, ensure the result shows a golang version that matches your installation.&lt;/li&gt;&#10;&lt;li&gt;If you need to push docker image, also connect to docker registry by running: docker login&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can create our working directory, initialize the repository and create boilerplate code (scaffolding) with these commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ mkdir wordpress-operator &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cd wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ operator-sdk init --domain digihunch.com --repo github.com/digihunch/wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ operator-sdk create api --group wordpress --version v1 --kind WordPress --resource --controller&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With the repo initialized, we can go to the section &amp;#8220;Defining the API&amp;#8221; and &amp;#8220;Implementing the Controller&amp;#8221;. The blog post does not cover every code editing needed to bring up wordpress. You are supposed to go to the author&amp;#8217;s &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest"&gt;repository&lt;/a&gt; to fit the changes into your own repo. The author&amp;#8217;s repo has a few more &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/tree/master/controllers"&gt;controllers&lt;/a&gt; such as &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/blob/master/controllers/common.go"&gt;common.go&lt;/a&gt; and &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/blob/master/controllers/mysql.go"&gt;mysql.go&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At the end of the lab, you should be able to run the controller and bring up wordpress. I used my own &lt;a href="https://github.com/digihunch/wordpress-operator"&gt;repository&lt;/a&gt; for this lab and have made the code changes for this lap in a couple &lt;a href="https://github.com/digihunch/wordpress-operator/commit/5540d7e045bf4da1ea1d140f1b9fd189fd9f2cc9"&gt;commits&lt;/a&gt;. To test locally with the code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git clone git@github.com:digihunch/wordpress-operator.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cd wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ make install run&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can validate wordpress install from a new terminal as the instruction shows:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl create -f config/samples/wordpress_v1_wordpress.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ minikube service wordpress --url&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For Developers that requires more details, RedHat has an &lt;a href="https://www.redhat.com/cms/managed-files/cl-oreilly-kubernetes-operators-ebook-f21452-202001-en_2.pdf?extIdCarryOver=true&amp;amp;sc_cid=701f2000001Css5AAC"&gt;eBook&lt;/a&gt; for Kubernetes Operators, in supplement to the &lt;a href="https://cloud.redhat.com/learn/topics/operators"&gt;documentation&lt;/a&gt;. As DevOps professional, I&amp;#8217;m mainly concerned with understanding how Operator works and using Operators correctly.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Too many Tools?&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we seem to have too many choice of tools when it comes to deploying workload on Kubernetes. Kustomize and Helm can deploy simple workloads. Operator can deploy stateful workloads, as well as keep the workload status in check. Further, we have FluxCD and ArgoCD based on GitOps workflow.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When assessing a tool, we should think about the complexity of the workload deployed. If it is a single stateless workload, Kustomize or Helm should be sufficient. If it is not very simple but still stateless, we can consider using Helm charts developed by the community. For multiple workloads, we can build our own top-level chart to combine existing sub-charts created by the community.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm is essentially a package manager. It does not follow controller pattern and therefore will not monitor the current status of deployment. Helm has other limitations compared to Operator. For example, as a templating scheme, it reaches limitation when dealing with complex logic, even with the help of its helper functions. It is also hard to reason through the template code when we have to troubleshoot a deployment. Refer to &lt;a href="https://thenewstack.io/we-pushed-helm-to-the-limit-then-built-a-kubernetes-operator/"&gt;this&lt;/a&gt; blog post for the author&amp;#8217;s experience with Helm.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we want our deployment to be fully declarative and continuous, then we will follow the Operator pattern by using a Kubernetes Operator. When we have many workloads of different levels of complexity, we can combine them with GitOps tool. Operator is one of the underlying technologies behind GitOps.&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background has-fixed-layout"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Workload profile&lt;/th&gt;&lt;th&gt;Just Installation&lt;/th&gt;&lt;th&gt;Installation and Maintain Status&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Single stateless workload&lt;/td&gt;&lt;td&gt;Helm or Kustomize&lt;/td&gt;&lt;td&gt;Operator (using Ansible or Helm)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Single stateful workload&lt;/td&gt;&lt;td&gt;Helm or Kustomize&lt;/td&gt;&lt;td&gt;Operator (using Golang)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multiple workloads&lt;/td&gt;&lt;td&gt;Helm (e.g. build parent chart)&lt;/td&gt;&lt;td&gt;GitOps in combination with Operator, Helm and Kustomize&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The table above helps refine deployment requirement. It&amp;#8217;s not a recommendation, but rather a model of analyzing deployment requirement.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Autoscaling on Kubernetes Platform&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/04/knative-introduction-serving/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Serving Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Local multi-node cluster – Minikube, MicroK8s and KinD</title><link>https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/</link><pubDate>Tue, 14 Sep 2021 11:18:00 -0400</pubDate><guid>https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-multi-node-k8s.webp" alt="Featured image of post Local multi-node cluster – Minikube, MicroK8s and KinD" /&gt;&lt;p class="wp-block-paragraph"&gt;In this post we compare Minikube, MicroK8s and KinD as different approaches to build multi-node cluster locally.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="is-docker-desktop-bad"&gt;Is Docker desktop bad?&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/"&gt;previous post&lt;/a&gt; about docker desktop as a single-node Kubernetes cluster setup, I touched on the deprecation of docker-shim. Now that CRI beats OCI as the standard for container runtime, the docker runtime will no longer be supported by Kubernetes. Also deprecated is docker-shim, the temporary interface that had make Docker runtime work in Kubernetes. This was announced in December 2020, and is coming through in Kubernetes 1.23, expected Oct 2021. However, docker desktop still uses docker runtime in it&amp;#8217;s single-node Kubernetes cluster. This essentially renders itself a non-compliant Kubernetes environment. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker desktop still has great value for application developers. If your role is development, spending a lot of time coding business logics and need an easy-to-use container runtime on your laptop, Docker desktop is a good choice. The recent &lt;a href="https://www.docker.com/blog/updating-product-subscriptions/"&gt;moves&lt;/a&gt; by the company seems to suggest that this is the business they are targeting now. On the other hand, if your roles are deployment, automation, orchestration, cloud native etc and you are looking for a playground, most likely you do need a runtime compliant to Kubernetes CRI. Docker desktop is not a &lt;a href="https://www.cncf.io/certification/software-conformance/"&gt;CNCF-certified project&lt;/a&gt; anymore, and it is not your choice. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="alternatives"&gt;Alternatives&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are a number of alternatives, the most well-known ones are Minikube, MicroK8s, KinD and K3s with K3d. &lt;a href="https://www.cncf.io/wp-content/uploads/2020/08/CNCF-Webinar-Navigating-the-Sea-of-Local-Clusters-.pdf"&gt;This &lt;/a&gt;presentation from CNCF in 2020 covers a lot of details about these technologies. I&amp;#8217;ll try to add my opinion.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://rancher.com/docs/k3s/latest/en/"&gt;K3s&lt;/a&gt; is Rancher Lab&amp;#8217;s lightweight Kubernetes distribution that supports multi-node cluster as well as different runtimes (e.g. containerd). It is not straightforward to setup, and &lt;a href="https://k3d.io/"&gt;k3d&lt;/a&gt; is an command-line wrapper to make it easy to install K3s cluster. K3s was accepted as a &lt;a href="https://www.cncf.io/projects/k3s/"&gt;CNCF project &lt;/a&gt;but only at Sandbox maturity level, so it is not my choice. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The other three: Minikue, MicroK8s and KinD are all certified CNCF project. I will further discuss how to choose among them. These projects are technologies that takes different approach to address the challenges with deploying multiple nodes in local environment (e.g. my laptop). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The challenge with running a Kubernetes cluster with multiple nodes locally is how to manage these nodes. They are separate virtual resources that need to be isolated from computing perspective, and connected as a cluster. This is typically the use case of a Type II &lt;a href="https://static.digihunch.com/2020/07/overview-of-virtualization/"&gt;hypervisor&lt;/a&gt;, or alternatively, it can also be implemented with container technology. This layer of technology (referred to as drivers) makes a big difference.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="minikube"&gt;Minikube&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Minikube supports multiple drivers. Depending on your platform (Windows, Linux, or MacOS), the preferred driver is different. Refer to the document &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/"&gt;here&lt;/a&gt; for preferred driver, and this blog &lt;a href="https://kubernetes.io/blog/2019/03/28/running-kubernetes-locally-on-linux-with-minikube-now-with-kubernetes-1.14-support/"&gt;post&lt;/a&gt; for more instructions. In addition to the documents, here some notes from my personal experience:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;On MacOS, &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/"&gt;Minikube&lt;/a&gt; lists Docker as preferred driver. I disagree with that. If you have no other reason to install &lt;strong&gt;Docker&lt;/strong&gt;, then I would recommend &lt;strong&gt;hyperkit&lt;/strong&gt; as the the preferred driver. Hyperkit can be installed with a simple &lt;strong&gt;Homebrew&lt;/strong&gt; command. For two reasons I do not recommend Docker as the driver of Minikube. First, it requires a separate installation of Docker Desktop, which includes a built-in instance of &lt;strong&gt;hyperkit&lt;/strong&gt; on its own. This isn&amp;#8217;t neat. Second, I often need Metal LB add-on with Minikube for testing Kubernetes Ingress. With Minikube on Docker, the Ingress ports are not exposed to MacOS&amp;#8217;s. Therefore you cannot directly visit websites spun up on Minikube. This is a &lt;a href="https://github.com/kubernetes/minikube/issues/7332"&gt;known issue&lt;/a&gt; for a while due to &lt;a href="https://github.com/kubernetes/minikube/issues/7332#issuecomment-608133325"&gt;limitation&lt;/a&gt; on docker &lt;a href="https://github.com/kubernetes/minikube/issues/13795"&gt;bridge&lt;/a&gt; with Mac. Some reported an ugly &lt;a href="https://github.com/kubernetes/minikube/issues/7332#issuecomment-1164452857"&gt;workaround&lt;/a&gt; with &lt;a href="https://github.com/chipmk/docker-mac-net-connect"&gt;docker-mac-net-connect&lt;/a&gt; but I never got it to work.&lt;/li&gt;&#10;&lt;li&gt;On Windows native environment, the preferred driver is hyper-V. The Minikube cli command have to run from Windows PowerShell. &lt;/li&gt;&#10;&lt;li&gt;On WSL2, Minikube doesn&amp;#8217;t play well, regardless of driver. The hyperkit driver won&amp;#8217;t work (it is designed for MacOS only). The kvm2 driver would require a KVM2 hypervisor. However, WSL2 itself is a VM on top of hypervisor, as explained &lt;a href="https://static.digihunch.com/2020/06/wsl2-environment-on-windows-10/"&gt;here&lt;/a&gt;. If KVM2 driver works it would require nested virtualization so I doubt it will ever be supported. As for Docker on WSL2 as driver, Minikube has it as an &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/docker/"&gt;experimental feature&lt;/a&gt;, and requires configuring cgroup to allow setting memory. I am not confident with it.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To me, Minikube is the tool for MacOS (I have Intel processor). On MacOS, we first need to install minikube and hyperkit with home brew.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can then start a kubernetes cluster, with minikube in a single command. I noticed a process on my MacBook called dnscrypt-proxy that conflicts with hyperkit DNS server when starting minikube. I had to remove dnscrypt-proxy (part of Cisco Umbrella Roaming Client) in order to get minikube to work, as &lt;a href="https://github.com/kubernetes/minikube/issues/3036"&gt;this&lt;/a&gt; thread suggests. You can find out by running:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sudo lsof -i :53&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If dnscrypt-proxy is running, find out the application by PID and remove the application. Otherwise there will be issues. Check out &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/hyperkit/#local-dns-server-conflict"&gt;this&lt;/a&gt; section on the document. The commands that I use to start multi-node cluster is:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube start --driver&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hyperkit --container-runtime&lt;span style="color:#f92672"&gt;=&lt;/span&gt;containerd --memory&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12288&lt;/span&gt; --cpus&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; --disk-size&lt;span style="color:#f92672"&gt;=&lt;/span&gt;150g --nodes &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get po -A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl describe node minikube|grep Runtime&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Node administration is simple. To enable dashboard, simply run &amp;#8220;minikube dashboard&amp;#8221;. To SSH to a node, simply do &amp;#8220;minikube ssh -n &amp;lt;node_name&amp;gt;&amp;#8221;. In order to stop the node and delete cluster, run &amp;#8220;minikube stop &amp;amp;&amp;amp; minikube delete&amp;#8221;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are some addons in minikube, for example, efk, gvisor, istio, metrics-server. To list add-ons, and enable metrics-server, for example, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube addons list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube addons enable metrics-server&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When creating cluster, instead of specifying the cluster imperatively, the configuration (e.g. driver, container runtime, cpu, memory, number of nodes, etc) can be stored as a &lt;a href="https://minikube.sigs.k8s.io/docs/commands/profile/"&gt;profile&lt;/a&gt; with -p switch. Like other Minikube configuration information, Minikube profiles are stored in ~/.minikube under the profile directory.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Minikube also has a &lt;a href="https://minikube.sigs.k8s.io/docs/benchmarks/imagebuild/minikubevsothers/"&gt;page&lt;/a&gt; that benchmarks the performance of these technologies, where it presents itself as the most performant.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://minikube.sigs.k8s.io/images/benchmarks/minikubeVsOthers/iterative.png" alt="Iterative Loads"/&gt;&lt;figcaption class="wp-element-caption"&gt;Minikube, KinD, k3d and microK8s performance&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 class="wp-block-heading" id="microk8s"&gt;MicroK8s&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MicroK8s is developed by Canonical. It can use either Multipass or LXD container as driver. Multipass can configure Ubuntu VMs using cloud-init. It supports multiple hypervisor backends as well but hyperkit is the default on MacOS, Hyper-V on Windows, and KVM on Linux.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;MicroK8s supports multi-node configuration across multiple machines. That is, nodes can span across multiple physical machines. This is more powerful than Minikube where multiple nodes are on the same physical machine. It brings MicroK8s additional use cases such as edge and IoT devices.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With that capability comes the extra step to configure a MicroK8s cluster. You will need to manually join a node to a cluster because the new node is potentially located on a different machine, and you execute the command from the new machine. On the other hand, with Minikube you simply specify the number of nodes desired in a command or profile.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Snap is the native package manager to install MicroK8s, making GNU Linux (e.g. Ubuntu) the native platform. It also supports MacOS and Windows. MicroK8s does not rely on Docker (unlike KinD and Minikube with Docker as driver), and uses containerd as runtime.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://microk8s.io/docs/working-with-kubectl"&gt;MicroK8s&lt;/a&gt; comes with its own packaged version of kubectl, and you use that with &amp;#8220;microk8s kubectl&amp;#8221; command, which is not convenient. You can configure your host kubectl to point to the MicroK8s cluster, as an extra step.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Compared to the other two technologies, MicroK8s is more powerful in the sense that the cluster is build on nodes across multiple machines. However, it takes more step to configure even for a multi-node, single-machine environment. Refer to &lt;a href="https://kubernetes.io/blog/2019/11/26/running-kubernetes-locally-on-linux-with-microk8s/#:~:text=Microk8s%20is%20the%20click%2Dand,doesn't%20require%20a%20VM."&gt;this&lt;/a&gt; post for the steps.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="kind"&gt;KinD&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;KinD is similar to Minikube with Docker as driver. It is more restricted than Minikube considering Docker is the only driver it supports. This makes it a requirement to have Docker installed locally.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although KinD uses Docker to run nodes, it does not use Docker as its container runtime. Therefore it remains as compliant environment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another advantage of KinD is it supports Docker on &lt;a href="https://kind.sigs.k8s.io/docs/user/using-wsl2/"&gt;WSL2&lt;/a&gt; very well. Simply install KinD on WSL2 and start Docker. This blog &lt;a href="https://kubernetes.io/blog/2020/05/21/wsl-docker-kubernetes-on-the-windows-desktop/"&gt;post&lt;/a&gt; contains the steps required to install KinD vs Minikube on WSL2. There is a comparison table in the conclusion section that highlights the fact that it is much easier to install KinD with WSL2 than to install Minikube.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, there are currently some &lt;a href="https://docs.docker.com/desktop/windows/networking/#known-limitations-use-cases-and-workarounds"&gt;known limitations&lt;/a&gt; with Docker desktop for Windows (including on WSL2). One is the absence of docker0 bridge. This means on Windows you cannot route traffic to the containers.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For cluster specification, KinD can configure a cluster declaratively using YAML file for example, the kind-config.yaml contains the following snippet:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Cluster&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;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;kind.x-k8s.io/v1alpha4&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;nodes&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;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;control-plane&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;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker&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;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker&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;role&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;worker&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;networking&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;disableDefaultCNI&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can bring up a cluster with a 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;kind create cluster --config&lt;span style="color:#f92672"&gt;=&lt;/span&gt;kind-config.yaml&#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 will also configure the kubectl context so we can check node with kubectl command. The file is in my &lt;a href="https://github.com/digihunch/real-quicK-cluster/tree/main/kind"&gt;real-quicK-cluster&lt;/a&gt; repo.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="conclusion"&gt;Conclusion&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After reviewing the technologies that back up multi-node kubernetes cluster for my role, I find that Minikube with hyperkit is my favourite for MacOS. On WSL2, I prefer to use KinD. Since I do not use Windows native environment or Ubuntu on my laptop, I cannot make recommendations. However I would start with Minikube (with hypverv or kvm2 as driver). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;strong&gt;Update July 2022&lt;/strong&gt;: When the test workload involves persistent storage, KinD is a better choice. When the test workload involves load balancer. Minikube is a better choice.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to storage provisioner, Minikube with storage-provisioner addon uses k8s.io/&lt;a href="https://github.com/kubernetes/minikube/tree/master/deploy/addons/storage-provisioner"&gt;minikube-hostpath&lt;/a&gt;. KinD uses &lt;a href="https://github.com/rancher/local-path-provisioner"&gt;rancher.io/local-path&lt;/a&gt;. When I have to test workload with persistent storage (e.g. PostgreSQL with &lt;a href="https://access.crunchydata.com/documentation/postgres-operator/v5/"&gt;Crunchy pgo&lt;/a&gt;), I realized Minikube have permission issues with persistent volume, as discussed &lt;a href="https://github.com/kubernetes/minikube/issues/12360"&gt;here&lt;/a&gt; as an issue with multiple nodes. The issue has been open since Aug 2021.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Load Balancer, Minikube has metallb as an addon and I can configure it within a &lt;a href="https://github.com/digihunch/real-quicK-cluster/blob/main/minikube/restart-minikube.sh"&gt;bash script&lt;/a&gt; conveniently. With KinD, I&amp;#8217;d have to configure that in a few &lt;a href="https://kind.sigs.k8s.io/docs/user/loadbalancer/"&gt;steps&lt;/a&gt; with both kubectl and Docker CLI commands and I was not able to connect to the load balancer by IP even after following the steps. So I tend to just use Minikube to test workload requiring load balancer and service mesh. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I find myself switch between Minikube and KinD on my MacBook depending on the test workload.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/09/log-shipping-in-kubernetes-with-efk/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Log Shipping in Kubernetes with EFK stack&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/09/file-storage-vs-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;File storage vs object storage in the cloud&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Creating X.509 TLS certificate for workload on Kubernetes</title><link>https://static.digihunch.com/2021/08/creating-tls-certificate-kubernetes/</link><pubDate>Sun, 29 Aug 2021 23:19:00 -0400</pubDate><guid>https://static.digihunch.com/2021/08/creating-tls-certificate-kubernetes/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-x509.webp" alt="Featured image of post Creating X.509 TLS certificate for workload on Kubernetes" /&gt;&lt;p class="wp-block-paragraph"&gt;In deployment automation, I often had to create self-signed X.509 certificate for testing TLS traffic into Kubernetes. Sometimes self-signed, sometimes signed by a CA. This post summarized the approaches I&amp;#8217;ve taken.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="create-self-signed-certificate-with-openssl"&gt;Create self-signed certificate with OpenSSL&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Traditionally, this is done in three OpenSSL commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -x509 -sha256 -newkey rsa:4096 -keyout ca.key -out ca.crt -days &lt;span style="color:#ae81ff"&gt;356&lt;/span&gt; -nodes -subj &lt;span style="color:#e6db74"&gt;&amp;#39;/CN=Health Certificate Authority&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -newkey rsa:4096 -keyout server.key -out server.csr -nodes -subj &lt;span style="color:#e6db74"&gt;&amp;#39;/CN=*.orthweb.com&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -sha256 -days &lt;span style="color:#ae81ff"&gt;365&lt;/span&gt; -in server.csr -CA ca.crt -CAkey ca.key -set_serial &lt;span style="color:#ae81ff"&gt;01&lt;/span&gt; -out server.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;I have an older post to cover the basics of cryptography in TLS certificate and PKI. In the three commands above, the first produces a private key and self-signed certificate for a CA. The second creates a private key and a CSR for the web site. The third one uses the CA&amp;#8217;s signing private key to sign the CSR from the website. The output is the certificate for the website. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Workloads running in Kubernetes typically consume certificates stored in Kubernetes Secret. The cons of this approach is that it usually requires an extra step to import the certificate files into Kubernetes Secret. 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;kubectl create -n orthweb secret generic orthweb-cred --from-file&lt;span style="color:#f92672"&gt;=&lt;/span&gt;tls.key&lt;span style="color:#f92672"&gt;=&lt;/span&gt;server.key --from-file&lt;span style="color:#f92672"&gt;=&lt;/span&gt;tls.crt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;server.crt --from-file&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ca.crt&lt;span style="color:#f92672"&gt;=&lt;/span&gt;ca.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note, people use the term self-signed certificate loosely. It sometimes means literally a certificate that is self-signed, like the one generated above. Sometimes, I had to self-sign a CA, then use the CA to sign one certificate for the server and one for the client. Because the application being tested requires that the client and server&amp;#8217;s certificates both under the same CA. This would involve a few more commands, 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;&lt;span style="color:#75715e"&gt;# Self-sign a CA&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -x509 -sha256 -newkey rsa:4096 -days &lt;span style="color:#ae81ff"&gt;365&lt;/span&gt; -nodes -subj /C&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CA/ST&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Ontario/L&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Waterloo/O&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Digihunch/OU&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Imaging/CN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;issuer.digihunch.com/emailAddress&lt;span style="color:#f92672"&gt;=&lt;/span&gt;info@www.digihunch.com -keyout /tmp/ca.key -out /tmp/ca.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Generate a CSR for server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -newkey rsa:4096 -nodes -subj /C&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CA/ST&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Ontario/L&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Waterloo/O&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Digihunch/OU&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Imaging/CN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;server.digihunch.com/emailAddress&lt;span style="color:#f92672"&gt;=&lt;/span&gt;orthweb@www.digihunch.com -addext extendedKeyUsage&lt;span style="color:#f92672"&gt;=&lt;/span&gt;serverAuth -addext subjectAltName&lt;span style="color:#f92672"&gt;=&lt;/span&gt;DNS:orthweb.digihunch.com,DNS:server2.digihunch.com -keyout /tmp/server.key -out /tmp/server.csr&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Use the self-signed CA to issue a certificate to the server&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -sha256 -days &lt;span style="color:#ae81ff"&gt;3650&lt;/span&gt; -in /tmp/server.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial &lt;span style="color:#ae81ff"&gt;01&lt;/span&gt; -out /tmp/server.crt -extfile &amp;lt;&lt;span style="color:#f92672"&gt;(&lt;/span&gt;echo subjectAltName&lt;span style="color:#f92672"&gt;=&lt;/span&gt;DNS:orthweb.digihunch.com,DNS:server2.digihunch.com&lt;span style="color:#f92672"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Generate a CSR for clietn&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl req -new -newkey rsa:4096 -nodes -subj /C&lt;span style="color:#f92672"&gt;=&lt;/span&gt;CA/ST&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Ontario/L&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Waterloo/O&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Digihunch/OU&lt;span style="color:#f92672"&gt;=&lt;/span&gt;Imaging/CN&lt;span style="color:#f92672"&gt;=&lt;/span&gt;client.digihunch.com/emailAddress&lt;span style="color:#f92672"&gt;=&lt;/span&gt;client@www.digihunch.com -keyout /tmp/client.key -out /tmp/client.csr&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# Use the self-signed CA to issue a certificate to the client&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;openssl x509 -req -sha256 -days &lt;span style="color:#ae81ff"&gt;365&lt;/span&gt; -in /tmp/client.csr -CA /tmp/ca.crt -CAkey /tmp/ca.key -set_serial &lt;span style="color:#ae81ff"&gt;01&lt;/span&gt; -out /tmp/client.crt&#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 example above, it is important to note that even though the server&amp;#8217;s CSR contains subject alternative name (SAN), I still have to specify the SAN again when signing the certificate for the server. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to OpenSSL there are other toolkits such as CFSSL that supports specifying configuration files. However, the steps in Shell command are generally not always easy to automate.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="create-self-signed-certificate-with-helm"&gt;Create self-signed certificate with Helm&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Moving to the context of workload deployment in Kubernetes, running openSSL command isn&amp;#8217;t always a viable option. For example, generating a certificate in the middle of deployment using a Helm Chart. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;Helm&lt;/a&gt;, template functions is for this purpose. In my Korthweb &lt;a href="https://github.com/digihunch/korthweb/blob/6480dad21490c0bfae27316093e533d68ecf04a2/orthanc/templates/tls-secrets.yaml#L4"&gt;project&lt;/a&gt; I used genSignedCert to create self-signed certificate and then store the key, certificate and CA certificate as Kubernetes Secret:&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:#f92672"&gt;{{&lt;/span&gt;- $dbtlscert :&lt;span style="color:#f92672"&gt;=&lt;/span&gt; genSignedCert .Values.dbtls.certCommonName nil &lt;span style="color:#f92672"&gt;(&lt;/span&gt;list .Values.dbtls.certCommonName&lt;span style="color:#f92672"&gt;)&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;365&lt;/span&gt; $ca &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;apiVersion: v1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind: Secret&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;metadata:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: &lt;span style="color:#f92672"&gt;{{&lt;/span&gt; .Values.dbtls.certCommonName | quote &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; namespace: &lt;span style="color:#f92672"&gt;{{&lt;/span&gt; $.Release.Namespace | quote &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;type: kubernetes.io/tls&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;data:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tls.crt: &lt;span style="color:#f92672"&gt;{{&lt;/span&gt; $dbtlscert.Cert | b64enc | quote &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; tls.key: &lt;span style="color:#f92672"&gt;{{&lt;/span&gt; $dbtlscert.Key | b64enc | quote &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; ca.crt: &lt;span style="color:#f92672"&gt;{{&lt;/span&gt; $ca.Cert | b64enc | quote &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;- end &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;The cons of this approach is that the syntax is not straightforward. As indicated in Helm &lt;a href="https://helm.sh/docs/topics/charts/#templates-and-values"&gt;documentation&lt;/a&gt;: Helm Chart templates are written in the&amp;nbsp;&lt;a href="https://golang.org/pkg/text/template/" target="_blank" rel="noreferrer noopener"&gt;Go template language&lt;/a&gt;, with the addition of 50 or so add-on template functions&amp;nbsp;&lt;a href="https://github.com/Masterminds/sprig" target="_blank" rel="noreferrer noopener"&gt;from the Sprig library&lt;/a&gt;&amp;nbsp;and a few other&amp;nbsp;&lt;a href="https://helm.sh/docs/howto/charts_tips_and_tricks/"&gt;specialized functions&lt;/a&gt;. While we talk about the &amp;#8220;&lt;a href="https://helm.sh/docs/chart_template_guide/functions_and_pipelines/#helm"&gt;Helm template language&lt;/a&gt;&amp;#8221; as if it is Helm-specific, it is actually a combination of the Go template language, some extra functions, and a variety of wrappers to expose certain objects to the templates. Many resources on Go templates may be helpful as you learn about templating.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="create-self-signed-certificate-with-cert-manager"&gt;Create self-signed certificate with Cert-Manager&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://cert-manager.io/docs/"&gt;Cert Manager&lt;/a&gt; project is very popular to produce X.509 certificates directly in Kubernetes secret. We can install cert manager using Helm:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create namespace cert-manager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add jetstack https://charts.jetstack.io&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install cert-manager jetstack/cert-manager --namespace cert-manager --version v1.0.3 --set installCRDs&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get pods -n cert-manager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get crd | grep cert-manager.io&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Alternatively, FluxCD&amp;#8217;s documentation on &lt;a href="https://fluxcd.io/docs/components/kustomize/kustomization/#kustomization-dependencies"&gt;Kustomization dependency&lt;/a&gt; uses Cert Manager as an example. It is a good way of installing cert-manager if you have GitOps pattern.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Creating self-signed certificate for website is fairly simple. It starts with &lt;a href="https://cert-manager.io/docs/configuration/selfsigned/#bootstrapping-ca-issuers"&gt;bootstrapping&lt;/a&gt; a CA issuer. Take the manifest below as an example. When creating the first certificate, make sure to specify isCA=true, so it stores the signing private key along with its own certificate in the ca-secret. Then use the newly created CA as issuer to create the X.509 certificate for the website.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ClusterIssuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;selfsigned-issuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selfSigned&lt;/span&gt;: {}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Certificate&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-ca&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthweb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;isCA&lt;/span&gt;: &lt;span style="color:#66d9ef"&gt;true&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;commonName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-ca&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;secretName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ca-secret&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;privateKey&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;algorithm&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ECDSA&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;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;256&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;issuerRef&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;selfsigned-issuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ClusterIssuer&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;group&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Issuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-ca-issuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthweb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ca&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;secretName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ca-secret&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Certificate&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthweb-cert&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthweb&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;commonName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthweb.com&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;secretName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;orthweb-secret&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;duration&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2160h&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;renewBefore&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;72h&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;subject&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;organizations&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;digihunch&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;dnsNames&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;web.orthweb.com&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;dcm.orthweb.com&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;privateKey&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;algorithm&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ECDSA&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;size&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;256&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;issuerRef&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-ca-issuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Issuer&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;group&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The site certificate is directly stored in Kubernetes Secret as specified in the secretName field. To fetch the certificate text, we need to decode the secret entry, 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;kubectl -n orthweb get secret orthweb-secret -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;{.data.ca\.crt}&amp;#39;&lt;/span&gt; | base64 -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that the example above uses ECDSA algorithm with size 256 for private key and certificate. It requires that the TLS client to support ECDSA algorithm as well. For more supportability, you can use RSA algorithm (2048 or 4096 size).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In addition to creating self-signed certificate, Cert Manager supports a number of other issuer types. For example, the support of &lt;a href="https://cert-manager.io/docs/configuration/acme/"&gt;ACME&lt;/a&gt; issuer type enables integration with Let&amp;#8217;s Encrypt. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cert Manager can secure Kubernetes Ingress resources with a sub-component called &lt;strong&gt;ingress-shim&lt;/strong&gt;. It is configured via annotation on the &lt;a href="https://cert-manager.io/docs/usage/ingress/"&gt;Ingress&lt;/a&gt; resource.&lt;/p&gt;&#10;&lt;figure class="wp-block-image is-resized"&gt;&lt;img decoding="async" src="https://cert-manager.io/images/high-level-overview.svg" alt="High level overview diagram explaining cert-manager architecture" style="width:752px;height:432px"/&gt;&lt;figcaption class="wp-element-caption"&gt;Cert Manager&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;h2 class="wp-block-heading" id="create-ca-signed-certificate-manually"&gt;Create CA-signed certificate manually&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For a certificate signed by a CA, there are may paid options, from manual, to self-help, to automated. The classic manual way is using OpenSSL, generating key, CSR. The CA takes CSR to sign a X.509 certificate returned to the website administration.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Many CA websites charges for a fee and makes it easy. For example, this site currently uses certificate from SSLs.com. Apart from the fee-for-cert option, there is a website named &amp;#8220;&lt;a href="https://www.sslforfree.com/"&gt;SSL for free&lt;/a&gt;&amp;#8220;, a CA with free option for 90-day single-domain, non-wildcard certificate and we can request it simply on their website, with proof of domain ownership. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The other popular free option is Let&amp;#8217;s Encrypt, which also employs &lt;a href="https://static.digihunch.com/2021/04/public-key-infrastructure-pki/"&gt;ACME&lt;/a&gt; protocol. The protocol requires ACME challenges to be satisfied in order to proof domain ownership. There are a few types of &lt;a href="https://letsencrypt.org/docs/challenge-types/"&gt;challenges&lt;/a&gt;: &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;HTTP-01 challenge &lt;/li&gt;&#10;&lt;li&gt;DNS-01 challenge&lt;/li&gt;&#10;&lt;li&gt;TLS-SNI-01 challenge&lt;/li&gt;&#10;&lt;li&gt;TLS-ALPN-01 challenge&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I have used the HTTP-01 and DNS-01 challenges. The DNS-01 challenge requires adding TXT records to DNS configuration. The HTTP-01 challenge requires adding a DNS A-record to resolve to the server, then two URIs with pre-defined value.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When I first set up this site I used certbot (the client program for letsencrypt) to create certificate every 90 days from the wordpress server, following &lt;a href="https://lightsail.aws.amazon.com/ls/docs/en_us/articles/amazon-lightsail-using-lets-encrypt-certificates-with-wordpress"&gt;this&lt;/a&gt; guide, including solving DNS-01 challenges.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="create-ca-signed-certificate-automatically-with-cert-manager-and-letsencrypt"&gt;Create CA-signed certificate automatically with cert manager and letsencrypt&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With Kubernetes, &lt;a href="https://cert-manager.io/docs/"&gt;cert-manager&lt;/a&gt; has the ability to integrate with let&amp;#8217;s encrypt for full automation. &lt;a href="https://medium.com/@rd.petrusek/kubernetes-istio-cert-manager-and-lets-encrypt-c3e0822a3aaf"&gt;Here&lt;/a&gt; is a good blog post on this. &lt;span style="text-decoration: underline;"&gt;Domain verification is still required&lt;/span&gt; but it can be done automatically. We first need to register an A record that resolves host name to the Ingress IP to enable this automation. The domain ownership validation may use the ACME protocol. This should also work on private networks with private DNS and ACME protocol using a private boulder server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Take domain name demo1.digihunch.com for example, if ingress exposes a public IP address which the domain name resolves to, then we can configure certificate with the following manifest:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;IngressClass&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;istio&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;controller&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;istio.io/ingress-controller&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ClusterIssuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;acme&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;privateKeySecretRef&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt&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;server&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;https://acme-staging-v02.api.letsencrypt.org/directory&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;solvers&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;http01&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;ingress&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;class&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;istio&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;cert-manager.io/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Certificate&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;demo&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;dnsNames&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#ae81ff"&gt;demo1.digihunch.com&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;issuerRef&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;ClusterIssuer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;letsencrypt&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;secretName&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;demo-tls&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This example uses Istio as ingress controller but the method works regardless of the controller technology behind Ingress. In the ClusterIssuer object, we&amp;#8217;re telling it to use the staging server from letsencrypt. We also specify http01 as challenge type, and that the ingress type is istio. In the Certificate object, we provided dnsName and specified ClusterIssuser. We also tell it to store the credentials to a secret named &lt;em&gt;demo-tls&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we apply the resources above, the ClusterIssuer connects to letsencrypt server via ACME protocol. Since the DNS name already resolves to the Public IP that the ingress is hosting, the ClusterIssuer configures the required Ingress, Services and Pods accordingly so the &lt;a href="https://letsencrypt.org/docs/challenge-types/#http-01-challenge"&gt;token&lt;/a&gt; to satisfy the challenge is presented at the designated URI. Instead of a staging server, we can also use production ACME server for production deployment. Note that the production ACME endpoint has a stricter rate limit.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When the ACME validation is in progress, it is important to ensure that port 80 is open and there is no other mechanism (such as routing rule, authorization requirement, mandatory redirect to 443) that blocks access from letsencrypt server.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="bottom-line"&gt;Bottom line&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cert Manager is deployed in Kubernetes, supporting a variety of issuer types. As a Kubernetes-native tool, it is a no-brainer for Kubernetes workload for X.509 certificate. Compared with using template function in Helm, it is not dependent on template function and the syntax is consistent (YAML). Compared with OpenSSL or other binary tools, it is easy to integrate with the platform.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Single-node Kubernetes cluster – docker desktop&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/09/log-shipping-in-kubernetes-with-efk/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Log Shipping in Kubernetes with EFK stack&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Single-node Kubernetes cluster – docker desktop</title><link>https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/</link><pubDate>Sun, 22 Aug 2021 00:24:00 -0400</pubDate><guid>https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-single-node-k8s.webp" alt="Featured image of post Single-node Kubernetes cluster – docker desktop" /&gt;&lt;p class="wp-block-paragraph"&gt;While there are many tools to set up single-node Kubernetes cluster (e.g. &lt;a href="https://minikube.sigs.k8s.io/docs/start/"&gt;minikube&lt;/a&gt;, &lt;a href="https://microk8s.io/"&gt;MicroK8s&lt;/a&gt;, &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/"&gt;kind&lt;/a&gt;, or &lt;a href="https://k3s.io/"&gt;k3s&lt;/a&gt; with the &lt;a href="https://k3d.io/"&gt;k3d&lt;/a&gt; wrapper), docker-desktop has a significant advantage: it comes with Docker installation, on MacOS, or on Windows. It is installed simply by enabling the option &amp;#8220;Enable Kubernetes&amp;#8221;. It can be blown away and reset in a heartbeat (with the button &amp;#8220;Reset Kubernetes Cluster&amp;#8221;). For its versatility, docker-desktop is a great development environment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;However, there are always nuances, which motivates me to write this blog. I wanted to note down what is on earth different about Docker-desktop, because the instructions for applications might differ slightly between single-node cluster on MacOS/Windows and the &amp;#8220;real&amp;#8221; multi-node cluster. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I will start with with a deep dive into the docker-desktop architecture, then we&amp;#8217;ll go through the steps to install some common applications with Kubernetes.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-docker-desktop-on-macos"&gt;Docker-Desktop on MacOS&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are a number of open-source and proprietary projects involved to bring docker-desktop to implementation. Let&amp;#8217;s begin with the following five:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://developer.apple.com/documentation/hypervisor"&gt;Hypervisor Framework&lt;/a&gt;: Apple&amp;#8217;s APIs on MacOS that allows you to interact with virtualization technologies in user space.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://wiki.freebsd.org/bhyve"&gt;bhyve&lt;/a&gt;: A type-2 hypervisor initially written for FreeBSD (and was contributed to FreeBSD in May 2011).&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/machyve/xhyve"&gt;xhyve&lt;/a&gt;: A port of bhyve project to MacOS with integration via Apple&amp;#8217;s Hypervisor Framework. The Hypervisor Framework allows xhyve to run entirely in userspace. It is sometimes loosely referred to as xhyve/bhyve hypervisor, and is optimized for lightweight virtual machines and container deployment.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/moby/hyperkit"&gt;HyperKit&lt;/a&gt; is an open-source toolkit on macOS based on xhyve. HyperKit is lightweight and therefore allows you to embed hypervisor capabilities in your application. The hypervisor component in HyperKit is based on xhyve/bhyve. HyperKit is designed to be interfaced with higher-level components such as the VPNKit and DataKit. Docker-desktop and &lt;a href="https://minikube.sigs.k8s.io/docs/drivers/hyperkit/"&gt;MiniKube&lt;/a&gt; are built on HyperKit.&lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://github.com/moby/hyperkit"&gt;LinuxKit&lt;/a&gt; is a toolkit for building custom minimal, immutable and purpose-build Linux distributions. It supports several well-known hypervisor platforms, such as HyperKit, Hyper-V, qemu and VMware. &lt;a href="https://www.docker.com/blog/introducing-linuxkit-container-os-toolkit/"&gt;LinuxKit&lt;/a&gt; started as an internal project in Docker Inc and is now managed as a &lt;a href="https://mobyproject.org/"&gt;Moby Project&lt;/a&gt;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Hyperkit is installed as part of docker desktop. The process can be found with ps 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;ps -Af | grep hyperkit&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Or in the activity monitor:&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-full"&gt;&lt;img loading="lazy" decoding="async" width="942" height="303" src="https://static.digihunch.com/wp-content/uploads/2021/08/image-3.png" alt="" class="wp-image-2648"/&gt;&lt;figcaption class="wp-element-caption"&gt;Docker related processes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker-deskop is essentially a LinuxKit virtual machine (as defined &lt;a href="https://github.com/linuxkit/linuxkit/blob/master/examples/docker-for-mac.yml"&gt;here&lt;/a&gt;). It runs containerd process inside of the virtual machine. &lt;a href="https://collabnix.com/how-docker-for-mac-works-under-the-hood/"&gt;This&lt;/a&gt; is an older article about this architecture. If Kubernetes is enabled, the virtual machine is also installed with kubelet, the agent process running on each Kubernetes node.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since MacOS is not the direct host of the containers, there is no way to map MacOS file system to container&amp;#8217;s as you can with a Docker/Kubernetes host. Prior to Docker 20.10, there used to be a &lt;a href="https://timonweb.com/docker/getting-path-and-accessing-persistent-volumes-in-docker-for-mac/"&gt;trick&lt;/a&gt; to indirectly access host volume from MacOS terminal. It has stopped working according to &lt;a href="https://github.com/docker/for-mac/issues/4822"&gt;this&lt;/a&gt; issue but workarounds are provided &lt;a href="https://stackoverflow.com/questions/63445657/why-i-am-getting-screen-is-terminating-error-in-macos/63595817#63595817"&gt;here&lt;/a&gt;. This &lt;a href="https://gist.github.com/BretFisher/5e1a0c7bcca4c735e716abf62afad389#2021-update-easiest-option-is-justins-repo-and-image"&gt;post&lt;/a&gt; proposes some good alternatives to access the file system of LinuxKit VM. For example, use netcat:&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; user@LinVM &lt;span style="color:#75715e"&gt;# nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;/ &lt;span style="color:#75715e"&gt;# cat /etc/os-release&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat /etc/os-release&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PRETTY_NAME&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;Docker Desktop&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;# cat /etc/kubernetes/current-version&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;cat /etc/kubernetes/current-version&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubeadm version: &amp;amp;version.Info&lt;span style="color:#f92672"&gt;{&lt;/span&gt;Major:&lt;span style="color:#e6db74"&gt;&amp;#34;1&amp;#34;&lt;/span&gt;, Minor:&lt;span style="color:#e6db74"&gt;&amp;#34;21&amp;#34;&lt;/span&gt;, GitVersion:&lt;span style="color:#e6db74"&gt;&amp;#34;v1.21.2&amp;#34;&lt;/span&gt;, GitCommit:&lt;span style="color:#e6db74"&gt;&amp;#34;092fbfbf53427de67cac1e9fa54aaa09a28371d7&amp;#34;&lt;/span&gt;, GitTreeState:&lt;span style="color:#e6db74"&gt;&amp;#34;archive&amp;#34;&lt;/span&gt;, BuildDate:&lt;span style="color:#e6db74"&gt;&amp;#34;2021-06-18T05:24:26Z&amp;#34;&lt;/span&gt;, GoVersion:&lt;span style="color:#e6db74"&gt;&amp;#34;go1.16.5&amp;#34;&lt;/span&gt;, Compiler:&lt;span style="color:#e6db74"&gt;&amp;#34;gc&amp;#34;&lt;/span&gt;, Platform:&lt;span style="color:#e6db74"&gt;&amp;#34;linux/amd64&amp;#34;&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;Typing in this terminal session feels clunky. According to this &lt;a href="https://stackoverflow.com/questions/64530530/how-may-i-connect-to-a-docker-desktop-virtual-machine-on-mac-docker-desktop-ve"&gt;thread&lt;/a&gt;, we can connect to the LinuxKit VM with tty and sane auto completion, using the command below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;stty -echo -icanon &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; nc -U ~/Library/Containers/com.docker.docker/Data/debug-shell.sock &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; stty sane&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;There are some other alternatives, using privileged Docker containers:&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;docker run -it --privileged --pid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;host debian nsenter -t &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; -m -u -n -i sh&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The following command uses a smaller image:&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;docker run -it --rm --privileged --pid&lt;span style="color:#f92672"&gt;=&lt;/span&gt;host justincormack/nsenter1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;As with Kubernetes, to access the file system on the node is via a privileged container, you can follow the &lt;a href="https://docs.microsoft.com/en-us/azure/aks/ssh"&gt;tips&lt;/a&gt; from Azure, identify node name, and debug against the node using a special container:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl debug node/docker-desktop -it --image&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mcr.microsoft.com/aks/fundamental/base-ubuntu:v0.0.11&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Note that the root directory on the host is mounted to container&amp;#8217;s file system as /host. This mapping renders a lot of symbolic link as dangled, even though they are actually not on the host file system. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-docker-desktop-on-windows"&gt;Docker-Desktop on Windows&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker works with Linux kernel. There have been a couple of efforts to run Linux virtual machine on Windows. For example, Hyper-V backend, and Windows Subsystem Linux (WSL) backend.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Traditionally, Docker on Windows was implemented with Hyper-V as the hypervisor. A LinuxKit distro is running on the Hypver-V VM, provider Linux kernel capabilities. Docker refers to containers running in this architecture as &amp;#8220;Windows Containers&amp;#8221;, which is a misnomer in my opinion.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first release of &lt;a href="https://www.zdnet.com/article/under-the-hood-of-microsofts-windows-subsystem-for-linux/"&gt;WSL &lt;/a&gt;provides a Linux-compatible kernel interface and runs a GNU user space on top of the interface. Neither the Linux kernel code, or a hypervisor is involved. The user space contains GNU Bash shell, command language, command-line tools and interpreters. The absence of Linux kernel in WSL, makes it useless for Docker setup. At that time The Hypver-V backend was still the only option to host docker container during the first version of WSL. This &lt;a href="https://www.docker.com/blog/new-docker-desktop-wsl2-backend/"&gt;post &lt;/a&gt;has a diagram of Docker on Windows with Hyper-V backend.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full"&gt;&lt;img loading="lazy" decoding="async" width="741" height="192" src="https://static.digihunch.com/wp-content/uploads/2021/08/image-1.png" alt="" class="wp-image-2616"/&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://static.digihunch.com/2020/06/wsl2-environment-on-windows-10/"&gt;WSL2&lt;/a&gt; comes with a real Linux Kernel (also on top of Hyper-V), making WSL2 a better alternative than the legacy Hyper-V as the backend of Docker on Windows. It can be turned on as the screenshot shows above. The rest of this post assumes WSL2 as backend. In this setup, we run a Bootstrapping distro independent of the WSL2 Linux distro, although both inside of the lightweight Linux Utility VM. Below is the diagram:&lt;/p&gt;&#10;&lt;p class="has-text-align-center wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="691px" viewBox="-0.5 -0.5 691 371" style="max-width:100%;max-height:371px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="690" height="370" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;rect x="10" y="30" width="150" height="230" fill="#fad9d5" stroke="#ae4132" 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-end; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 27px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 17px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Windows&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="27" fill="#000000" font-family="Helvetica" font-size="17px" text-anchor="middle"&gt;Windows&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="280" width="650" height="70" rx="10.5" ry="10.5" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 648px; height: 1px; padding-top: 315px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 17px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; "&gt;Hypervisor (Hyper-V)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="335" y="320" fill="#000000" font-family="Helvetica" font-size="17px" text-anchor="middle" font-weight="bold"&gt;Hypervisor (Hyper-V)&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="170" y="30" width="490" height="230" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 488px; height: 1px; padding-top: 27px; margin-left: 171px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 17px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; "&gt;Lightweight Linux Utility VM&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="415" y="27" fill="#000000" font-family="Helvetica" font-size="17px" text-anchor="middle" font-weight="bold"&gt;Lightweight Linux Utility VM&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="190" y="180" width="450" height="60" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 448px; height: 1px; padding-top: 210px; margin-left: 191px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 17px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; "&gt;WSL2 Linux Kernel&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="415" y="215" fill="#333333" font-family="Helvetica" font-size="17px" text-anchor="middle" font-weight="bold"&gt;WSL2 Linux Kernel&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="180" width="120" height="60" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 210px; 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: 17px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; font-weight: bold; white-space: normal; word-wrap: normal; "&gt;NT Kernel&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="90" y="215" fill="#333333" font-family="Helvetica" font-size="17px" text-anchor="middle" font-weight="bold"&gt;NT Kernel&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="80" width="120" height="60" fill="#d5e8d4" stroke="#82b366" 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: 110px; 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: 17px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Windows Usermode&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="90" y="115" fill="#000000" font-family="Helvetica" font-size="17px" text-anchor="middle"&gt;Windows Usermo&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="190" y="70" width="250" height="80" fill="#d5e8d4" stroke="#82b366" 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: 248px; height: 1px; padding-top: 110px; margin-left: 191px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 17px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;&lt;div&gt;WSL2-compatible Linux Distro&lt;/div&gt;&lt;div&gt;in Usermode (e.g. Ubuntu)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="115" fill="#000000" font-family="Helvetica" font-size="17px" text-anchor="middle"&gt;WSL2-compatible Linux Distro&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="70" width="170" height="80" fill="#d5e8d4" stroke="#82b366" 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: 168px; height: 1px; padding-top: 110px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 17px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Docker desktop&lt;br&gt;(Bootstrapping distro)&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="555" y="115" fill="#000000" font-family="Helvetica" font-size="17px" text-anchor="middle"&gt;Docker desktop&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 414.96 274.61 L 414.99 246.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 414.95 279.86 L 411.46 272.86 L 414.96 274.61 L 418.46 272.87 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 415 241.12 L 418.49 248.12 L 414.99 246.37 L 411.49 248.11 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 555.67 173.63 L 555.18 156.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 555.82 178.88 L 552.12 171.98 L 555.67 173.63 L 559.12 171.79 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 555.03 151.12 L 558.73 158.02 L 555.18 156.37 L 551.73 158.21 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 249.88 173.63 L 249.97 156.37" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 249.86 178.88 L 246.39 171.86 L 249.88 173.63 L 253.39 171.9 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 249.99 151.12 L 253.46 158.14 L 249.97 156.37 L 246.46 158.1 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 156.37 110 L 183.63 110" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 151.12 110 L 158.12 106.5 L 156.37 110 L 158.12 113.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 188.88 110 L 181.88 113.5 L 183.63 110 L 181.88 106.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 90 73.63 L 90 60 Q 90 50 100 50 L 545 50 Q 555 50 555 56.82 L 555 63.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 90 78.88 L 86.5 71.88 L 90 73.63 L 93.5 71.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 555 68.88 L 551.5 61.88 L 555 63.63 L 558.5 61.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 446.37 110 L 463.63 110" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 441.12 110 L 448.12 106.5 L 446.37 110 L 448.12 113.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 468.88 110 L 461.88 113.5 L 463.63 110 L 461.88 106.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 90 146.37 L 90 173.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 90 141.12 L 93.5 148.12 L 90 146.37 L 86.5 148.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 90 178.88 L 86.5 171.88 L 90 173.63 L 93.5 171.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 89.84 246.37 L 89.16 273.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 89.97 241.12 L 93.3 248.2 L 89.84 246.37 L 86.3 248.03 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 89.03 278.88 L 85.7 271.8 L 89.16 273.63 L 92.7 271.97 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The innovative component is the lightweight Linux Utility VM. It is called a VM, but very different from the traditional sense of VM such as VirtualBox or VMware. Traditional VM is isolated from host OS, slow to boot and has large memory footprint. The lightweight Utility VM on the other hand, is integrated with host OS, super fast to boot (i.e. ~1 second), and comes with small memory footprint. It is not turned on until needed. The VM runs both a WSL2 Linux Kernel and GNU/Linux usermode (known as &amp;#8220;distribution&amp;#8221;, for example, Ubuntu). When an end-user say WSL2, s/he most likely refers to the distribution. Similarly, the so called &amp;#8220;docker-desktop on Windows with WSL2 backend&amp;#8221;, is also managed as two WSL2 distros: the bootstrapping distro (docker-desktop) and the data store distro (docker-desktop-data). &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The detailed components in the Bootstrapping distro is in the second diagram in &lt;a href="https://www.docker.com/blog/new-docker-desktop-wsl2-backend/"&gt;this &lt;/a&gt;post, which has a detailed discussion. With this architecture, you don&amp;#8217;t even need the WSL2 Linux Distro for Docker desktop to function. You can even run docker CLI command from Windows PowerShell without any Linux distro (although this is implemented only for backward compatibility and not recommended anymore) . In the following session, we first list out the WSL2 distros. Notice that the docker-desktop distro is not the default. We then connect to the distro using -d switch. Last, we run docker info from windows user space.&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-powershell" data-lang="powershell"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PS C:\WINDOWS\system32&amp;gt; wsl -l -v&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; NAME STATE VERSION&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;* Ubuntu Running &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker-desktop Running &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; docker-desktop-data Running &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PS C:\WINDOWS\system32&amp;gt; wsl -d docker-desktop&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WINLAPTOP&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;/mnt/host/c/WINDOWS/system32&lt;span style="color:#75715e"&gt;# cd ~&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WINLAPTOP&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;~&lt;span style="color:#75715e"&gt;# printenv|grep DIST&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WSL_DISTRO_NAME=docker-desktop&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;WINLAPTOP&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;~&lt;span style="color:#75715e"&gt;# exit&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;PS C:\WINDOWS\system32&amp;gt; docker info&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Client&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Context&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Debug Mode&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; false&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Plugins&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; buildx&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; Build with BuildKit (Docker Inc., v0.5.&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;-docker)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; compose&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; Docker Compose (Docker Inc., v2.0.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;-beta.&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; scan&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; Docker Scan (Docker Inc., v0.8.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Server&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Containers&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;93&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Running&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Paused&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Stopped&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;13&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Images&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;28&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Server Version&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;20.10&lt;/span&gt;.7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Storage Driver&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; overlay2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Backing Filesystem&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; extfs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Supports d_type&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Native Overlay Diff&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; userxattr&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; false&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Logging Driver&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; json-file&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cgroup Driver&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; cgroupfs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Cgroup Version&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Plugins&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Volume&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; local&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Network&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; bridge host ipvlan macvlan null overlay&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Log&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; awslogs fluentd gcplogs gelf journald json-file local logentries splunk syslog&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Swarm&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; inactive&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Runtimes&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; io.containerd.runc.v2 io.containerd.runtime.v1.linux runc&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;Default&lt;/span&gt; Runtime&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; runc&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Init Binary&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; docker-init&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; containerd version&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; d71fcd7d8303cbf684402823e425e9dd2e99285d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; runc version&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; init version&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; de40ad0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Security Options&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; seccomp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Profile&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Kernel Version&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5.10&lt;/span&gt;.16.&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;-microsoft-standard-WSL2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Operating System&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; Docker Desktop&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; OSType&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; linux&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Architecture&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; x86_64&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CPUs&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Total Memory&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;.32GiB&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Name&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; docker-desktop&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ID&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; WHDE&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;PJF3&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;HVFC&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;AZJA&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;EDKH&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;VUZR&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;RRUJ&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;HHXX&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;TDV5&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;4UJG&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;XY4E&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;PK4F&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Docker Root Dir&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; /var/lib/docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Debug Mode&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; false&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Registry&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; https&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;//index.docker.io/v1/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Labels&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Experimental&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; false&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Insecure Registries&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#ae81ff"&gt;127.0&lt;/span&gt;.0.&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;/&lt;span style="color:#ae81ff"&gt;8&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Live Restore Enabled&lt;span style="color:#960050;background-color:#1e0010"&gt;:&lt;/span&gt; false&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The wsl -d command as illustrated above is a good way to connect to the docker-desktop distro. The alternative to get to the distro is via privileged container (Docker) or helper pod (Kubernetes), which is the same as in Docker-desktop on MacOS. Refer to the section above for specific steps.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-application-install-on-docker-desktop"&gt;Application Install on docker desktop&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The followings are my notes to install commonly used applications in Docker Desktop with Kubernetes. They work on both MacOS or WSL2, requiring Kubernetes enabled.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading" id="h-metric-server"&gt;Metric server&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The metric server is provided in the &lt;a href="https://github.com/kubernetes-sigs/metrics-server"&gt;official&lt;/a&gt; repository. Releases are publish &lt;a href="https://github.com/kubernetes-sigs/metrics-server/releases/latest"&gt;here&lt;/a&gt;, which provides the installation step as follows:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/kubernetes-sigs/metrics-server/releases/download/v0.5.0/components.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;However, there is some issues when deploying it on MacOS, the deployment will fail due to certificates not matching the hostname. To fix the issue, it is recommended to download the yaml file (components.yaml), and edit the file by adding &amp;#8211;kubelet-insecure-tls to the args section of the container named &lt;em&gt;metrics-server&lt;/em&gt;. This is sufficient to fix the issue. Some people are not comfortable with port 443 being insecure TLS, and would rather change the port to 4443. This is completely unnecessary but if that&amp;#8217;s the case, make sure the named port for https is also updated to 4443.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Once metric server has been installed, the following two commands should return meaningful results:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl top no&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl top po&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This should also allow application (Pods) to query for cluster resource usage. When working with single-node cluster on MacOS or WSL2, multiple Pods might come up with a single command and the memory can be easily over-subscribed. The two commands above allows you to check and make adjustment on the node configuration (in Docker preference).&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading" id="h-dashboard"&gt;Dashboard&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to metric server, the dashboard is kept in the official &lt;a href="https://github.com/kubernetes/dashboard"&gt;repo&lt;/a&gt;, in the the path of aio/deploy/recommended.yaml. &lt;a href="https://github.com/kubernetes/dashboard/releases/latest"&gt;Here&lt;/a&gt; is the published release, where the instruction says:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes/dashboard/v2.3.1/aio/deploy/recommended.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;However, this is not directly applicable either because the login page requires token or kubeconfig. We need to be able to bypass that. To do so, download the yaml file (recommended.yaml), and add parameter &amp;#8211;enable-skip-login to the args section for the container named &lt;em&gt;kubernetes-dashboard&lt;/em&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To display the login page properly, we need to start the proxy using this command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl proxy&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The login page will then be available at &lt;a href="http://localhost:8001/api/v1/namespaces/kubernetes-dashboard/services/https:kubernetes-dashboard:/proxy/"&gt;this&lt;/a&gt; URL. The URL reflects the namespace and service name. On the login page, the skip button will be available.&lt;/p&gt;&#10;&lt;h4 class="wp-block-heading" id="h-rancher"&gt;Rancher&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The official installation guide of Rancher 2.5x recommends RKE Kubernetes. If you prefer not to run a separate cluster on MacOS, you can install it on docker desktop (with Kubernetes enabled). The installation steps require Helm 3 and are completed in three helm commands.&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Install Nginx Ingress controller using Helm, following the three commands &lt;a href="https://kubernetes.github.io/ingress-nginx/deploy/#using-helm"&gt;here&lt;/a&gt;. Alternatively, you can apply the rendered template as posted &lt;a href="https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v0.48.1/deploy/static/provider/cloud/deploy.yaml"&gt;here&lt;/a&gt;. The controller will later be used by the ingress that Rancher&amp;#8217;s chart creates.&lt;/li&gt;&#10;&lt;li&gt;Follow the steps on &lt;a href="https://ranchermanager.docs.rancher.com/getting-started/installation-and-upgrade/install-upgrade-on-a-kubernetes-cluster"&gt;this&lt;/a&gt; page to install Rancher, even though the page does not say it applies to docker desktop. If you do not have TLS certificate, the Rancher helm chart can generate one for you, using cert-manager.&lt;/li&gt;&#10;&lt;li&gt;The installation exposes rancher application on port 443 of the MacBook, and the cert is issued to &amp;#8220;rancher.my.org&amp;#8221; by default. To access it, add &amp;#8220;127.0.0.1 rancher.my.org&amp;#8221; to the host file.&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To back out from the steps above, just uninstall with helm. 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;helm uninstall rancher -n cattle-system&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm uninstall cert-manager -n cert-manager&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm uninstall ingress-nginx&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h4 class="wp-block-heading" id="h-jenkins"&gt;Jenkins&lt;/h4&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Similar to Rancher, Jenkins instruction assumes minikube cluster instead of docker desktop.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add jenkinsci https://charts.jenkins.io&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo update&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create namespace jenkins&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install jenkins -n jenkins -f https://raw.githubusercontent.com/jenkinsci/helm-charts/main/charts/jenkins/values.yaml jenkinsci/jenkins&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl --namespace jenkins port-forward svc/jenkins 8080:8080&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To find out the default password for 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;kubectl -n jenkins get secrets jenkins -o jsonpath&lt;span style="color:#f92672"&gt;={&lt;/span&gt;.data.jenkins-admin-password&lt;span style="color:#f92672"&gt;}&lt;/span&gt; | base64 -D&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To uninstall Jenkins:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm uninstall jenkins -n jenkins&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 class="wp-block-heading" id="h-container-runtime"&gt;Container Runtime&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although I seem to be a proponent of docker desktop thus far, this post would be incomplete to not discuss what is missing with docker desktop. One key difference between docker desktop and minikube is the container runtime being used. Docker desktop uses docker as the runtime, and it does not support other runtime as of now. Minikube allows user to choose runtime, including containerd. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because of this difference, Kubernetes nodes with Docker as runtime and with containerd as runtime place pod log files in different locations. To find out the runtime, use the following command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl describe node &amp;lt;node_name&amp;gt; | grep Runtime&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If the runtime is docker, the stdout of container is placed in /var/lib/docker/containers/&amp;lt;sha&amp;gt;/. If the runtime is containerd, the stdout log of pods are stored in /var/log/containers/. This is important to know when you configure log shipping and needs to get stdout from node. The log path used in containerd is the standard path in compliance with Container Runtime Interface (CRI) so you should develop log shipping solution based on that.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker&amp;#8217;s refusal to comply to CRI also caused Kubernetes to stop supporting it as container runtime as of Dec 2020. For more background, refer to &lt;a href="https://kubernetes.io/blog/2020/12/02/dockershim-faq/"&gt;this&lt;/a&gt; and &lt;a href="https://kubernetes.io/blog/2020/12/02/dont-panic-kubernetes-and-docker/"&gt;this&lt;/a&gt;. &lt;a href="https://ink.insertcoin.dev/news/dockershim-deprecation"&gt;Here&lt;/a&gt; is also an article with great diagrams on the removal of docker-shim.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-bottom-line"&gt;Bottom line&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker-desktop is a great tool for a quick single-node Kubernetes environment. As of docker 20.10, docker-desktop still uses docker as runtime. This limits its use case to development only. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If you need a CRI compliant environment, docker-desktop is not a good choice. We will discuss alternatives in the next post.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/08/scalable-infrastructure-deployment-in-terraform/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Infrastructure deployment in Terraform 1/2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/08/creating-tls-certificate-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Creating X.509 TLS certificate for workload on Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Helm – Configuration Management for Kubernetes Resources</title><link>https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/</link><pubDate>Mon, 26 Jul 2021 19:28:22 -0400</pubDate><guid>https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-helm.webp" alt="Featured image of post Helm – Configuration Management for Kubernetes Resources" /&gt;&lt;p class="wp-block-paragraph"&gt;Developer ships application in Docker container, so it can eventually hosted in Kubernetes cluster. However, there are still some installation steps, before the application can operate online in production. In this post, we use the container image of Orthanc application as a starting point. We first build services in Kubernetes to go through these steps. Then, to automate the steps, we build a helm chart. The code is kept in &lt;a href="https://github.com/digihunch/korthweb"&gt;Korthweb&lt;/a&gt; project, in which the &lt;a href="https://github.com/digihunch/korthweb/tree/main/manual"&gt;&lt;em&gt;manual&lt;/em&gt;&lt;/a&gt; directory has the files requirement for manual deployment, and the &lt;em&gt;&lt;a href="https://github.com/digihunch/korthweb/tree/main/helm"&gt;helm&lt;/a&gt;&lt;/em&gt; directory is the helm chart.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-manual-deployment"&gt;Manual Deployment&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://digihunch.github.io/korthweb/deployment/manual/"&gt;manual deployment steps&lt;/a&gt; include different kinds of activities, such as creating X.509 certificates, apply config map, create Kubernetes deployment using the YAML declarations, and use helm to install dependency. The steps need to take place in a particular sequence. Some step requires pulling information from secrets created in the previous step. This is why the deployment is not portable. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In order to automate the steps, one might think of wrapper script, which is very limited. A configuration management tool is needed in this scenario. Two common options are Kustomize, and Helm. &lt;a href="https://kubernetes.io/docs/tasks/manage-kubernetes-objects/kustomization/"&gt;Kustomize&lt;/a&gt; is a native tool which can be run by kubectl. It is also driven by declarative statement in YAML, which is simple to grasp. However, in lack of a templating mechanism, Kustomize may require wordy statements. Helm, on the other hand, comes with a templating mechanism which greatly increase reusability, making it more suitable for complex steps required in installation.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-helm-repo-and-chart"&gt;Helm Repo and Chart&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm is known as package manager for applications running on Kubernetes. Helm defines an application as a collection of related Kubernetes resources, and it manages application deployment through a templated approach. An installation workbook is called a &lt;strong&gt;&lt;em&gt;chart&lt;/em&gt;&lt;/strong&gt;. Charts are kept in repositories. There are some well-known repositories, such as &lt;a href="https://github.com/bitnami/charts"&gt;Bitnami&lt;/a&gt;, Helm &lt;a href="https://charts.helm.sh/stable/"&gt;stable&lt;/a&gt;. You need to add a repostory before using the Helm Charts in it. To add a repo, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add bitnami https://charts.bitnami.com/bitnami&#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 host your own repo (public or private) as well. To search for charts across repositories, the best place is &lt;a href="https://artifacthub.io/"&gt;artifact hub&lt;/a&gt;, which indexes charts from a lot of public repositories. To search for charts from the repositories added, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm search repo postgres&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Template is the soul of Helm chart. A Helm chart consists of a directory of files following specific pattern so Helm can understand how to deploy the application. For example, the chart name is the name of the working directory. Under the directory, the values.yaml and chart.yaml defines variables and constants, both serving as template inputs. The template directory is the most important part of the directory where the installation logics are defined. Helm runs the entire directory hierarchy (except for paths specified in .helmignore file) through a Go template rendering engine. The template result spec out the detailed steps.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A great example of using Helm chart to simplify installation is the wordpress chart by Bitnami. You can install all the required components in a single 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;helm install my-release bitnami/wordpress&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://github.com/digihunch/korthweb/tree/main/helm"&gt;helm chart&lt;/a&gt; in &lt;a href="https://github.com/digihunch/korthweb"&gt;Korthweb&lt;/a&gt; project is also an evolving helm chart I created for installing Orthanc application.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm V3 (released in late 2019) includes an important architectural change &amp;#8211; the removal of tiller. This means Helm can operate on the client-side &amp;#8211; a significant simplification. Helm graduated from CNCF project in 2020. There are also a few changes in V3, as outlined &lt;a href="https://helm.sh/docs/faq/changes_since_helm2/"&gt;here&lt;/a&gt;, including the &lt;a href="https://helm.sh/docs/faq/changes_since_helm2/#consolidation-of-requirementsyaml-into-chartyaml"&gt;consolidation&lt;/a&gt; of requirements.yaml into Charts.yaml.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-template-and-function"&gt;Template and Function&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As discussed, templating is the key towards reusability and flexibility in configuration management. We&amp;#8217;ve worked with Jinja2 template engine in &lt;a href="https://static.digihunch.com/2020/05/ansible-directory-for-scalability-2-of-2/"&gt;Ansible&lt;/a&gt; and Python. Here in &lt;a href="https://helm.sh/docs/howto/charts_tips_and_tricks/"&gt;Helm&lt;/a&gt;, we use Go templates with some enhancement. The syntax is mostly based on Go template, which is somewhat similar to Jinja2. Helm also added all functions from the &lt;a href="https://masterminds.github.io/sprig/"&gt;Sprig&lt;/a&gt; library, making it more powerful and flexible than Jinja2. Helm chart developer should be very familiar with these functions, as well as the &lt;a href="https://helm.sh/docs/howto/charts_tips_and_tricks/"&gt;best practices&lt;/a&gt;. For example, the &lt;a href="https://masterminds.github.io/sprig/crypto.html"&gt;cryptographic and security functions&lt;/a&gt; in Sprig library gives us the ability to create self-signed X509 certificates during installation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since template introduces another layer of abstraction, to help troubleshooting we should be able to preview rendered template with the template 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;helm template orthanc | less&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above simply renders template without attempting to execute the chart. To go one step further, you can dry-run the installation 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;helm install orthweb ./orthanc --debug --dry-run | less&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Although Jinja2 (using {% &amp;#8230; %} to express template control) and Go (using {{ &amp;#8230; }} to express template control) have different syntaxes, one aspect that is similar between them, is chomping whitespace with minus sign (-). This is pretty common in templating language. The documentation of both &lt;a href="https://jinja.palletsprojects.com/en/3.0.x/templates/#whitespace-control"&gt;Jinja2&lt;/a&gt; and &lt;a href="https://helm.sh/docs/chart_template_guide/control_structures/#controlling-whitespace"&gt;Helm&lt;/a&gt; have a section on whitespace control. Not paying attention to this nuance may cause pesky errors. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-dependency"&gt;Dependency&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Orthanc application relies on Postgres database, which itself is deployed by a separate helm chart. This can be specified in Chart.yaml (Helm V3), like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;dependencies:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - condition: postgresql-ha.enabled&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: postgresql-ha&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; repository: https://charts.bitnami.com/bitnami&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; version: 7.8.x&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The values of variables of the dependency chart can be specified in values.yaml of the root chart. They can also be imperatively specified as a parameter of helm install command.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The section above also requires the dependency chart to be downloaded into the &lt;em&gt;charts&lt;/em&gt; sub-directory. This can be done 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;helm dependency update&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then you will notice a file with tgz extension in the &lt;em&gt;charts&lt;/em&gt; sub-directory. Note that when you change the version of the dependency package in Chart.yaml, then you will need to run the command again. Alternatively, this command can be automatically executed before helm install if you specify the switch &amp;#8211;dependency-update with helm install.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The main chart (e.g. wordpress) is referred to as parent chart, and the charts it depends on are referred to as sub-chart (e.g. mariadb, memcached). When it comes to managing property values, values from parent chart can override those from sub-chart, as explained &lt;a href="https://helm.sh/docs/chart_template_guide/subcharts_and_globals/#overriding-values-from-a-parent-chart"&gt;here&lt;/a&gt;. On the other hand, values from sub-chart can override those from parent chart in two formats: &lt;a href="https://helm.sh/docs/topics/charts/#using-the-exports-format"&gt;export format&lt;/a&gt; (keyword &lt;em&gt;exports&lt;/em&gt;) and &lt;a href="https://helm.sh/docs/topics/charts/#using-the-exports-format"&gt;child-parent format&lt;/a&gt; (keyword import-values). This is something to be careful and we can use the aforementioned template command to display the rendered values.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-hooks"&gt;Hooks&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm does a great job in figuring out the dependency relationship between kubernetes objects defined in the chart, and create them in order. So typically you do not need hooks for objects in the chart. However, in certain circumstances, such as cleaning up after uninstallation, we may need hooks. &lt;a href="https://helm.sh/docs/topics/charts_hooks/#the-available-hooks"&gt;Here&lt;/a&gt; is a list of available hooks. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is worth-noting that hook is not tied to an action. Instead it is tied to a kubernetes resource. The resource could be a &lt;a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/"&gt;job&lt;/a&gt;, a config map, etc. The resource is tied to a hook simply by resource &lt;a href="https://helm.sh/docs/topics/charts_hooks/#writing-a-hook"&gt;annotation&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-moving-to-gui"&gt;Moving to GUI&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm is a command-line tool. For a team with varying levels of familiarity with command-line, GUI-based tool is a better option. For that, some enterprises adopt &lt;a href="https://rancher.com/products/rancher/"&gt;Rancher&lt;/a&gt;, a &lt;a href="https://www.rancher.com/quick-start"&gt;comprehensive&lt;/a&gt; Kubernetes cluster management platform. Rancher manages many aspects of &lt;a href="https://rancher.com/why-rancher/rancher-strengthens-kubernetes/"&gt;Kubernetes cluster&lt;/a&gt; through web portal. One aspect is the support of &lt;a href="https://ranchermanager.docs.rancher.com/getting-started/installation-and-upgrade/installation-references/helm-chart-options"&gt;helm chart&lt;/a&gt;. Rancher can be install on a cluster of its own. For demo, it can also be &lt;a href="https://rafalfaro.medium.com/how-to-install-rancher-2-5-in-docker-desktops-bundled-kubernetes-cluster-ebd5e1b0ae8"&gt;installed&lt;/a&gt; on &lt;a href="https://docs.docker.com/desktop/kubernetes/"&gt;docker desktop&lt;/a&gt;, a single-node Kubernetes cluster by Docker. In both cases, Nginx ingress controller needs to be configured.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/07/traffic-management-in-kubernetes-service-and-ingress/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Service and Ingress -Traffic Management in Kubernetes&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/08/scalable-infrastructure-deployment-in-terraform/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Infrastructure deployment in Terraform 1/2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Certified Kubernetes Administrator (CKA) Exam</title><link>https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/</link><pubDate>Fri, 30 Apr 2021 09:50:00 -0400</pubDate><guid>https://static.digihunch.com/2021/04/preparing-certified-kubernetes-administrator-exam/</guid><description>&lt;p class="wp-block-paragraph"&gt;The Certified Kubernetes Administrator (CKA) exam is a hands-on session where you need to follow the instructions to configure the system in a bash terminal on the web browser. In my experience, some shortcut keys (such as Alt+F) do not work, which slows me down a little bit. For each question, you need to switch kubectl context as instructed in the question. Some questions share the same context so it is very easy to omit this step. You can verify response with your own command but will not be told whether you scored in each question. During the CKA exam I tried to spin up a terminal session from within &lt;a href="https://static.digihunch.com/2019/10/personal-vim-cheatsheet/"&gt;Vim&lt;/a&gt; editor and the terminal ran out of buffer. I had to reboot the machine with the help of proctor, and my completed work are saved.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1405" height="121" src="https://static.digihunch.com/wp-content/uploads/2021/05/image.png" alt="" class="wp-image-2266"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In general this is an exam I enjoy preparing and writing because it is very hands on. Result is out a day after, and I passed at 96%. I heard about tight timelines but I managed to finish 15 minutes before the end, most likely owing to my dexterity with Linux commands. With that I&amp;#8217;m happy to share my notes in preparation for the CKA exam.&lt;/p&gt;&#10;&lt;p class="has-white-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="590px" viewBox="-0.5 -0.5 590 638" style="max-width:100%;max-height:638px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="347" width="170" height="290" rx="25.5" ry="25.5" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 344px; margin-left: 1px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; background-color: #ffffff; white-space: normal; word-wrap: normal; "&gt;Worker Node&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="344" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="377" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 392px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="396" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="427" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 442px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kubelet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="446" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubelet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="477" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 492px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container runtime&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="85" y="496" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container runtime&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 85 457 L 85 477 L 85 457 L 85 470.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 85 475.88 L 81.5 468.88 L 85 470.63 L 88.5 468.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="10" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="10" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="125" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="190" y="347" width="170" height="290" rx="25.5" ry="25.5" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 344px; margin-left: 191px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; background-color: #ffffff; white-space: normal; word-wrap: normal; "&gt;Worker Node&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="344" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="377" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 392px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="396" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="427" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 442px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kubelet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="446" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubelet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="477" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 492px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container runtime&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="496" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container runtime&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 275 457 L 275 477 L 275 457 L 275 470.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 275 475.88 L 271.5 468.88 L 275 470.63 L 278.5 468.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="200" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="235" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 281px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="235" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 281px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="200" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 201px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="235" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="280" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 281px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="315" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="380" y="347" width="170" height="290" rx="25.5" ry="25.5" fill="#fff2cc" stroke="#d6b656" stroke-dasharray="3 3" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 168px; height: 1px; padding-top: 344px; margin-left: 381px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; background-color: #ffffff; white-space: normal; word-wrap: normal; "&gt;Worker Node&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="344" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Worker Node&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="377" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 392px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-proxy&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="396" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-proxy&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="427" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 442px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kubelet&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="446" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kubelet&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="477" width="150" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 148px; height: 1px; padding-top: 492px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container runtime&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="465" y="496" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container runtime&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 465 457 L 465 477 L 465 457 L 465 470.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 465 475.88 L 461.5 468.88 L 465 470.63 L 468.5 468.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="390" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="425" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="527" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 537px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="505" y="541" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="425" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="557" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 567px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="505" y="571" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="390" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 391px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="425" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="470" y="587" width="70" height="20" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 597px; margin-left: 471px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;container&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="505" y="601" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;container&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 546.37 392 L 570 392 Q 580 392 580 382 L 580 197 Q 580 187 570 187 L 431.37 187" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 541.12 392 L 548.12 388.5 L 546.37 392 L 548.12 395.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 426.12 187 L 433.12 183.5 L 431.37 187 L 433.12 190.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;rect x="10" y="17" width="460" height="250" rx="37.5" ry="37.5" fill="#dae8fc" stroke="#6c8ebf" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-end; justify-content: unsafe center; width: 458px; height: 1px; padding-top: 14px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; font-weight: bold; white-space: normal; word-wrap: normal; "&gt;Control&amp;nbsp; Plane&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="240" y="14" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle" font-weight="bold"&gt;Control&amp;nbsp; Plane&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="37" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 44px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="56" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="107" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 114px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-scheduler&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="126" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-scheduler&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="40" y="47" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 54px; margin-left: 41px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="66" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="57" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 64px; margin-left: 51px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="76" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="40" y="117" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 124px; margin-left: 41px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-scheduler&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="136" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-scheduler&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="127" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 134px; margin-left: 51px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-scheduler&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="146" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-scheduler&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="30" y="187" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 194px; margin-left: 31px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;cloud-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="130" y="206" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;cloud-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="40" y="197" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 204px; margin-left: 41px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;cloud-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="216" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;cloud-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="50" y="207" width="200" height="30" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 198px; height: 1px; padding-top: 214px; margin-left: 51px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;cloud-controller-manager&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="150" y="226" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;cloud-controller-manager&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="305" y="167" width="120" height="40" fill="#ffffff" stroke="#000000" pointer-events="none"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 118px; height: 1px; padding-top: 174px; margin-left: 306px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;kube-api-server&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="365" y="186" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;kube-api-server&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 335 55 C 335 44.33 395 44.33 395 55 L 395 99 C 395 109.67 335 109.67 335 99 Z" fill="#ffffff" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 335 55 C 335 63 395 63 395 55 M 335 59 C 335 67 395 67 395 59 M 335 63 C 335 71 395 71 395 63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe flex-start; justify-content: unsafe center; width: 58px; height: 1px; padding-top: 74px; margin-left: 336px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: none; white-space: normal; word-wrap: normal; "&gt;etcd&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="365" y="86" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;etcd&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 254.25 76.75 L 330.75 162.25" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 250.75 72.83 L 258.02 75.72 L 254.25 76.75 L 252.8 80.38 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 334.25 166.17 L 326.98 163.28 L 330.75 162.25 L 332.2 158.62 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 255.8 219.36 L 299.2 199.64" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 251.02 221.54 L 255.94 215.45 L 255.8 219.36 L 258.84 221.83 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 303.98 197.46 L 299.06 203.55 L 299.2 199.64 L 296.16 197.17 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 255.37 145.42 L 299.63 173.58" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 250.94 142.6 L 258.73 143.41 L 255.37 145.42 L 254.97 149.31 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 304.06 176.4 L 296.27 175.59 L 299.63 173.58 L 300.03 169.69 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 365 113.37 L 365 160.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 365 108.12 L 368.5 115.12 L 365 113.37 L 361.5 115.12 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 365 165.88 L 361.5 158.88 L 365 160.63 L 368.5 158.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 546.37 442 L 570 442 Q 580 442 580 432 L 580 197 Q 580 187 570 187 L 431.37 187" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 541.12 442 L 548.12 438.5 L 546.37 442 L 548.12 445.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;path d="M 426.12 187 L 433.12 183.5 L 431.37 187 L 433.12 190.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="none"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For taking CKA exam, we should be familiar with the diagram above.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-tips-for-troubleshooting"&gt;Tips for Troubleshooting&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The CKA exam is hands-on and therefore requires quite a bit of troubleshooting. Here are my notes.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Check Node status to start with&lt;/li&gt;&#10;&lt;li&gt;Check core services on each node:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;sudo systemctl status kubelet&lt;/li&gt;&#10;&lt;li&gt;sudo systemctl status docker&lt;/li&gt;&#10;&lt;li&gt;sudo journalctl -u kubelet&lt;/li&gt;&#10;&lt;li&gt;sudo journalctl -u docker&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Check component logs (on hosting VM)&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;/var/log/kube-apiserver.log&lt;/li&gt;&#10;&lt;li&gt;/var/log/kube-scheduler.log&lt;/li&gt;&#10;&lt;li&gt;/var/log/kube-controller-manager.log&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;If cluster is built by kubeadm, then some of those services are running in Pods within kube-system namespace. Check those pods:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;run interactive shell: &amp;gt; kubectl exec podname &amp;#8211;stdin &amp;#8211;tty &amp;#8212; /bin/sh&lt;/li&gt;&#10;&lt;li&gt;there is an image for lots of useful network tool called nicolaka/netshoot&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Store pod names to variable. e.g. &amp;gt; POD_NAME=$(kubectl get pods -l run=nginx -o jsonpath=&amp;#8221;{.items[0].metadata.name}&amp;#8221;)&lt;/li&gt;&#10;&lt;li&gt;With kubectl, you may&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;alias it to k for faster typing&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;dry-run: to run imperative command without creating object&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;record: record the command that was used to make a change&lt;/li&gt;&#10;&lt;li&gt;-o: set output format, wide, yaml, or jsonpath=&amp;#8221;expression&amp;#8221;. For example, to get pod name: &amp;gt; kubectl get pods -l run=nginx -o jsonpath=&amp;#8221;{.items[0].metadata.name}&amp;#8221;&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;sort-by: use JSONPath expression&lt;/li&gt;&#10;&lt;li&gt;&amp;#8211;selector: filter results &lt;strong&gt;by label&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-build-k8s-cluster-using-kubeadm"&gt;Build K8s cluster using kubeadm&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The CKA exam requires you to know how to build cluster with kubeadm. This involves installing four components (docker-ce, kubeadm, kubectl and kubelet), as outlined below:&lt;/p&gt;&#10;&lt;table id="tablepress-12" class="tablepress tablepress-id-12 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;th class="column-1"&gt;step&lt;/th&gt;&lt;th colspan="2" class="column-2"&gt;command&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td class="column-1"&gt;1. Install docker-ce&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -&lt;br /&gt;&#10;&gt; sudo add-apt-repository \&lt;br /&gt;&#10; "deb [arch=amd64] https://download.docker.com/linux/ubuntu \&lt;br /&gt;&#10; $(lsb_release -cs) \&lt;br /&gt;&#10; stable"&lt;br /&gt;&#10;&gt; sudo apt-get update&lt;br /&gt;&#10;&gt; sudo apt-get install -y docker-ce=18.06.1~ce~3-0~ubuntu&lt;br /&gt;&#10;&gt; sudo apt-mark hold docker-ce&lt;br /&gt;&#10;&gt; sudo systemctl status docker&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-1"&gt;2. Install kubeadm, kubelet and kubectl&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | sudo apt-key add -&lt;br /&gt;&#10;cat &lt;&lt; EOF | sudo tee /etc/apt/sources.list.d/kubernetes.list&lt;br /&gt;&#10;deb https://apt.kubernetes.io/ kubernetes-xenial main&lt;br /&gt;&#10;EOF&lt;br /&gt;&#10;&gt; sudo apt-get update&lt;br /&gt;&#10;&gt; sudo apt-get install -y kubelet kubeadm kubectl&lt;br /&gt;&#10;&gt; sudo apt-mark hold kubelet kubeadm kubectl&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-1"&gt;3. Form a K8s cluster&lt;/td&gt;&lt;td class="column-2"&gt;On master node:&lt;br /&gt;&#10;&gt; sudo kubeadm init --pod-network-cidr=10.244.0.0/16&lt;br /&gt;&#10;This command prints out a command for worker nodes to join.&lt;br /&gt;&#10;&lt;/td&gt;&lt;td class="column-3"&gt;On worker node:&lt;br /&gt;&#10;sudo the command generated on master&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-1"&gt;4. Configure kubectl&lt;/td&gt;&lt;td class="column-2"&gt;On master node:&lt;br /&gt;&#10;&gt; mkdir -p $HOME/.kube&lt;br /&gt;&#10;&gt; sudo cp -i /etc/kubernetes/admin.conf $HOME/.kube/config&lt;br /&gt;&#10;&gt; sudo chown $(id -u):$(id -g) $HOME/.kube/config&lt;br /&gt;&#10;&lt;/td&gt;&lt;td class="column-3"&gt;Optionally on worker node:&lt;br /&gt;&#10;&gt; mkdir -p $HOME/.kube&lt;br /&gt;&#10;then scp $HOME/.kube/config from control plane node&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-1"&gt;5. Set up cluster networking&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; echo "net.bridge.bridge-nf-call-iptables=1" | sudo tee -a /etc/sysctl.conf&lt;br /&gt;&#10;&gt; sudo sysctl -p&lt;br /&gt;&#10;Then from any environment with kubectl, bring up the system pods for cluster networking&lt;br /&gt;&#10;&gt; kubectl apply -f https://raw.githubusercontent.com/coreos/flannel/master/Documentation/kube-flannel.yml&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-12 from cache --&gt;&#10;&lt;h3 class="wp-block-heading" id="h-add-new-node-to-kubeadm-cluster"&gt;Add new node to KubeAdm cluster&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This is fairly simple with the help of kubeadm. The node to join cluster must be able to communicate with master node. Create a token and print join command from master node:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; kubeadm token create --print-join-command&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then from the node to join, run this command &lt;strong&gt;as sudo&lt;/strong&gt;. You will see that it performs the TLS bootstrap for you. Once completed, the standard output will say this node has joined the cluster. You can confirm with command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; kubectl get nodes&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Sometimes one needs to migrate pods to the newly joined node. This can be done by draining the existing nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note you can also use &lt;a href="https://github.com/kubernetes-sigs/kubespray" class="rank-math-link"&gt;kubespray &lt;/a&gt;to build K8s cluster as &lt;a href="https://static.digihunch.com/2020/12/instance-initialization-with-aws-cdk-in-python/" class="rank-math-link"&gt;previously &lt;/a&gt;discussed, and &lt;a href="https://github.com/digihunch/kubelab" class="rank-math-link"&gt;here &lt;/a&gt;is a my IaC project to launch AWS instances and build a K8s cluster with kubespray on top of it. For my learning, I often create a GKE (Google Kubernetes Engine) cluster from GCP&amp;#8217;s cloudshell. There is a &lt;a href="https://cloud.google.com/kubernetes-engine/docs/quickstart" class="rank-math-link"&gt;guide&lt;/a&gt; on how to start a cluster but it comes down to three commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ gcloud config set compute/zone us-east1-b&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ gcloud container clusters create tcluster --num-nodes&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ gcloud container clusters get-credentials tcluster&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The third command above is to configure kubectl on the cloudshell. Follow &lt;a href="https://cloud.google.com/anthos/clusters/docs/on-prem/1.5/how-to/ssh-cluster-node" class="rank-math-link"&gt;this&lt;/a&gt; guide if you need to SSH to node.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-upgrade-kubeadm-cluster"&gt;Upgrade KubeAdm cluster&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/kubeadm/kubeadm-upgrade/"&gt;This&lt;/a&gt; involves upgrade three components (kubeadm, kubectl and kubelet) on two types of node: master node and worker node. They steps vary slightly for two nodes. But drain and uncordon is needed for both types of nodes. Pick a node and follow the steps below:&lt;/p&gt;&#10;&lt;table id="tablepress-13" class="tablepress tablepress-id-13 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;th class="column-1"&gt;Step&lt;/th&gt;&lt;th colspan="2" class="column-2"&gt;Command&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td class="column-1"&gt;1. drain the node from kubectl client (e.g. master node)&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; sudo kubectl drain nodename --ignore-daemonsets&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-1"&gt;2. Determine kubeadm target version&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; apt-mark showhold&lt;br /&gt;&#10;&gt; sudo apt-mark unhold kubeadm kubectl kubelet&lt;br /&gt;&#10;&gt; apt list --installed | grep kube&lt;br /&gt;&#10;&gt; apt-cache show kubeadm | less&lt;br /&gt;&#10;&gt; sudo apt-get install -y kubeadm=1.20.2-00&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-1"&gt;3. update kubeadm&lt;/td&gt;&lt;td class="column-2"&gt;On master node:&lt;br /&gt;&#10;&gt; sudo kubeadm upgrade plan v1.20.2&lt;br /&gt;&#10;&gt; sudo kubeadm upgrade apply v1.20.2&lt;br /&gt;&#10;&lt;/td&gt;&lt;td class="column-3"&gt;On worker node:&lt;br /&gt;&#10;&gt; sudo kubeadm upgrade node&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td class="column-1"&gt;4. On the node to update, determine target version for kubectl and kubelet, then install&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; apt-cache show kubectl | less&lt;br /&gt;&#10;&gt; apt-cache show kubelet | less&lt;br /&gt;&#10;&gt; sudo apt-get install -y kubectl=1.20.2-00 kubelet=1.20.2-00&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-1"&gt;5. Restart kubelet&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; sudo systemctl daemon-reload&lt;br /&gt;&#10;&gt; sudo systemctl restart kubelet&lt;br /&gt;&#10;&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-7"&gt;&#10;&#9;&lt;td class="column-1"&gt;6. Uncordon&lt;/td&gt;&lt;td colspan="2" class="column-2"&gt;&gt; kubectl uncordon nodename&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-13 from cache --&gt;&#10;&lt;h3 class="wp-block-heading" id="h-backup-and-restore-etcd"&gt;Backup and restore Etcd&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://etcd.io/"&gt;Etcd&lt;/a&gt; is a distributed key-value store. It uses Raft protocol for distributed consensus. Etcd is the third distributed system I touch on. The previous two are: Cassandra (using Paxos protocol for distributed consensus) and ZooKeeper (using ZAB protocol). &lt;a href="https://www.alibabacloud.com/blog/a-brief-analysis-of-consensus-protocol-from-logical-clock-to-raft_594675"&gt;Here&lt;/a&gt; is a good article that summarizes the protocols. As for the exam we only need to use etcd with the client tool.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The etcd itself can run on a cluster of servers, each running etcd as a systemd service as etcd/etcd (user/group). It can be deployed in two ways: &lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;stacked etcd: an instance of etcd lives with kube-api-server on the same control plane node&lt;/li&gt;&#10;&lt;li&gt;external etcd: in a dedicated cluster of etcd&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Alternatively, etcd can run as a pod, most likely in kube-system namespace. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The etcd service listens on port 2379 for client communication and on port 2380 for server (peer-to-peer) communication. When the systemd service was initialized there are a few key environment variables (e.g. cert locations, ETCD_DATA_DIR) privoded as configuration. To see them, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; cat /etc/systemd/system/etcd.service | grep Env&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;These environment variables (prefixed with ETCD_) are for the service only. They can provide current configuration information for us to use later. &amp;nbsp;When it’s running as a pod, check out the directory for static pod for the yaml declaration (e.g. /etc/Kubernetes/manifests/etcd.yaml), where these parameters are passed in as environment variable.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The &lt;a href="https://kubernetes.io/docs/tasks/administer-cluster/configure-upgrade-etcd/"&gt;etcdctl&lt;/a&gt; utility is a command line client for etcd. The default API version is 3 so no need any more to set ETCDCTL_API=3 before each command. The utility needs three arguments three arguments (&amp;#8211;cacert, &amp;#8211;cert, and &amp;#8211;key) but we can pass the information via environment variables:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_CACERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt; /home/cloud_user/etcd-certs/etcd-ca.pem&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_CERT&lt;span style="color:#f92672"&gt;=&lt;/span&gt; /home/cloud_user/etcd-certs/etcd-server.crt&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_KEY&lt;span style="color:#f92672"&gt;=&lt;/span&gt; /home/cloud_user/etcd-certs/etcd-server.key&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; export ETCDCTL_ENDPOINTS&lt;span style="color:#f92672"&gt;=&lt;/span&gt;https://etcd1:2379&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The environment variable names are uppercase of the argument name with prefix ETCDCTL_. Only global options of arguments can be supplied via environment variables. They remain effective throughout the rest of activities. Also note that the CACERT is needed only when client-cert-auth is true. Now to backup, we can simply run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; etcdctl snapshot save /home/cloud_user/etcd_backup.db&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To restore from a file, you want to remove existing etcd data directory first. The directory can be found in ETCD_DATA_DIR variable. Suppose it is /var/lib/etcd, you need root permission to write to it, then correct ownership before starting the service:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; sudo systemctl stop etcd &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo mv /var/lib/etcd/ /tmp/&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; sudo etcdctl snapshot restore /home/cloud_user/etcd_backup.db --data-dir /var/lib/etcd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; sudo chown -R etcd:etcd /var/lib/etcd &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; sudo systemctl start etcd&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To verify the restore result, simply run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; etcdctl get cluster.name&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 class="wp-block-heading" id="h-object-management"&gt;Object Management&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the CKA exam, we need to interact with many types of built-in Kubernetes objects.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;RBAC objects:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;A Role defines permissions &lt;strong&gt;within namespace&lt;/strong&gt;.&lt;/li&gt;&#10;&lt;li&gt;A ClusterRole defines &lt;strong&gt;cluster-wide&lt;/strong&gt; permissions.&lt;/li&gt;&#10;&lt;li&gt;Both Roles and ClusterRoles are K8s objects that defines a set of permissions&lt;/li&gt;&#10;&lt;li&gt;RoleBinding and ClusterRoleBinding are objects that connect Roles and ClusterRoles to users.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Service Account: an account &lt;strong&gt;used by container processes&lt;/strong&gt; within Pods to authenticate the K8s API. If your Pods need to communicate with the K8s API, you can use service accounts to control their access.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="has-white-background-color has-background wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="271px" viewBox="-0.5 -0.5 271 261" style="max-width:100%;max-height:261px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="150" y="70" width="120" height="50" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 95px; margin-left: 152px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;RoleBinding&lt;br&gt;* roleRef&lt;br&gt;* subjects&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="152" y="99" fill="#000000" font-family="Helvetica" font-size="12px"&gt;RoleBinding&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="70" width="120" height="50" fill="#dae8fc" stroke="#6c8ebf" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 95px; margin-left: 2px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;ClusterRoleBinding&lt;br&gt;* roleRef&lt;br&gt;* subjects&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="2" y="99" fill="#000000" font-family="Helvetica" font-size="12px"&gt;ClusterRoleBinding&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="90" y="0" width="100" height="30" rx="4.5" ry="4.5" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 98px; height: 1px; padding-top: 15px; margin-left: 91px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;ServiceAccount&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="140" y="19" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;ServiceAccount&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="0" y="170" width="120" height="90" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 215px; margin-left: 2px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;&lt;div&gt;&lt;span&gt;ClusterRole:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;* rules&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; apiGroups&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resources&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resourceNames&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; verbs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="2" y="219" fill="#333333" font-family="Helvetica" font-size="12px"&gt;ClusterRole:&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="150" y="170" width="120" height="90" fill="#f5f5f5" stroke="#666666" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe flex-start; width: 118px; height: 1px; padding-top: 215px; margin-left: 152px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: left; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #333333; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;&lt;div&gt;&lt;span&gt;Role:&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;* rules&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; apiGroups&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resources&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; resourceNames&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;span&gt;&amp;nbsp; &amp;nbsp;&amp;#8211; verbs&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="152" y="219" fill="#333333" font-family="Helvetica" font-size="12px"&gt;Role:&amp;#8230;&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 196.37 15 L 203.18 15 Q 210 15 210 25 L 210 70" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 191.12 15 L 198.12 11.5 L 196.37 15 L 198.12 18.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 83.63 15 L 70 15 Q 60 15 60 25 L 60 70" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 88.88 15 L 81.88 18.5 L 83.63 15 L 81.88 11.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 60 120 L 60 163.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 60 168.88 L 56.5 161.88 L 60 163.63 L 63.5 161.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 210 120 L 210 163.63" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 210 168.88 L 206.5 161.88 L 210 163.63 L 213.5 161.88 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/g&gt;&lt;a transform="translate(0,-5)" xlink:href="https://www.diagrams.net/doc/faq/svg-export-text-problems" target="_blank" rel="noopener"&gt;&lt;text text-anchor="middle" font-size="10px" x="50%" y="100%"&gt;Viewer does not support full SVG 1.1&lt;/text&gt;&lt;/a&gt;&lt;/switch&gt;&lt;/svg&gt;&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Inspect resource usage either with a K8s Metrics Server, or by command:&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&amp;gt; kubectl top pod --sort-by &amp;lt;JSONPATH&amp;gt; --selector &amp;lt;selector&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://andrewlock.net/running-kubernetes-and-the-dashboard-with-docker-desktop/"&gt;Here&lt;/a&gt; is a good guide to install metrics server and dashboard (e.g. on docker-desktop).&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-pods-and-containers"&gt;Pods and Containers&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;ConfigMaps: store data in key-value map.&lt;/li&gt;&#10;&lt;li&gt;Secrets: same as ConfigMaps but for sensitive data only&lt;/li&gt;&#10;&lt;li&gt;Two ways to pass ConfigMap and Secret data to your container:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;As environment variables in container operating system&lt;/li&gt;&#10;&lt;li&gt;As files presented on mounted volumes in container file system.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Container Resource management:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Resource requests: K8s scheduler will use resource requests to avoid scheduling pods on nodes that do not have enough available resources. 1 CPU unit = 1/1000 of one core&lt;/li&gt;&#10;&lt;li&gt;Resource limits: allow you to limit the amount of resources your containers can use. The container runtime is responsible for enforcement. The enforcement behaviour is different. For example, some terminates container that attempts to use more resource than the limit.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Probes&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Liveness Probe: automatically determine whether or not a container application is in a healthy state. By default K8s does not consider a container to be down until the container process stops. Liveness Probe allow you to customize this detection mechanism and make it more sophisticated.&lt;/li&gt;&#10;&lt;li&gt;Startup Probes: similar to liveness probes. However, while liveness probes run constantly on a schedule, startup probes run at container startup and stop running once they succeed. Startup probes are used to determine when the application has successfully started up. It is especially useful for legacy applications that can have long startup times.&lt;/li&gt;&#10;&lt;li&gt;Readiness Probes: determine when a container is ready to accept requests. When you have a service backed by multiple container endpoints, user traffic will not be sent to a particular pod until its containers have all passed the readiness checks defined by their readinesse probes. Use readiness probes to prevent user traffic from being sent to pods that are still in the process of starting up.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Restart policy for self-healing pods&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;(default) Always: container will always be restarted if they stop, even if they completed successfully (returned 0).&lt;/li&gt;&#10;&lt;li&gt;OnFailure: container will be restarted if the container process exists with an error code, or the container is determined to be unhealthy by a liveness probe.&lt;/li&gt;&#10;&lt;li&gt;Never: let it be&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Multi-container pods:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;containers share the same networking namespace and can communicate with one another on any port, even if the port is not exposed to the cluster&lt;/li&gt;&#10;&lt;li&gt;Container can use volumes to share data in a Pod. Example: a legacy application is hard-coded to write log output to a file on disk. You use a sidecar container to read the log file from shared volume and prints it to the console so the log output will appear in the container log.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Init containers: containers that run once during the startup process of a pod. A pod can have any number of init containers, and they will each run once into completion, before the next init container starts. You may use init containers to perform a variety of startup tasks, they can contain and use software and setup scripts that are not needed by your main containers. They are often useful in keeping your main containers lighter and more secure by offloading startup tasks to a separate container. Use case include:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;cause a pod to wait for another K8s resource to be created before finishing startup&lt;/li&gt;&#10;&lt;li&gt;perform sensitive startup steps securely outside of app containers&lt;/li&gt;&#10;&lt;li&gt;populate data into a shared volume at startup&lt;/li&gt;&#10;&lt;li&gt;communicate with another service at startup&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Scheduling: Scheduler (a component in control plane) assigns Pods to a suitable Node so kubelets can run them. The factor taken into account:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;resource request vs available node resources&lt;/li&gt;&#10;&lt;li&gt;various configurations that affect scheduling using node labels&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Pod allocation&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;nodeSelector is an attribute of Pod to allow you to limit which Node(s) the Pod can be scheduled on. The selector is based on label.&lt;/li&gt;&#10;&lt;li&gt;nodeName is an attribute of Pod that allows you to bypass scheduling and assign Pod to a specific Node by name.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;DaemonSet: automatically runs a copy of a Pod on each node. When a new node is added to the clsuter, DaemonSet will run a new copy of the Pod on it. DaemonSets also respect normal scheduling rules around node labels, taints and tolerations. If a pod would not normally be scheduled on a node, a DaemonSet will not create a copy of the Pod on that node.&lt;/li&gt;&#10;&lt;li&gt;Static Pod: A Pod that is managed directly by the kubelet on a node, not by the K8s API server. They can run even if there is not K8s API server present. Kubelet automatically creates static Pods from YAML manifest files located in the manifest path on the node.&lt;/li&gt;&#10;&lt;li&gt;Mirror Pod: Kubelet will create a mirror Pod for each static Pod. Mirror Pods allow you to see the status of the static Pod via the K8s API, but you cannot change or manage them via the API.&lt;/li&gt;&#10;&lt;li&gt;Taints: applied to nodes to repel a set of pods. A taint specifies key-value and effect. Effect can be &lt;code&gt;NoSchedule&lt;/code&gt; or &lt;code&gt;NoEffect&lt;/code&gt;. The former prevents pods without matching tolerations to schedule to the tainted node. The latter also evicts pre-existing pods with no matching toleration. &lt;/li&gt;&#10;&lt;li&gt;Tolerations: applied to pods so they can be scheduled to nodes with matching taints. A toleration consists of key-value pair, effect and operation. The operation can be &lt;code&gt;Equal&lt;/code&gt; or &lt;code&gt;Exists&lt;/code&gt;. To determine whether a toleration matches a taint. The keys and the effects must be the same. In addition:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;the operator is Exists (and thus no value should be specified in the toleration); or&lt;/li&gt;&#10;&lt;li&gt;the operator is Equal, and all the values match those of the taints;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we have three ways to influence the scheduling behaviour. The first, is simply by specifying &lt;code&gt;nodeSelector&lt;/code&gt; on the Pod, with the required the node label. The second, as just discussed, is to use &lt;code&gt;Taints&lt;/code&gt; and &lt;code&gt;Tolerations&lt;/code&gt;. The third way, is similar to the first, using &lt;code&gt;nodeAffinity&lt;/code&gt; attributes on Pods. nodeAffinity is more powerful and flexible than nodeSelector by supporting more complex scheduling rules (e.g. matching rules).&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Use Node Affinity when your scheduling rule is based on &lt;span style="text-decoration: underline" class="underline"&gt;direct condition&lt;/span&gt;, i.e. &lt;span style="text-decoration: underline" class="underline"&gt;schedule a Pod to this Node when XXX&lt;/span&gt;. In this case, you have &lt;a href="https://kubernetes.io/docs/reference/labels-annotations-taints"&gt;well-known labels&lt;/a&gt; on nodes, and specify &lt;a href="https://kubernetes.io/docs/tasks/configure-pod-container/assign-pods-nodes-using-node-affinity/#schedule-a-pod-using-required-node-affinity"&gt;nodeAffinity&lt;/a&gt; on Pods. &lt;/li&gt;&#10;&lt;li&gt;Use Taints and Tolerations when your scheduling rule is based on &lt;span style="text-decoration: underline" class="underline"&gt;inverse statement, i.e. do not schedule a Pod to this Node unless XXX&lt;/span&gt;. In this case, you put a taint &amp;#8220;MyCondition:NoSchedule&amp;#8221; on a Node, so that no Pod will ever get scheduled to this Node. The only exception is when a Pod has the Toleration &amp;#8220;MyCondition:NoSchedule&amp;#8221;.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-deployments"&gt;Deployments&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Deployment is an object that defines a desired state for a ReplicaSet (a set of replica Pods). The Deployment Controller seeks to maintain the desired state by creating, deleting, and replacing Pods with new configurations.&lt;/li&gt;&#10;&lt;li&gt;With Deployments, you can horizontally scale an application up and down by changing the number of replicas. You can perform rolling updates and rollback.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-networking"&gt;Networking&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;The K8s network model defines how Pods communicate with each other, regardless of which Node they are running on.&lt;/li&gt;&#10;&lt;li&gt;Each Pod has its own unique IP address within the cluster. Any Pod can reach any other Pod using that Pod&amp;#8217;s IP address. This creates a virtual network that allows Pods to easily communicate with each other.&lt;/li&gt;&#10;&lt;li&gt;One type of K8s network plugin is CNI plugin. It has many flavours such as Calico. Each plugin has its own unique installation process. Kubenetes nodes will remain &lt;strong&gt;NotReady&lt;/strong&gt; until a network plugin is installed.&lt;/li&gt;&#10;&lt;li&gt;The K8s virtual network uses a DNS (e.g. a Kubeadm cluster uses CoreDNS pod in kube-system namespace) to allow Pods to locate other Pods and Services using domain names. The Pod DNS name follows this format: pod-ip-address.namespace.pod.cluster.local&lt;/li&gt;&#10;&lt;li&gt;A K8s NetworkPolicy is an object that allows you to control the flow of network communication to and from Pods so you can isolate traffic. NetworkPolicy can apply to Ingress (using from selector), Egress (using to selector) or both.&lt;/li&gt;&#10;&lt;li&gt;NetworkPolicy has an attribute podSelector to determine to which Pods in the namespace the NetworkPolicy applies, by selecting Pods by with Pod labels.&lt;/li&gt;&#10;&lt;li&gt;By default, Pods are considered non-isolated and completed open to all communication. If any NetworkPolidy selects a Pod, the Pod is considered isolated and will only be open to traffic allowed by NetworkPolicies.&lt;/li&gt;&#10;&lt;li&gt;A variety of selector can be used: podSelector, namespaceSelector, ipBlockSelector and port.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-services"&gt;Services&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Services provide a way to expose an application running as a set of pods, so clients can access applications in an abstract way without needing to be aware of the application pods. In this model, client make requests to a Service, which routes traffic to its pods in a load-balanced fashion&lt;/li&gt;&#10;&lt;li&gt;Endpoints are the backend entities to which Services route traffic. If there are multiple Pods behind a service, each Pod will have an endpoint associated with the service.&lt;/li&gt;&#10;&lt;li&gt;Each service has a type that determines how and where service will expose your application.&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;ClusterIP: expose application inside the cluster network&lt;/li&gt;&#10;&lt;li&gt;NodePort: expose application outside the cluster network&lt;/li&gt;&#10;&lt;li&gt;LoadBalancer: expose application outside thecluster network, but use an extermal cloud load balancer from cloud platform.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Services are assigned with DNS names. The FQDN follows this format: service.namespace.svc.cluster-domain.example, which is used by pods across namespaces&lt;/li&gt;&#10;&lt;li&gt;Pods within the same namespace can reference service simply by service name.&lt;/li&gt;&#10;&lt;li&gt;To manage external access to service, you can also use Ingress object. Ingress object is capable of providing more functionality than a simple NodePort Service, such as SSL termination, advanced load balancing, or name-based virtual hosting. You must install one or more Ingerss controller (many different implementations) to back up the ingress objects.&lt;/li&gt;&#10;&lt;li&gt;Ingress defines a set of routing rules. Each rule has a set of paths, each with a backend. Requests matching a path will be routed to its associated backend.&lt;/li&gt;&#10;&lt;li&gt;If a Service uses a named port, an ingress can also use the port&amp;#8217;s name (instead of port number) to choose to which port of a service it will route.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-storage"&gt;Storage&lt;/h3&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Volumes allow you to store data outside the container file system, while allowing the container to access the data at runtime. When Pod is gone, volumes do not persist.&lt;/li&gt;&#10;&lt;li&gt;Persistent Volumes are a slightly more advanced form of Volume. They allow you to treat storage as an abstract resource and consume it in Pods. PV can be provisioned separately by storage administrator, and they persist regardless of pod lifecycle. PV needs to be claimed by pods. PV uses a set of attributes to describe the underlying storage resource.&lt;/li&gt;&#10;&lt;li&gt;Both volumes and PVs each have a volume type: NFS, Cloud (AWS, Azure, GCP), ConfigMaps and Secrets, Simple Directory on node&lt;/li&gt;&#10;&lt;li&gt;Two volume types to distinguish:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;hostPath: store data in a specified directory on K8s node&lt;/li&gt;&#10;&lt;li&gt;emptyDir: store data in dynamically created location on the node. The directory exists only as long as the Pod exists on the node. The directory and the data are deleted as Pod is removed. This type is useful for simply sharing data between containers in the same pod.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Both volumes and PVs are specified under Pod, and individual containers must include volumeMounts object to map volume name to local mountPath&lt;/li&gt;&#10;&lt;li&gt;Storage Class object allow K8s admins to specify the types of storage services they offer on their platform. A key property is allowVolumeExpansion. This allows PVC to resize. At storage class level, there are two reclaim policies: Retain and Delete. The default is Delete.&lt;/li&gt;&#10;&lt;li&gt;PV has an attribute named persistentVolumeReclaimPolicy. This is reclaim policy at PV level. If the attribute is not defined, it is inherited from storage class. The persistentVolumeReclaimPolicy has three options. When PVC is deleted:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Retain: keeps all data but requires admin to manually reclaim the volume (i.e. delete PV, clean up data, delete storage asset)&lt;/li&gt;&#10;&lt;li&gt;Delete (cloud storage only): deletes both PV and the underlying storage resource automatically&lt;/li&gt;&#10;&lt;li&gt;Recycle: scrub (rm -rf /vol/) all data in the underlying storage resource, and allow the volume to be reused.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;PVC represents a user&amp;#8217;s request for storage resources. It defines a set of attributes similiar to those of a PV. When a PVC is created, it will look for a PV that is able to meet the requested criteria. If it finds one, it will automatically be bound to the PV. PVC can be mounted to a Pod&amp;#8217;s containers just like any other volume&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In general the CKA exam experience is quite positive and rewarding. In future posts I will shift focus on Kubernetes not only for the CKA exam, but also for keeping track of my learning.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Good luck with your CKA exam.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/04/public-key-infrastructure-pki/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Public Key Infrastructure (PKI) – Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/05/secure-web-application-deployment/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Secure web application deployment&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Basic Resource Object in Kubernetes 1 of 2</title><link>https://static.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/</link><pubDate>Sat, 16 Jan 2021 22:13:00 -0400</pubDate><guid>https://static.digihunch.com/2021/01/basic-kubernetes-resource-object-1-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;For someone from a system administration background, it would be amazing to discover that Kubernetes provides a solution to every pain point in the traditional software deployment landscape. On the contrary, it also brings about a lot of complexity due to the types of resource objects introduced. &lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/pod-128.png" alt=""/&gt;&lt;figcaption&gt;Pod&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Pod is a shared execution environment for one or more containers. The containers running in a Pod share resources such as memory, volumes, network namespace (e.g. IP address, port range, hostname, routing table), UTS namespace (e.g. hostname) and IPC namespace (Unix domain sockets). Every Pod has its own IP address that is routable on the Pod network. All Pods connect to the same flat network called the Pod network.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A pod most commonly only contains a single container, which is considered a good practice, unless there is good reasons to put two containers in a single pod (sharing resource). One such good reason is to co-schedule tightly-coupled workloads (such as logging, sharing volume, etc). Within the Pod, the containers communicate with each other via localhost interface of the Pod. In service mesh model, there is also a proxy container in each application Pod. The proxy container handles all network traffic entering and leaving the Pod. Also, within the Pod, to avoid competing for resources, individual containers can have their own cgroup limits, which actively police resource usage.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Pods are mortal (composable). They come and go (with dynamic IPs), so application should not store state in Pods. Deploying a Pod is an atomic (all or nothing) operation. When a Pod is scheduled to a node, it enters the pending state while the container runtime on the node downloads images and starts any containers. Once&amp;#8217;s everything is ready, the Pod enters the running state.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We typically deploy Pods via higher-level controllers such as Deployments (to offer scalability and rolling updates), DaemonSets (to run one instance of a service on every node in the cluster), StatefulSets (for stateful application components), and CronJobs (for short-lived tasks that need to run at set times just like a Linux &lt;a href="https://static.digihunch.com/2018/05/cron-and-logrotate-in-centos/"&gt;cronjob&lt;/a&gt;).&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/deploy-128.png" alt=""/&gt;&lt;figcaption&gt;Deployments&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Deployment manages multiple replicas of the same Pod (via ReplicaSets). To follow best practice, you interact with Deployments instead of ReplicaSets, and use YAML file (declarative model). You can perform rolling update or rollback.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/rs-128.png" alt=""/&gt;&lt;figcaption&gt;ReplicaSets&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ReplicaSets provide self-healing and scaling capabilities to Pods. If a Pod fails, it will be replaced. If load increases, then the ReplicaSets creates new Pod. This is all implemented with a background reconciliation loop that is constantly checking whether the right number of Pod replicas are present on the cluster. If not, Kubernetes declares a red-alert condition, orders the control plan to bring up more replicas. The best practice however, is that you should not manage ReplicaSets directly. Instead, you should perform all actions against the Deployment object and leave the Deployment to manage ReplicaSets.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://storage.googleapis.com/cdn.thenewstack.io/media/2017/11/07751442-deployment.png" alt=""/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/svc-128.png" alt=""/&gt;&lt;figcaption&gt;Service&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Pods themselves are mortal (IP churn) so it&amp;#8217;s a bad idea to talk directly to individual Pods. Service object provides stable and reliable networking for a set of dynamic Pods. Service gets its own stable IP address, stable port and stable DNS name. It can also load-balance request across the Pods.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Services are loosely coupled with Pods via labels and label selectors. You specify label selector for Service and labels on Pods when creating them. All the labels in label selector are used to select target Pods. Service acts as front-end, consisting of stable IP, DNS name and port, with Pods acting as backend, consisting of constantly changing Pods. Labels are simple yet extremely powerful. During blue-green update, you may use version label as a technique to control what backend pool is used behind Service object. For example, start with version=1, deploy version 2, remove version from label selector, and eventually add version=2 back to label selector, before phasing out the old Deployment.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Services learn Pod status via Endpoint object, more details to follow.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are several types of Service, the default being &lt;strong&gt;ClusterIP&lt;/strong&gt;. A ClusterIP Service has a stable IP address and port that is only accessible from inside the cluster. The ClusterIP gets registered against the name of the Service on the cluster&amp;#8217;s internal DNS service (implemented via coreDNS with Control plane Pods). This means that the ClusterIP only works within the cluster, not outside. The other type of Service is called a &lt;strong&gt;NodePort&lt;/strong&gt;, which is built on top of ClusterIP, but also enables access from outside of the cluster. The Service object has a reliable NodePort mapped to every node in the cluster. The NodePort value is the same on every cluster. Traffic from outside of the cluster can hit any node in the cluster on the NodePort and get through the the Pods.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Other types of Services include LoadBalancer and ExternalName. LoadBalancer Services integrate with load-balancers from cloud provider. They build on top of NodePort Services and allow clients on the internet to reach your Pods via the load balancer of cloud vendor. ExternalName Services route traffic to systems outside of your K8s cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For service discovery within the cluster, Kubelet program every container with the knowledge of the internal DNS (/etc/resolv.conf). The internal DNS service watches constantly the API server for new Services and automatically register them in the DNS. The other means of service discovery is through environment variables. However, in this method the Pods have no way of learning about new Services added to the cluster after the Pod itself is created.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/ep-128.png" alt=""/&gt;&lt;figcaption&gt;Endpoints&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Endpoints object is a dynamic list of all the healthy Pods on the cluster that match the Service&amp;#8217;s label selector. Each Service gets its own Endpoints objects for an up-to-date list of matching Pods. Kubernetes is constantly evaluating the Service&amp;#8217;s label selector against the currently list of healthy Pods on the cluster. Any new Pods that match the selector get added to the Endpoints object, and any Pods that disappear get removed.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When sending traffic to Pods, via a Service, an application will query the cluster&amp;#8217;s internal DNS for the IP address of a Service, then sends the traffic to this stable IP address. Service then forwards it on to a Pod. Kubernetes-native application however, has the ability to query the Endpoints API directly, bypassing the DNS lookup and use of the Service&amp;#8217;s IP.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It requires a thorough understanding of Services, Endpoints and the service discovery mechanism to perform effective troubleshooting in Kubernetes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The aforementioned internal DNS service (we usually call it the &amp;#8220;cluster DNS&amp;#8221;) is implemented in the kube-system Namespace as a set of Pods managed by a Deployment called coredns. These Pods are fronted by a Service called kube-dns. The cluster DNS is constantly looking for new Services and automatically register their details (metadata.name). We might need to check the logs for each of the coredns Pods during troubleshooting. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The kubelet process on every node is watching the API Server for new Endpoints objects, when it sees them, it creates local networking rules that redirect ClusterIP traffic to Pod IPs, using &lt;a href="https://static.digihunch.com/2020/11/ipvs-iptables-and-kube-proxy/" class="rank-math-link"&gt;IPVS technology&lt;/a&gt; on Linux to manage these rules.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/ds-128.png" alt=""/&gt;&lt;figcaption&gt;DaemonSet&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A DaemonSet ensures that all (or some) Nodes run a copy of a Pod. As nodes are added to the cluster, Pods are added to them. As nodes are removed from the cluster, those Pods are garbage collected. Deleting a DaemonSet will clean up the Pods it created.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Some typical uses of a DaemonSet are: cluster storage daemon on every node, logs collection daemon on every node, a node monitoring daemon on every node.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/hpa-128.png" alt=""/&gt;&lt;figcaption&gt;Horizontal Pod Autoscaler&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Horizontal Pod Autoscaler automatically scales the number of Pods in a replication controller, deployment, replica set or stateful set based on observed CPU utilization (or, with custom metrics support, on some other application-provided metrics). Note that Horizontal Pod Autoscaling does not apply to objects that can&amp;#8217;t be scaled, for example, DaemonSets.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Horizontal Pod Autoscaler is implemented as a Kubernetes API resource and a controller. The resource determines the behaviour of the controller. The controller periodically adjusts the number of replicas in a replication controller or deployment to match the observed average CPU utilization to the target specified by user.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are more details about HPA &lt;a href="https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/" class="rank-math-link"&gt;here&lt;/a&gt; and &lt;a href="https://cloud.google.com/kubernetes-engine/docs/concepts/horizontalpodautoscaler" class="rank-math-link"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/blob/master/icons/png/resources/labeled/sts-128.png?raw=true" alt="sts-128.png"/&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;StatefulSets are designed for stateful application, which creates and saves valuable data. The three properties that form the state of a Pod are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Pod names (&amp;lt;StatefulSetName&amp;gt;-&amp;lt;Integer&amp;gt;)&lt;/li&gt;&lt;li&gt;DNS hostnames&lt;/li&gt;&lt;li&gt;volume bindings&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;They are sometimes referred to as the Pods &lt;em&gt;sticky ID&lt;/em&gt;. StatefulSets ensures that these are all predictable and persistent. For example, failed Pods managed by a StatefulSet will be replaced by new Pods with the exact same Pod name, the exact same DNS hostname, and the exact same volumes, even if the replacement Pod is started on a different cluster Node.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that StatefulSets create one Pod at a time, and always wait for previous Pods to be &lt;em&gt;running and ready&lt;/em&gt; before creating the next. Scaling operations are also governed by the same ordered startup rules. This is different from Deployments that use a ReplicaSet controller to start all Pods at the same time, causing potential race conditions. The way StatefulSet controllers do their own self-healing and scaling is architecturally different to Deployments which use a separate ReplicaSet controller for these operations. The reason it is a game changer to know the order in which Pods will be scaled down, as well as that Pods will not be terminated in parallel, is because clustered apps that store data are usually at high risk of losing data if multiple replicas go down at the same time.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Deleting a StatefulSet does not terminate Pods in order. So you may want to scale a StatefulSet to 0 replicas before deleting it. You might also set 10 seconds grace period before terminating to allow applications a chance to flush local buffers and safely commit any writes still in flight.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, Volumes are decoupled from Pods via PersistentVolumes and PersistentVolumeClaims. So volumes have separate lifecycles to Pods and can survive Pod failures and termination operations. When a StatefulSet Pod is created, any volumes it needs are created at the same time and named in a way to connect them to the right Pod. Any time a StatefulSet Pod fails or is terminated, the associated volumes are unaffected. This allows replacement Pods to attach to the same storage as the Pods they&amp;#8217;re replacing, even if the replacement Pod is scheduled to a different cluster Node. Similarly, if a StatefulSet Pod is detected as part of a scale-down operation, subsequent scale-up operations will attach new Pods to the existing volumes that match their names.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since each StatefulSet Pod needs its own unique storage, hence its own PVC, this can be done by volumeClaimTemplate, which dynamically creates a PVC each time a new Pod replica is dynamically created. This eliminates the hassle to have to pre-create a unique PVC for every potential StatefulSet Pod.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/kubernetes/community/raw/master/icons/png/resources/labeled/ns-128.png" alt=""/&gt;&lt;figcaption&gt;Namespaces&lt;/figcaption&gt;&lt;/figure&gt;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Namespaces allows you to partition resource objects. For example, you may create a Namespace called prod and dev. Object names must be unique within Namespaces but not across Namespaces.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/12/ansible-tower-lab-environment-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AWS CDK example in Typescript – provision an AWX server&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/01/blockchain-and-di-fi/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Blockchain and DeFi&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>