<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Containers on Digi Hunch</title><link>https://static.digihunch.com/tag/containers/</link><description>Recent content in Containers on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 23 Apr 2025 13:37:45 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/containers/index.xml" rel="self" type="application/rss+xml"/><item><title>Optimize CPU and Memory for Kubernetes Pod</title><link>https://static.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/</link><pubDate>Fri, 13 Jan 2023 11:47:00 -0400</pubDate><guid>https://static.digihunch.com/2023/01/optimize-cpu-and-memory-for-kubernetes-pods/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/cpu-feature.webp" alt="Featured image of post Optimize CPU and Memory for Kubernetes Pod" /&gt;&lt;p class="wp-block-paragraph"&gt;When optimizing workload performance, it is important to understand how on earth operating system allocates CPU and memory to processes. This helps understand how to set resource limit Kubernetes Pod in an optimal way.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-cpu-resource-assignment"&gt;CPU resource assignment&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The OS distributes CPU resource to processes by the unit of time share of CPU time. Most of the time, many processes with CPU instructions (machine code) are waiting in the Job queue, for their share of CPU time in order to execute their instructions. As soon as CPU becomes idle, the CPU scheduler selects a process from the ready queue to run next:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="488" src="https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment.webp" alt="" class="wp-image-12886" style="width:552px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment.webp 1024w, https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment-300x143.webp 300w, https://static.digihunch.com/wp-content/uploads/2023/01/cpu-assignment-768x366.webp 768w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Ideally, OS should schedule CPU in a way that it should not waste any CPU cycle. It should also minimizes waiting time and response time of processes. At a high level, there are two types of CPU scheduling:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Preemptive: OS allocate CPU resources to a process for only a limited period of time and then takes those resources back. It could interrupt a running process to execute a higher priority process.&lt;/li&gt;&#10;&lt;li&gt;Non-preemptive: New processes are executed only after the current executing process has completed its execution.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://www.geeksforgeeks.org/preemptive-and-non-preemptive-scheduling/"&gt;Here&lt;/a&gt; is more information about preemptive and non-preemptive scheduling. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;CPU is compressible resource in Linux&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the &lt;a href="https://www.usenix.org/legacy/publications/library/proceedings/usenix01/freenix01/full_papers/alicherry/alicherry_html/node5.html#:~:text=All%20scheduling%20is%20preemptive%3A%20If,is%20a%20single%20run%2Dqueue."&gt;Linux&lt;/a&gt; world, all scheduling is preemptive. We also call it &lt;a href="https://en.wikipedia.org/wiki/Kernel_preemption"&gt;kernel preemption&lt;/a&gt;. As the wikipedia entry states: the&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Scheduling_(computing)"&gt;scheduler&lt;/a&gt;&amp;nbsp;is permitted to forcibly perform a&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Context_switch"&gt;context switch&lt;/a&gt;&amp;nbsp;(on behalf of a runnable and&amp;nbsp;&lt;a href="https://en.wikibooks.org/wiki/Operating_System_Design/Scheduling_Processes/Priority_Scheduling"&gt;higher-priority&lt;/a&gt;&amp;nbsp;process) on a driver or other part of the kernel during its execution, rather than&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/Computer_multitasking#Cooperative_multitasking.2Ftime-sharing"&gt;co-operatively&lt;/a&gt;&amp;nbsp;waiting for the driver or kernel function (such as a&amp;nbsp;&lt;a href="https://en.wikipedia.org/wiki/System_call"&gt;system call&lt;/a&gt;) to complete its execution and return control of the processor to the scheduler when done.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Linux scheduler implements a number of&amp;nbsp;&lt;em&gt;&lt;a href="https://access.redhat.com/documentation/en-us/red_hat_enterprise_linux/6/html/performance_tuning_guide/s-cpu-scheduler"&gt;scheduling policies&lt;/a&gt;&lt;/em&gt;, which determine when and for how long a thread runs on a particular CPU core. The scheduling policies in RHEL include real time policies such as SCHED_FIFO and SCHED_RR where processes have a sched_priority value in the range of 1 (low) to 99 (high); and normal policies such as SCHED_OTHER, SCHED_BATCH and SCHED_IDLE, where sched_priority (specified as 0) is not used in scheduling decisions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is important to understand preemptive CPU scheduling on Linux. When OS allocate CPU resource to a process for one time slot, it is not committed to the same process for the next time slot. The OS reserves the ability to revoke the next CPU use and re-assign it for processes of higher priority.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Because of this, we regard CPU as a compressible resource. The compressible characteristic impacts how we optimize CPU utilization for a process, including setting CPU request and limit for Kubernetes workload. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Memory is non-compressible resource&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A few years ago, I discussed how to&lt;a href="https://static.digihunch.com/2020/04/how-memory-usage-adds-up-in-linux/"&gt; calculate memory usage&lt;/a&gt;. A process requests memory from OS using memory allocation functions (the &lt;a href="https://man7.org/linux/man-pages/man3/malloc.3.html"&gt;malloc&lt;/a&gt; family), and return memory to OS using &lt;a href="https://man7.org/linux/man-pages/man1/free.1.html"&gt;free&lt;/a&gt; functions. The design of Linux OS knows that processes have a tendency to request more memory than they use, which causes under-utilization. In combat against under-utilization, the Linux OS supports &lt;a href="https://en.wikipedia.org/wiki/Memory_overcommitment"&gt;memory overcommitment&lt;/a&gt; (on by default), allowing processes to request more memory than what is available. The processes have access to virtual memory space and the OS may swap some pages out to disks. The overcommitment mechanism also prevents processes from crashing due to insufficient memory assignment. The kernel can also OOM kill a process when the entire system is in a crisis.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Memory is non-compressible resource. When OS assigns memory pages to a process, the process has to right to keep those pages, until the OS takes them away. Unlike assigning CPU cycles, the assignment of memory pages to processes does not have an expiry time. This is the non-compressible characteristic of memory assignment. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;CPU limit and requests for Kubernetes workload&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It was considered best practice to set request and limit for memory and CPU. However, knowing CPU is compressible resource and memory isn&amp;#8217;t, we should re-consider this practice. In short, for CPU, we should set request only, &lt;a href="https://home.robusta.dev/blog/stop-using-cpu-limits"&gt;without setting limit&lt;/a&gt;. For memory, we should set &lt;a href="https://home.robusta.dev/blog/kubernetes-memory-limit"&gt;limit to exactly the same as request&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A process has different level of demands for CPU at different times. Depending on the activity in the process, the level of demand can even be spiky. If there is a lot of iowait, it may not need a lot of CPU. But when there are lots of computing-bound activities, the program is CPU-thirsty as it is programmed to to more. The last thing we want is to throttle the CPU use for a process in such legit situations. When &lt;a href="https://medium.com/indeed-engineering/unthrottled-fixing-cpu-limits-in-the-cloud-a0995ede8e89"&gt;throttling&lt;/a&gt; happens, the process does not get sufficient time share of CPU time. At the platform level, we can&amp;#8217;t control when the Pod (process) gets busy. The best thing it can do, is trying to fit more CPU time shares to this process when it becomes CPU thirsty. When we apply a limit of CPU in workload setting, we are potentially throttling the CPU use for a process at the times it needs more CPU time shares, which is counter-productive. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We should still configure CPU request, so that kube-scheduler factors it in when scheduling multiple Pods to a Node. The CPU request alone ensures the number of Pods are not excessive. This is the only thing we can do about controlling CPU assignment for Pods. We should also monitor &lt;a href="https://wbhegedus.me/understanding-kubernetes-cpu-limits/"&gt;CPU throttling&lt;/a&gt;. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Memory limit and request for Kubernetes workload&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Memory is not compressible, therefore we should set both limit and request to the same value. We set memory request so that kube-scheduler has an idea assigning Pods. We set the limit so that no single Pod takes more memory than its fair share. Unlike CPU, once a Pod takes more memory than its fair share, the platform will have to be aggressive to reclaim it back, which may impacts the running of the Pod (process). In contrast, CPU scheduler never guarantees the assignment of CPU time share to a Pod beyond the end of the current CPU cycle.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we&amp;#8217;re setting memory limit and request with different values, we&amp;#8217;re sending a confusing signal. We&amp;#8217;re inviting Pods to use more memory than they requested. This increases the chance of memory shortage at the node level, and hence the need to OOM kill a Pod.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Horizontal autoscaling and Cluster Autoscaling&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The native HPA is metrics-based. As I &lt;a href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;previously discussed&lt;/a&gt;, neither CPU nor memory metrics are good indicators of time to scale. A process or a Pod may have a temporary high demand of CPU purely due to how programmers write the code. Even if we followed the best practices as above, I would still not regard CPU and memory metrics as a reliable indicator to drive auto scaling. If a service is a potential point of congestion, we should use a queue in front and the queue size is almost always a much better indicator of the timing to scale. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As to cluster autoscaler, on it FAQ, it says flat out that you should NOT use a &lt;a href="https://github.com/kubernetes/autoscaler/blob/master/cluster-autoscaler/FAQ.md#should-i-use-a-cpu-usage-based-node-autoscaler-with-kubernetes"&gt;CPU usage based scaling mechanism&lt;/a&gt;. I guess this is for a similar reason (compressibility). As discussed, when a Pod is pending for schedule for too long, it emits and event that drives the cluster autoscaler.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Summary&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When I first worked on Kubernetes workload I did not give this much thought and proposed the use of CPU limit. As of January 2023 I still find static code analysis tools that requires CPU limit for Pods in the check (e.g. CKV_K8S_11 on &lt;a href="https://www.checkov.io/5.Policy%20Index/kubernetes.html"&gt;Checkov&lt;/a&gt;), which leads me to investigate the issue further, and noticed more voices advocating the correct use of resource limit (such as &lt;a href="https://sysdig.com/blog/kubernetes-limits-requests/"&gt;this&lt;/a&gt; post) in 2022. For existing deployments, it is worth a review the resource limit configuration.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/12/eks-impression/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;EKS impression&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2023/01/github-action-gotchas/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;GitHub Action Gotchas&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Computing services: from PaaS to Serverless</title><link>https://static.digihunch.com/2022/10/computing-from-paas-to-serverless/</link><pubDate>Fri, 21 Oct 2022 19:31:00 -0400</pubDate><guid>https://static.digihunch.com/2022/10/computing-from-paas-to-serverless/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-computing-paas-serverless.webp" alt="Featured image of post Computing services: from PaaS to Serverless" /&gt;&lt;p class="wp-block-paragraph"&gt;Silicon Valley startups in mid-2000s likely do not run their own IT operations (i.e. renting their own data centre spaces, purchasing their own rack-mounted servers). Since the &lt;a href="https://web.archive.org/web/20070101134207/http://www.amazon.com/aws/"&gt;launch of EC2&lt;/a&gt;, AWS has been renting extra computing capacity to those startups, in the IaaS model. The leased infrastructure requires maintenance work, and AWS realized that many of these customers cannot afford specialized database admins, network admins, storage admins, or even server admins. As a result, they created a handful of managed services aiming to cut out admin overhead and let their customer focus on coding. This is how Platform-as-a-service (PaaS) came about. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s take a look at what are exactly operation activities.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-ops-activities"&gt;Ops activities&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;IT operation team manages server provisioning, installation of operating system, tuning performance, configuring auto scaling and load balancing, configure networking and storage systems, etc. Networking can be so complex that many infrastructure teams have a dedicated &lt;a href="https://en.wikipedia.org/wiki/Network_operations_center"&gt;Network Operation Center&lt;/a&gt; (NOC), who along with security team, manages key aspects of networking, such as segmentation, router configuration, load balancing, firewall configuration.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For client-server application, the client-side code will make outgoing connections, utilizing the TCP/IP stack on the host through an ephemeral port. The server-side code has to be wrapped as a service. A daemon ensures the process running this service stays up and listens to a TCP port in order to respond to request by invoking the functions. Application team usually assumes these activities.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If database is involved, then the patching, upgrade, replication, data protection are all Ops problems. If storage is involved, then Ops has to manage mass data accumulated over years, the integration between storage and database and applications, performance, replication, etc. Some larger organizations have full-time database administrator and storage administrators.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then comes container. Containers have their benefits but it increases the operation overhead by an order of magnitude. Running container application at scale warrants its own platform, most likely a Kubernetes platform, to address all of the problems above again at the cluster level. Some organization created platform team to manage container and VM platforms.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is the Ops, that turns functional code into a running business. It is also the Ops, that becomes a pain point as a startup scales. With IaaS and PaaS models, AWS managed to convince many small businesses to delegate their IT operations to AWS. This is the humble start of cloud computing.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-elastic-beanstalk"&gt;Elastic Beanstalk&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At first, I wasn&amp;#8217;t too impressed with Elastic Beanstalk, since it abstracts away too many details. However, I later realized that it has been surprisingly popular in the developer community, especially with individual developers and SMBs. It simplifies deployment to the point that their users don&amp;#8217;t need to know other AWS services, allowing them to focus on coding application logic.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You configure Applications and Environments (one application may have multiple environments). In the Environment layer, you can specify code platform (e.g. Python 3.8 on 64bit Amazon Linux 2, Java, Go, PHP, Ruby) and even container platform (Docker on EC2 or ECS). Behind the scene, Elastic Beanstalk configures EC2 instances, Elastic Load Balancers, etc on the selected VPC and integrate with logging and monitoring services. In the console, Elastic Beanstalk exposes a list of configurations options (e.g. AMI, instance type). This centralized configuration page is dummied down for those who don&amp;#8217;t want to deal with Ops. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The downside of Elastic Beanstalk is it takes away a lot of flexibility. Many developers find Elastic Beanstalk limit their choices of deployment, as their applications scale. Elastic Beanstalk does not suit for applications that demand extensive operation efforts. Its niche market is individual developers and SMB. Few enterprise applications run on Elastic Beanstalk.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-containerization-with-ecs-and-eks"&gt;Containerization with ECS and EKS&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we containerize an application, we build container images. Then we run these images with container runtimes, is a core feature of container platform. Container platform also provides orchestration engine since we frequently take containers up and down. In addition, container platform provides mechanisms for container networking and storage.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS has a couple options for container platform. ECS (Elastic Container Service) came out earlier. It organizes a group of EC2 instances as a cluster. You can manage autoscaling, &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/networking-networkmode.html"&gt;networking&lt;/a&gt;, and &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/bestpracticesguide/storage.html"&gt;persistent storage&lt;/a&gt; (EFS, FSx etc) on ECS. EKS (Elastic Kubernetes Service) is the managed Kubernetes service by AWS. Just like AKS, it provides a managed control plane along with computing nodes.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I see ECS as a proprietary and simplified container platform, and Kubernetes as an open-source standard for full-fledged container platform with an entire ecosystem. EKS includes an upstream-certified Kubernetes distribution with a set of tools specific to AWS. Since Kubernetes is the de-facto standard container platform, I prefer EKS by default, unless I can justify the use of ECS. In fact, ECS and Kubernetes have many concepts in common. For example, a &amp;#8220;Task&amp;#8221; in ECS is equivalent to a Pod in Kubernetes. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Whether it is ECS or EKS, right-sizing the computing node group is always challenging especially when the application traffic load is irregular. AWS Fargate is a technology that provides on-demand, right-sized compute capacities. It works with ECS and EKS. When integrated with EKS, we delegate the node management (e.g. scaling) to Fargate and forget about sizing the node pool.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Using ECS and Fargate involves quite a bit of configurations. To simplify that, we can use App Runner, which builds ECS cluster and uses Fargate to execute the container behind the scenes. App Runner helps client in a way similar to Elastic Beanstalk, but concentrate on Container workload.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-serverless-with-lambda-and-api-gateway"&gt;Serverless with Lambda and API Gateway&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The services above have their limitations when it comes to scaling capability. First, they cannot scale to zero. You still pay for idling resources. Also, it is not easy to find the optimal autoscaling setting. Lambda and API Gateway together solves these challenges. AWS refers to it as serverless, which has since become a buzzword. To understand what it is, let&amp;#8217;s examine two concepts:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Function as a Service&lt;/strong&gt;: service with the ability to execute code on demand. Users only pay for code execution time and do not care where the underlying runtime is&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Backend as a Service&lt;/strong&gt;: service with the ability to listen to a port and respond to web request&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Lambda itself is a function as a service. Triggered by events, it only incurs a charge when it&amp;#8217;s invoked. It does not stay up and listening to a TCP port for incoming web request, as does a backend service. In order to act as a backend, Lambda needs to pair up with API gateway. In this configuration, API gateway listens to a web request, and it fires an event to trigger the execution of Lambda function. Lambda and API gateway together makes a backend as a service. In AWS, the coupling of API gateway and Lambda function ensures an idle service does not incur computing cost.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since Lambda supports many types of events as trigger, it is also used in event-driven architecture, either standalone or from a VPC. Under the hood, Lambda runs code in a container (with a quick startup time relative to a VM).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Developers can release Lambda code by uploading zip package to S3 bucket, or just packaging code into container image. For deployment, apart from AWS console and CLI, one can leverage CloudFormation, SAM (serverless application model), or CDK.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Lambda vs Fargate&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both Lambda and Fargate are serverless capabilities, at least from a marketing perspective. Both can be used to back web service but there are differences. They provision computing resource at different granularity. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In a web service, the execution duration of a Lambda function is the response duration to an API request, in terms of seconds. While the server is waiting for a request, there is no usage of the computing resource so you&amp;#8217;re not paying for waiting for a request. However, this also creates the delay of cold-start, especially when the code size is large. There are several ways to optimize the cold start (e.g. &lt;a href="https://docs.aws.amazon.com/lambda/latest/dg/snapstart.html"&gt;SnapStart&lt;/a&gt; for Java), but none of those can completely get rid of the cold-start delay with a once-after-a-while request. A light GET call could take 5 seconds with cold start. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On the Fargate side, the resource provisioning is based on container lifecycle, instead of request lifecycle. As a result, you&amp;#8217;re still paying for wait time, and it is not per-request billing. Since the container remains up, your request is not going to experience the cold-start if it&amp;#8217;s been idle for a while. Although, Fargate saves you from the effort to right-sizing the computing nodes for container execution, it is not quite the idea of &amp;#8220;scale-to-zero when idle&amp;#8221; by itself. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Serverless Architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the white paper &lt;a href="https://docs.aws.amazon.com/whitepapers/latest/serverless-multi-tier-architectures-api-gateway-lambda/welcome.html"&gt;AWS Serverless Multi-Tier Architectures with Amazon API Gateway and AWS Lambda&lt;/a&gt;, AWS advocates the serverless architecture as a modern alternative to the traditional widely adopted three-tier architecture (presentation, logic and data tiers). In the three tier architecture, the scalability of three tier are managed separately. The modern serverless architecture that AWS whitepaper proposes uses API Gateway and Lambda function in place of Load Balancer and EC2 instances (e.g. in an Auto Scaling Group), as illustrated below:&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="432" height="345" src="https://static.digihunch.com/wp-content/uploads/2022/08/serverless.png" alt="" class="wp-image-7170"/&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both API Gateway and Lambda scale automatically to support the need of application workload. It assumes the role of logic tier in three-tier architecture but requires minimal maintenance work. For presentation tier, AWS has serverless alternatives such as CloudFront, S3. For data tier, AWS has serverless alternatives such as Amazon Aurora for relational database and DynamoDB for NoSQL. However, the &amp;#8220;no request, no pay&amp;#8221; model for Lambda does not apply to the data tier in serverless architecture.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Whilst this paradigm benefits small shop IT who wants to minimize infrastructure cost, it has downsides. There is no ability for infrastructure optimization. Since you do not manage where the code runs, client may have concerns over security (e.g. multi-tenant runtime). As business grows, keep using Lambda can result in technology lock-in. Also, a less used application usually requires warm-up time. A code start (downloading the code and preparing the environment behind the scene) can take 100ms to over a second.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;PaaS attempts to help startups simplify the &amp;#8220;grunt work&amp;#8221; of IT operation. Serverless takes it even further. The semantics of serverless computing is confusing and the &lt;a href="https://en.wikipedia.org/wiki/Serverless_computing"&gt;Wikipedia&lt;/a&gt; page acknowledges it as a misnomer. The nature of serverless model, is the cloud users delegate server capacity management to cloud platforms. The users don&amp;#8217;t need to manage servers, VMs, instances, containers, etc on their own. In a &lt;a href="https://static.digihunch.com/2022/04/knative-introduction-serving/"&gt;previous post&lt;/a&gt;, I discussed the ability to scale to zero, which is just one of the many enabling technologies of serverless. Also, &amp;#8220;no request, no pay&amp;#8221; is neither an inherent nature of serverless model. Serverless service may involve storage (e.g. data service, S3, Aurora serverless) which incurs storage cost. There is a &lt;a href="https://docs.aws.amazon.com/prescriptive-guidance/latest/website-deployment-services/welcome.html"&gt;whitepaper&lt;/a&gt; on choosing the right AWS service to deploy your website or web application, with a decision tree. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AWS pioneered serverless with Lambda release in 2014 but competitor follows. In the Azure landscape, there is an entire suite of computing services from virtual machine to serverless (also with a &lt;a href="https://docs.microsoft.com/en-us/azure/architecture/guide/technology-choices/compute-decision-tree"&gt;decision tree&lt;/a&gt; in documentation). Azure&amp;#8217;s counterpart for &lt;a href="https://azure.microsoft.com/en-ca/solutions/serverless/"&gt;serverless architecture&lt;/a&gt; is Azure Function (released in 2016 for GA) with API Management. As for GCP, the &lt;a href="https://cloud.google.com/serverless"&gt;serverless&lt;/a&gt; suite includes the event-driven Cloud Function (introduced in 2017) and Knative-based FaaS Cloud Run (introduced in 2019).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are &lt;a href="https://thenewstack.io/serverless-needs-standards-to-be-the-future-of-application-infrastructure"&gt;voices&lt;/a&gt; in advocacy of standardization of serverless model, and CNCF had since made minuscule efforts such as &lt;a href="https://cloudevents.io/?utm_source=thenewstack&amp;amp;utm_medium=website&amp;amp;utm_campaign=platform"&gt;CloudEvents&lt;/a&gt;. The status quo, unfortunately, is anything but standardized.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Lastly, here is a table that summarizes the pros and cons of each computing service model.&lt;/p&gt;&#10;&lt;figure class="wp-block-table"&gt;&lt;table class="has-fixed-layout"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Computing Service Model&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Pro&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Con&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EC2&lt;/td&gt;&lt;td&gt;&amp;#8211; Most straightforward and widespread legacy model&lt;br&gt;&amp;#8211; Legacy&lt;/td&gt;&lt;td&gt;&amp;#8211; Ops tasks can be heavy (e.g. patch and vulnerability management of OS)&lt;br&gt;&amp;#8211; Utilization can be low&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;ECS&lt;/td&gt;&lt;td&gt;&amp;#8211; Container orchestration is managed&lt;br&gt;&amp;#8211; Convenient to scale&lt;br&gt;&amp;#8211; Well integrated with other AWS services&lt;/td&gt;&lt;td&gt;&amp;#8211; Limited advanced features&lt;br&gt;&amp;#8211; Vendor lock-in&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;EKS&lt;/td&gt;&lt;td&gt;&amp;#8211; Highly scalable and flexible&lt;br&gt;&amp;#8211; Advanced, platform-neutral deployment tools available (e.g. Helm, ArgoCD, etc)&lt;br&gt;&amp;#8211; Custom configurations (e.g. operators)&lt;/td&gt;&lt;td&gt;&amp;#8211; Significant operation overhead&lt;br&gt;&amp;#8211; Steep learning curve (especially for teams)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Lambda&lt;/td&gt;&lt;td&gt;&amp;#8211; automatically scale&lt;br&gt;&amp;#8211; low ops overhead&lt;br&gt;&amp;#8211; pay per use&lt;/td&gt;&lt;td&gt;&amp;#8211; limited choices of runtime&lt;br&gt;&amp;#8211; subject to latency due to cold start; yet warm start incurs cost&lt;br&gt;&amp;#8211; not suitable for long running tasks (batch processing jobs etc)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In summary, the Lambda-based serverless model is good for stateless server-side workload with short response time (&amp;lt;15s), tolerance of cold-start, no need for portability across platforms, and no complex package dependency. &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/10/graphql-and-grpc/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;GraphQL and gRPC&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/11/aws-serverless-services-and-developer-tools/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AWS serverless services and developer tools&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Knative Eventing Introduction</title><link>https://static.digihunch.com/2022/04/knative-introduction/</link><pubDate>Fri, 29 Apr 2022 10:16:00 -0400</pubDate><guid>https://static.digihunch.com/2022/04/knative-introduction/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-knative-eventing.webp" alt="Featured image of post Knative Eventing Introduction" /&gt;&lt;p class="wp-block-paragraph"&gt;In the previous &lt;a href="https://static.digihunch.com/2022/04/knative-introduction-serving/"&gt;post&lt;/a&gt;, I mentioned that Knative Serving and Knative Eventing should be seen as two different projects. The former is supposed to be widely used as a serving layer for microservices, whereas the latter has a narrower customer base. There are a dozen companies who need to build Platform as a Service, and will benefit from event-driven architecture. They are the niche customer for Knative Eventing. In this post, we go through two demos for Eventing.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-event-driven-architecture"&gt;Event Driven Architecture&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Before jumping into Knative eventing, let&amp;#8217;s first understand the value of event-driven architecture. As covered in Knative &lt;a href="https://www.cncf.io/online-programs/event-driven-architecture-with-knative-events/"&gt;presentation&lt;/a&gt; in 2020, a growing micro-service architecture involves lots of service-to-service communications that looks like a spider web in a diagram. Two services communicating with each other are tightly coupled: changing one requires a change of the other. Event-driven architecture evolves to decouple those interdependent services. Knative eventing tries to standardize the event data with Cloud Event and also proposes a framework to drive this architecture. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The documentation uses different words to express the same concepts. So let&amp;#8217;s take a note first: &lt;strong&gt;source&lt;/strong&gt; = &lt;strong&gt;producer&lt;/strong&gt; which emits events, and &lt;strong&gt;sink&lt;/strong&gt; = &lt;strong&gt;subscriber&lt;/strong&gt; = &lt;strong&gt;consumer&lt;/strong&gt; which is the destination of events. Knative Eventing supports two models of in even-driven architecture.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Brokers and Triggers provides an &amp;#8220;event mesh&amp;#8221;. Event producers delivers events to a Broker. Triggers then distribute them uniformly by consumers. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Broker is a &amp;#8220;hub&amp;#8221; for events. It is a central location to receive and send events for delivery. We can choose from the following broker types:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;MT (multi-tenant) channel-based broker (which a channel implementation, such as Kaka channel or an in-memory channel)&lt;/li&gt;&#10;&lt;li&gt;GCP broker&lt;/li&gt;&#10;&lt;li&gt;Apache Kafka broker&lt;/li&gt;&#10;&lt;li&gt;RabbitMQ broker&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Triggers represents a desire to subscribe to events from a specific broker. It can act as a filter&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://knative.dev/docs/eventing/channels/images/channel-workflow.png" alt="Sources send events to a Channel. The Channel fans out events to Subscriptions. The Subscriptions send events to Sinks."/&gt;&lt;figcaption class="wp-element-caption"&gt;channel-subscriber model&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Channels and Subscriptions provide a &amp;#8220;event pipe&amp;#8221; model which transforms and routes events between Channels using Subscriptions. This model makes more sense when events out of one system needs to be transformed and then routed to another process.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A channel can be either a generic channel object, or a custom channel implementation (such as Kafka channel or in-memory channel)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The subscription consists of a Subscription object, which specifies the Channel and the Sink (aka the Subscriber) to deliver events to. You can also specify some Sink-specific options, such as how to handle failures.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The key difference is Broker-Trigger model allows you to filter events, where as Channel-Subscription model allows you to transform events.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that a Knative Service can act as both a Source and a Sink for events, and for good reason. You may want to consume events from the Broker and send modified events back to the Broker, as you would in any pipeline use-case. Between these components, Knative Eventing uses&amp;nbsp;&lt;a href="https://github.com/cloudevents/spec/blob/v1.0.1/primer.md"&gt;CloudEvents&lt;/a&gt;&amp;nbsp;to send information back and forth. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-knative-eventing-lab"&gt;Knative Eventing Lab&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can create a lab cluster using Kind as per the &lt;a href="https://github.com/digihunch/real-quicK-cluster#kind"&gt;instruction &lt;/a&gt;in real-quicK-cluster. In &lt;a href="https://kind.sigs.k8s.io/docs/user/quick-start/#settings-for-docker-desktop"&gt;docker-desktop settings&lt;/a&gt;, I configured 6 CPU and 8GB memory as the lab involves a number of nodes. The creation takes a couple minutes. This Knative eventing lab has four paths one can take, based on different model and different implementations:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Lab A demonstrates the configuration of broker-trigger model using Kafka based broker.&lt;/li&gt;&#10;&lt;li&gt;Lab B demonstrates the configuration of channel-subscription model, using in-memory channel.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The two labs share some common steps. We first install Knative Eventing using the provided manifests:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/eventing-crds.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/eventing-core.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get pods -n knative-eventing&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The Pods should come to READY fairly quickly. In the mean time, we can configure the consumer as well using the manifests below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Namespace&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;apps&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Deployment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;replicas&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchLabels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;template&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;gcr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;releases&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;event_display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ports&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;protocol&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;targetPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;apps&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Deployment&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;replicas&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;matchLabels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;&amp;amp;&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;template&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#f92672"&gt;*&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;labels&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;containers&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;image&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;gcr&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;io&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;releases&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;cmd&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;event_display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;selector&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;app&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ports&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;-&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;protocol&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;port&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;targetPort&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The consumers (aka sinks) will be able to process events at the validation step at the end.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-lab-a-install-kafka-and-configure-kafka-based-broker"&gt;Lab A &amp;#8211; Install Kafka and configure Kafka-based Broker&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This lab is for broker-trigger model. We first configure a &lt;a href="https://knative.dev/docs/eventing/broker/kafka-broker/"&gt;Kafka Broker&lt;/a&gt;, which requires a Kafka cluster. The document uses &lt;a href="https://strimzi.io/quickstarts/"&gt;Strimzi operator&lt;/a&gt; to quickly install Kafka cluster. Instead, here we use Helm to install the Kafka cluster:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install my-cluster-kafka bitnami/kafka -n kafka --create-namespace --set volumePermissions.enabled&lt;span style="color:#f92672"&gt;=&lt;/span&gt;true --set replicaCount&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;At the end of installation, the Helm notes indicate that the bootstrap server is &lt;em&gt;my-cluster-kafka.kafka.svc.cluster.local:9092&lt;/em&gt;. All Kafka pods should come ready in about 5 minutes. After cluster install, we need to install Kafka controller and the Broker-layer data plane which allow you to map a Kafka instance to Broker CRD later:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/knative-sandbox/eventing-kafka-broker/releases/download/knative-v1.3.0/eventing-kafka-controller.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/knative-sandbox/eventing-kafka-broker/releases/download/knative-v1.3.0/eventing-kafka-broker.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get pods -n knative-eventing&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n knative-eventing edit cm kafka-broker-config&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;As the command above suggests, once data plane is installed, we edit the default broker config by updating the bootstrap.servers with the bootstrap server address noted above. We also need to make sure the replication factor is no greater than number of partitions, which should not be a problem in our lab as the replica count was set to 3. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we can create a Broker in the same namespace with consumer:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;apiVersion: eventing.knative.dev/v1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kind: Broker&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;metadata:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; namespace: event-example&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; annotations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; eventing.knative.dev/broker.class: Kafka&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;spec:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; config:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; apiVersion: v1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; kind: ConfigMap&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; name: kafka-broker-config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; namespace: knative-eventing&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once the manifest above is applied, we should see the status of broker (named default) to be READY. We can also validate broker creation with kn CLI command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; $ kubectl -n event-example get broker default&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME URL AGE READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;default http://kafka-broker-ingress.knative-eventing.svc.cluster.local/event-example/default 19m True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn broker list -n event-example&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME URL AGE CONDITIONS READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;default http://kafka-broker-ingress.knative-eventing.svc.cluster.local/event-example/default 19m &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; OK / &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; True&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we create Triggers that connects both to the broker with filters, and to the consumers by referencing service name. The manifest to create triggers is provided below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Trigger&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;broker&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;filter&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attributes&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;type&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;greeting&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ref&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;eventing&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Trigger&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;broker&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;filter&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;attributes&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;source&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;sendoff&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ref&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can confirm the setups above by monitoring the resources (like we did for broker), or use knative CLI:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn trigger list -n event-example&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME BROKER SINK AGE CONDITIONS READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;goodbye-display default service:goodbye-display 3m24s &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; OK / &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-display default service:hello-display 3m24s &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; OK / &lt;span style="color:#ae81ff"&gt;6&lt;/span&gt; True&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can go to the validation step to send event and validate result.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-lab-b-install-in-memory-channel-and-configure-channel"&gt;Lab B &amp;#8211; Install in-memory channel and configure Channel&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This lab is for channel-subscription model. we first install an in-memory channel:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl apply -f https://github.com/knative/eventing/releases/download/knative-v1.3.0/in-memory-channel.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can map this channel to a Channel resource in Eventing. We can do so by applying the manifest below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The creation of Channel resources can be verified as below&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn -n event-example channel list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME TYPE URL AGE READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-channel-1 InMemoryChannel http://demo-channel-1-kn-channel.event-example.svc.cluster.local 6m34s True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-channel-2 InMemoryChannel http://demo-channel-2-kn-channel.event-example.svc.cluster.local 6m34s True&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we need to create Subscription which connects Channels to Consumers. Subscriptions can be created with the manifest below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Subscription&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;subscription&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ref&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;hello&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Subscription&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;metadata&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;subscription&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;namespace&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;event&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;example&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;spec&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;messaging&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;knative&lt;/span&gt;.&lt;span style="color:#a6e22e"&gt;dev&lt;/span&gt;&lt;span style="color:#f92672"&gt;/&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Channel&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;demo&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;channel&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;subscriber&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;ref&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;apiVersion&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;kind&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;name&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;goodbye&lt;/span&gt;&lt;span style="color:#f92672"&gt;-&lt;/span&gt;&lt;span style="color:#a6e22e"&gt;display&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can confirm subscription:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn -n event-example subscription list&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME CHANNEL SUBSCRIBER REPLY DEAD LETTER SINK READY REASON&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-subscription-1 Channel:demo-channel-1 service:hello-display True&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;demo-subscription-2 Channel:demo-channel-2 service:goodbye-display True&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can go to validation step.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-validation"&gt;Validation&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For validation, we need to first configure an event source. The source in our lab is a throw-away Pod with curl utility. I use my favourite &lt;a href="https://github.com/nicolaka/netshoot"&gt;nicolaka netshoot&lt;/a&gt; to fire off the events using. To launch the Pod and get to command shell:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl run tmp-shell -n event-example --rm -i --tty --image nicolaka/netshoot -- /bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;If you don&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;t see a command prompt, try pressing enter.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;bash-5.1# &#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can POST message to the corresponding endpoint URL to fire events. The endpoint is different for each lab:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Lab&lt;/th&gt;&lt;th&gt;URL&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;A (Kafka-based broker)&lt;/td&gt;&lt;td&gt;http://kafka-broker-ingress.knative-eventing.svc.cluster.local/event-example/default&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;B (channel)&lt;/td&gt;&lt;td&gt;http://demo-channel-1-kn-channel.event-example.svc.cluster.local&lt;br&gt;http://demo-channel-2-kn-channel.event-example.svc.cluster.local&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With the correct URL, we can build a payload for POST method. Below is three curl commands with different payload POST to the same endpoint (taking lab A as an example):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;display:grid;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# curl -v &amp;#34;http://broker-ingress.knative-eventing.svc.cluster.local/event-example/default&amp;#34; \&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-X POST &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Id: say-hello&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Specversion: 1.0&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Type: greeting&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Source: not-sendoff&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex; background-color:#3c3d38"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex; background-color:#3c3d38"&gt;&lt;span&gt;-d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;msg&amp;#34;:&amp;#34;Hello Knative!&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# curl -v &amp;#34;http://broker-ingress.knative-eventing.svc.cluster.local/event-example/default&amp;#34; \&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-X POST &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Id: say-goodbye&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Specversion: 1.0&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Type: not-greeting&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Source: sendoff&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex; background-color:#3c3d38"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex; background-color:#3c3d38"&gt;&lt;span&gt;-d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;msg&amp;#34;:&amp;#34;Goodbye Knative!&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# curl -v &amp;#34;http://broker-ingress.knative-eventing.svc.cluster.local/event-example/default&amp;#34; \&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-X POST &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Id: say-hello-goodbye&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Specversion: 1.0&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Type: greeting&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Ce-Source: sendoff&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex; background-color:#3c3d38"&gt;&lt;span&gt;-H &lt;span style="color:#e6db74"&gt;&amp;#34;Content-Type: application/json&amp;#34;&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;\&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex; background-color:#3c3d38"&gt;&lt;span&gt;-d &lt;span style="color:#e6db74"&gt;&amp;#39;{&amp;#34;msg&amp;#34;:&amp;#34;Hello Knative! Goodbye Knative!&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;All the requests should receive 202 return code. As highlighted above, we specified type and source for each event fired to the broker. On the consumer side, we can simply check the pod log to verify the events received. Below is an example of events received as a result of the events sent above (take broker-trigger model as example):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n event-example logs -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hello-display --tail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2022/03/09 17:05:59 Failed to read tracing config, using the no-op default: empty json tracing config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: not-sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:19.196589801Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hello Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-hello-goodbye&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:42.977803608Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hello Knative! Goodbye Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n event-example logs -l app&lt;span style="color:#f92672"&gt;=&lt;/span&gt;goodbye-display --tail&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2022/03/09 17:05:58 Failed to read tracing config, using the no-op default: empty json tracing config&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: not-greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-goodbye&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:27.430702588Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Goodbye Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;☁️ cloudevents.Event&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Context Attributes,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; specversion: 1.0&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; type: greeting&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; source: sendoff&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; id: say-hello-goodbye&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; datacontenttype: application/json&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Extensions,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; knativearrivaltime: 2022-03-09T17:14:42.977803608Z&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Data,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;msg&amp;#34;&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Hello Knative! Goodbye Knative!&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This is the gist of Knative evening. Note that the format of HTTP payload when we fire off events needs to conform to &lt;a href="https://cloud.google.com/eventarc/docs/cloudevents"&gt;CloudEvents format&lt;/a&gt;. The container image (&lt;a href="https://github.com/knative/eventing/tree/main/cmd/event_display"&gt;event_display&lt;/a&gt;) used in the container is also created for the purpose of processing events received and display them in stdout.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Persistent storage allows for stateful applications on Kubernetes. It also enables architectural patterns that requires persistent storage. Knative eventing is a framework that enables event driven architecture. It supports many &lt;a href="https://knative.dev/docs/eventing/channels/channels-crds/"&gt;messaging channels&lt;/a&gt; (including &lt;a href="https://nats.io/"&gt;NATs&lt;/a&gt;, a cloud-native messaging system) and &lt;a href="https://knative.dev/docs/eventing/broker/"&gt;broker&lt;/a&gt; types (MT channel based, GCP, RabbitMQ). &lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/04/knative-introduction-serving/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Serving Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/05/fsx-ontap-enterprise-storage-on-aws/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;FSx ONTAP – Enterprise storage on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Knative Serving Introduction</title><link>https://static.digihunch.com/2022/04/knative-introduction-serving/</link><pubDate>Sun, 17 Apr 2022 11:33:00 -0400</pubDate><guid>https://static.digihunch.com/2022/04/knative-introduction-serving/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-knative-serving.webp" alt="Featured image of post Knative Serving Introduction" /&gt;&lt;h2 class="wp-block-heading" id="h-background"&gt;Background&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As per &lt;a href="https://www.ibm.com/cloud/learn/knative"&gt;IBM&lt;/a&gt;&amp;#8216;s definition, Knative enables serverless workloads to run on Kubernetes clusters, and makes building and orchestrating containers with Kubernetes faster and easier. It has drawn a lot of attention recently. It released version 1.0 in November 2021, and was accepted as a &lt;a href="https://www.cncf.io/blog/2022/03/02/knative-accepted-as-a-cncf-incubating-project/"&gt;CNCF incubating&lt;/a&gt; project in March 2022. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Glories aside, the value it delivers is the ability to go serverless on a Kubernetes platform. Originally, &lt;a href="https://cloud.google.com/knative"&gt;Knative&lt;/a&gt; was built with three components: build, serving and eventing. The &lt;a href="https://github.com/knative/build"&gt;build&lt;/a&gt; component was deprecated in favour of the &lt;a href="https://tekton.dev/"&gt;Tekton&lt;/a&gt; project. Tekton is cloud native CI/CD pipeline. It connects to source code repo and build artifacts. It can also deploy applications with pipeline as code. Tekton&amp;#8217;s documentation includes a quality tutorial &lt;a href="https://tekton.dev/docs/getting-started/"&gt;here&lt;/a&gt; linked to interactive terminal. On the other hand, we&amp;#8217;ll focus on Knative Serving in this post, and Knative Eventing in the &lt;a href="https://static.digihunch.com/2022/04/knative-introduction/"&gt;next&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://knative.dev/docs/"&gt;Knative&lt;/a&gt; can be seen as a serverless framework with a number of open-source technologies as building blocks, such as Istio for ingress gateway, Kafka or Google Pub/Sub as event-streaming engine, and Prometheus for observability to name a few. Despite of using the same CLI tool (named &lt;a href="https://knative.dev/docs/install/"&gt;kn&lt;/a&gt;), Knative Serving and Eventing are considered separate capabilities using different groups of CRDs. They are installed separately, using their respective &lt;a href="https://knative.dev/docs/install/yaml-install/"&gt;YAML&lt;/a&gt; files, or &lt;a href="https://knative.dev/docs/install/operator/knative-with-operators/"&gt;operator&lt;/a&gt;. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-serving-vs-eventing"&gt;Serving vs Eventing ?&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After reading the article &amp;#8220;&lt;a href="https://ahmet.im/blog/knative-positioning/"&gt;Did we market Knative wrong&lt;/a&gt;&amp;#8221; from Ahmet Balkan, my impression is the two are not related. They could have been two separate projects without sharing a common name. As the author puts, these two shared some core logics. But beyond that, they don&amp;#8217;t have anything in common. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Serving is lightweight and requires Istio. It provides the capability to scale from N to 0 when the system is idle, and from 0 to N when requests come in. It is the missing serving layer for running microservices on Kubernetes and Ahmet position it as the first thing that people installed after creating a cluster.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The target user of Eventing is much smaller. Eventing is for event-driven architecture and is more complex than Serving. Ahmet admits that they over-estimated how many people on the planet want to build a Heroku-like PaaS layer on top of Knative. There are a couple of dozen companies who would work through the complexity and build their own Kubernetes-based internal PaaS or even public-facing FaaS using Knative. They are the niche audience of Knative Eventing. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For most platform builders who just need to run micro-service, Knative Serving is all they need.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-a-demo-on-serving"&gt;A Demo on Serving&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The definition of term &amp;#8220;serverless&amp;#8221; is very loose. Instead, Knative documentation promotes the ability to &lt;a href="https://knative.dev/docs/getting-started/first-autoscale/#watch-your-knative-service-scale-to-zero"&gt;scale to zero&lt;/a&gt; and refer to it as &amp;#8220;some people call this Serverless&amp;#8221;. This suggests that &amp;#8220;the ability to scale to zero&amp;#8221; should be the better term to describe this capability. To deploy it, we need the Service object with apiVersion serving.knative.dev/v1. Below is a guide for a quick hands-on, with some steps modified from the Knative Serving &lt;a href="https://knative.dev/docs/getting-started/first-service/"&gt;tutorial&lt;/a&gt; and installation &lt;a href="https://knative.dev/docs/install/yaml-install/serving/install-serving-with-yaml/"&gt;guide&lt;/a&gt;, with optional steps (extensions, DNS) skipped.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Follow the first part of &lt;a href="https://static.digihunch.com/2021/11/istio-ingress-egress/"&gt;this post&lt;/a&gt; or &lt;a href="https://github.com/digihunch/real-quicK-cluster#minikube"&gt;this guide&lt;/a&gt; in real-quicK-cluster project, to install minikube with metal LB and configure istio (using istioctl as per instruction). Then Knative serving can be installed with two YAML manifests:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-crds.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/serving/releases/download/knative-v1.2.0/serving-core.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can install Knative istio controller to integrate them so we can see the knative gateways:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f https://github.com/knative/net-istio/releases/download/knative-v1.2.0/net-istio.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl -n knative-serving get gateway&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can install the dummy service, either &lt;a href="https://knative.dev/docs/getting-started/first-service/#knative-service-hello-world"&gt;using kn CLI tool or using YAML manifest&lt;/a&gt;. The manifest is given below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;serving.knative.dev/v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hello&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;namespace&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;default&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;template&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;hello-world&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;annotations&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/class&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;kpa.autoscaling.knative.dev&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/metric&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;rps&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/target&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;50&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/min-scale&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;0&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;autoscaling.knative.dev/max-scale&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;10&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;spec&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containerConcurrency&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;containers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;env&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TARGET&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;value&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;World&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;gcr.io/knative-samples/helloworld-go&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;user-container&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;8080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can notice that in the annotations there are some settings to overwrite the default configuration. They are explained in the &lt;a href="https://knative.dev/docs/serving/autoscaling/#additional-resources"&gt;autoscaling&lt;/a&gt; section of the Knative serving documentation. Here we use request per second (rps) as metric, and have 50 as target. The scale range is between 3 and 10 replicas. Once the resource is created, we can validate it with one of the two commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get services.serving.knative.dev&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kn service describe hello&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The creation of knative service also created a number of Kubernetes resources, such as deployment and services:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get svc&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME TYPE CLUSTER-IP EXTERNAL-IP PORT&lt;span style="color:#f92672"&gt;(&lt;/span&gt;S&lt;span style="color:#f92672"&gt;)&lt;/span&gt; AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello ExternalName &amp;lt;none&amp;gt; knative-local-gateway.istio-system.svc.cluster.local 80/TCP 52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world ClusterIP 10.110.127.231 &amp;lt;none&amp;gt; 80/TCP 108s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-private ClusterIP 10.111.27.73 &amp;lt;none&amp;gt; 80/TCP,9090/TCP,9091/TCP,8022/TCP,8012/TCP 108s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubernetes ClusterIP 10.96.0.1 &amp;lt;none&amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get deploy&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY UP-TO-DATE AVAILABLE AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 71s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Before testing, let&amp;#8217;s mock the DNS entry to resolve to the Istio ingress service IP like below in /etc/hosts:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;192.168.64.16 hello.default.example.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Now we can confirm that the service returns the expected result (after a brief pause): &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl hello.default.example.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hello World!&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Wait for a short period without curl or connection from browser, the service should scale down to zero:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get deploy -l serving.knative.dev/service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hello -w&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY UP-TO-DATE AVAILABLE AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/0 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 78s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From a separate terminal, emulate HTTP request with &lt;a href="https://github.com/rakyll/hey"&gt;hey&lt;/a&gt; command, so the service receives more than 50 rps per pod:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ hey -n &lt;span style="color:#ae81ff"&gt;200&lt;/span&gt; -c &lt;span style="color:#ae81ff"&gt;20&lt;/span&gt; -z 20s -m GET http://hello.default.example.com&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We can watch the deployment get scaled up to 10. Once the hey command completes its task, the deployment will scale back down:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl get deploy -l serving.knative.dev/service&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hello -w&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;NAME READY UP-TO-DATE AVAILABLE AGE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/0 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 78s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 3m54s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m2s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m4s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m4s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m4s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 4m5s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 4m9s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 3/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 4/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;4&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 7/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;7&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 9/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;9&lt;/span&gt; 4m10s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 10/10 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 4m11s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 10/5 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 5m18s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 10/5 &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt; 5m18s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 5/5 &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 5m18s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 5/2 &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 5m20s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 5/2 &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;5&lt;/span&gt; 5m20s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/2 &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 5m20s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/1 &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 5m22s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 2/1 &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt; 5m22s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/1 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 5m22s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/0 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 5m52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 1/0 &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; 5m52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;hello-world-deployment 0/0 &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt; 5m52s&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Autoscaling in Knative serving is backed by KPA (&lt;a href="https://knative.dev/docs/serving/autoscaling/"&gt;Knative Pod Autoscaler&lt;/a&gt;, used in the example above) or HPA. Also note that the trigger to scale up and down is client connection (RPS). This is different than KEDA. KEDA also supports the capability to scale to zero. However, with KEDA, it is an event that scales a deployment up from zero. In Knative serving, it is the connection to the Service itself that wakes up the service. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-cost-of-scale-to-zero"&gt;Cost of scale-to-zero&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As discussed, both KEDA and Knative serving supports the ability to scale to zero and wake up from zero. The trigger to wake up from zero is different. With KEDA, workload wakes up by an event. With Knative Serving, workload has to wake up upon receiving a connection to the service. In the design to solve this problem, &lt;a href="https://knative.dev/docs/serving/"&gt;Knative&lt;/a&gt; has four sub-components :&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Activator &amp;#8211; When a service is scaled to zero, its request are routed to the&amp;nbsp;&lt;em&gt;activator&lt;/em&gt;&amp;nbsp;which waits for a Pod to become ready (wake up) and proxies the traffic while editing the underlying&amp;nbsp;&lt;code&gt;Endpoints&lt;/code&gt;&amp;nbsp;to route the traffic directly to the Pod(s)&lt;/li&gt;&#10;&lt;li&gt;Autoscaler &amp;#8211; KPA&lt;/li&gt;&#10;&lt;li&gt;Controller &amp;#8211; The main component responsible for watching API objects for Knative CRDs (KService, Configuration, Route, Revision) and manage their lifecycle, create the underlying Kubernetes resources and garbage-collect old objects.&lt;/li&gt;&#10;&lt;li&gt;Webhook &amp;#8211; a Kubernetes Admission Webhook acting both as validating admission controller and mutating admission controller.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The key player is activator, which receives requests when a service is scaled to zero. The wake-up process is summarized as follows:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;After receiving request, activator will buffer (hold onto) the request&lt;/li&gt;&#10;&lt;li&gt;Then, it will look at request’s hostname to find which KService it is for.&lt;/li&gt;&#10;&lt;li&gt;Then, it will scale up the Kubernetes Deployment and wait for a Pod to become ready.&lt;/li&gt;&#10;&lt;li&gt;In the meanwhile, it updates Kubernetes Service to point to Pod IP addresses (so that activator gets out of the network path if the KService is awake).&lt;/li&gt;&#10;&lt;li&gt;Finally, activator proxies the request to the started pod&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;So the &amp;#8220;plumbing&amp;#8221; of the service connection managed by the Activator pod is the cost of scale to zero. This is also called &lt;a href="https://knative.dev/docs/serving/load-balancing/"&gt;Load Balancing&lt;/a&gt;, and the behaviour can be tweaked using two parameters: activator capacity and target burst capacity:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;a href="https://knative.dev/docs/serving/load-balancing/target-burst-capacity/"&gt;Target burst capacity&lt;/a&gt;: once the target deploy has one more more Pod, service requests may still route via activator or bypassing the activator. Target burst capacity determines at what point service request should start to bypass activator. &lt;/li&gt;&#10;&lt;li&gt;&lt;a href="https://knative.dev/docs/serving/load-balancing/activator-capacity/"&gt;Activator capacity&lt;/a&gt;: determines how many requests can activator hold on to. Considering the service-to-service connection also routes via Service CRD, there can be a lot of connections via Activator so its capacity needs to be adjusted.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The additional overhead of managing and optimizing the &amp;#8220;Load Balancer&amp;#8221; is also part of operational cost for the ability to scale to zero.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In comparison, KEDA now has an &lt;a href="https://github.com/kedacore/http-add-on"&gt;HTTP add-on&lt;/a&gt; still at beta but allows connection-based wake-up. The &lt;a href="https://github.com/kedacore/http-add-on/blob/main/docs/design.md"&gt;design&lt;/a&gt; is a little different. As discussed in this &lt;a href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;previous &lt;/a&gt;post.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-conclusion"&gt;Conclusion&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Knative consists of two disparate components: Serving and Eventing. Serving uses KPA to provide scaling based on service request, and the ability to scale to zero. It is often compared with KEDA, a single-purpose lightweight tool for autoscaling. &lt;a href="https://github.com/kedacore/http-add-on/blob/main/docs/faq.md#how-is-this-project-similar-or-different-from-knative"&gt;Here&lt;/a&gt; is a blurb on their difference by KEDA. The takeaways is that KEDA is more focused on scalability, whereas Knative serving covers more aspects. For example, the Service object of Knative also supports &lt;a href="https://knative.dev/docs/getting-started/first-traffic-split/"&gt;Traffic Splitting&lt;/a&gt;. Knative serving can integrate with istio.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/04/kubernetes-operator/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Kubernetes Operator&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/04/knative-introduction/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Eventing Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>AKS Lessons Learned 1 of 2</title><link>https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/</link><pubDate>Sat, 04 Dec 2021 02:11:06 -0400</pubDate><guid>https://static.digihunch.com/2021/12/aks-troubleshooting-lessons-learned/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-aks-lesson-1.webp" alt="Featured image of post AKS Lessons Learned 1 of 2" /&gt;&lt;p class="wp-block-paragraph"&gt;In general, troubleshooting Kubernetes is tricky. That is because one has to get in and out of pods. I took two days to troubleshoot some networking issues with private AKS cluster. For the amount of of tricks I had to employ, I need to take some notes.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="the-issue"&gt;The issue&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;After writing the Terraform code, I used the following dummy service to test the private AKS cluster:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-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;apps/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;Deployment&lt;/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;aks-helloworld-one&lt;/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;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selector&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matchLabels&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;template&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;labels&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&lt;/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;containers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;neilpeterson/aks-helloworld: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;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;env&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TITLE&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;value&lt;/span&gt;: &lt;span style="color:#e6db74"&gt;&amp;#34;Welcome to Azure Kubernetes Service (AKS)&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;---&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&lt;/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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;LoadBalancer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selector&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;app&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;aks-helloworld-one&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 expected behaviour, is that the service object will tell cloud API to provision a load balancer, with public IP listing at port 80. I should be able to curl to the IP address and connect to the site in the Pod. However, I was not able to. On the bastion host, I was able to curl to nodePort of the node address. But anything on public IP does not work, no matter where I ran curl from. This feels like a basic issue, but is is quite annoying because the native troubleshooting tool for Azure Load Balancer is horrible. In and out of a bunch of components named &amp;#8220;insights&amp;#8221;, &amp;#8220;diagnostic log&amp;#8221;, or &amp;#8220;Metrics&amp;#8221;, I can&amp;#8217;t simply find a way to trace whether it received an HTTP request. Most of the information I was able to see was irrelevant or useless.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="the-approach"&gt;The approach&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The hard way to troubleshooting infrastructure as code, is configuration comparison approach: revert to a baseline configuration, and see if the expected function works. Then from the baseline, change one configuration at a time and see where it starts to break. This approach is very time consuming, and AKS cluster as a relatively large resource, with numerous attributes, takes this effort to extreme. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The baseline configuration I started with 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;az aks create -g AutomationTest -n orthCluster --generate-ssh-keys --node-count &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --tags Owner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;MyOwner&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With this baseline, I simply use kubectl to apply the YAML file above. Then I can tell that the port is working. With a good start point, I started to apply one change at a time and repeat the test. I ran into a snug when I&amp;#8217;m using the following configuration:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks create -g AutomationTest -n orthCluster --generate-ssh-keys --node-count &lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --tags Owner&lt;span style="color:#f92672"&gt;=&lt;/span&gt;MyOwner --enable-private-cluster --network-plugin azure --network-policy calico&#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 cluster created from the command above, the variable introduced is &amp;#8211;enable-private-cluster. This puts the cluster on a private network. I cannot connect to the cluster via a public endpoint anymore, and thus have to figure out some tricks to run the kubectl commands. I had to play with the Command Run feature of AKS cluster because I don&amp;#8217;t have a bastion host when using AZ CLI command. The Command Run feature would not allow me to use any file from bastion host. So i had to create my test objects, the Deploy and the Service objects all by imperative commands. The equivalent commands I worked out 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;kubectl create deployment aks-helloworld-one --image&lt;span style="color:#f92672"&gt;=&lt;/span&gt;neilpeterson/aks-helloworld:v1 --replicas&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt; --port&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl expose deploy aks-helloworld-one --port &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt; --target-port &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt; --type&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#39;LoadBalancer&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then I realized a limitation with Command Run feature: it only supports basic command switches and doesn&amp;#8217;t like switches such as &amp;#8211;replicas. So I used the following 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;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl get no&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl create deployment aks-helloworld-one --image=neilpeterson/aks-helloworld:v1&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl get deploy&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl expose deploy aks-helloworld-one --port 80 --target-port 80 --type=LoadBalancer&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;az aks command invoke -g AutomationTest -n orthCluster -c &lt;span style="color:#e6db74"&gt;&amp;#34;kubectl get svc&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This trick allows me to continue with the testing eliminate Azure CNI and Calico policy as the cause. Testing after each cluster creation is painful because the cluster creation can take 10 minutes.I had to temporarily minimize the size of the cluster to speed up provisioning. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I finally came to the point that I can reproduce the issue using TF template. I realized that when I set the vnet_subnet_id attribute of azurerm_kubernetes_cluster&amp;#8217;s default_node_pool, the problem came back. That&amp;#8217;s the smoking gun that the node subnet is the issue. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="the-network-security-group-on-node-subnet"&gt;The Network Security Group on Node Subnet&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The node subnet has an associated network security group. I discovered that once I add an allow rule for port 80 to the security group, the curl test will work. I also noticed the security group rule change will take 60 sec to come to effect and load balancer will also take 60 sec to warm up.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This confuses me because port 80 is only listened by the load balancer and not by any of the nodes. It&amp;#8217;s most likely when public load balancer is used the load balancer is placed on the node subnet. According to &lt;a href="https://docs.microsoft.com/en-us/azure/aks/load-balancer-standard"&gt;this&lt;/a&gt; note: &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Inbound, external traffic flows from the load balancer to the virtual network for your AKS cluster. The virtual network has a Network Security Group (NSG) which allows all inbound traffic from the load balancer. This NSG uses a service tag of type LoadBalancer to allow traffic from the load balancer.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The packet coming from external source can travel up to the VNet, but it was blocked at the NSG of node subnet.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="lessons-learned"&gt;Lessons Learned&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We always need to have some dummy service ready to test what we need. We can use nginx dummy service like:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;apiVersion&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;apps/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;Deployment&lt;/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-nginx&lt;/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;selector&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;matchLabels&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;replicas&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;template&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;labels&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&lt;/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;containers&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;image&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;nginx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;containerPort&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&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;v1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;kind&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Service&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;metadata&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;labels&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&lt;/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;type&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;LoadBalancer&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;ports&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;port&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;80&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;protocol&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;TCP&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;selector&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;run&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;my-nginx&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;As discussed above, it&amp;#8217;s also important to have a Bastion host able to access the control plane when the AKS cluster is private. Azure touts about CloudShell (and its ability to run in specified V-Net) but it&amp;#8217;s pretty useless in troubleshooting. CloudShell sessions run inside of Kubernetes cluster and lacks common network troubleshooting tool such as nc. Azure has a managed service for Bastion but it requires a subnet with the exact name of AzureBastionSubnet.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will explore more issues in the &lt;a href="https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/"&gt;next&lt;/a&gt; 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/11/from-microservice-to-service-mesh/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;From Microservice to Service Mesh&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/12/aks-lessons-learned-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;AKS Lessons Learned 2 of 2&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>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>Docker storage</title><link>https://static.digihunch.com/2020/11/docker-storage/</link><pubDate>Tue, 03 Nov 2020 20:22:00 -0400</pubDate><guid>https://static.digihunch.com/2020/11/docker-storage/</guid><description>&lt;p class="wp-block-paragraph"&gt;Microservices are all about stateless and ephemeral workloads, and containers are great microservices. This may suggest that that Docker is all about ephemeral storage. In fact, Docker supports both non-persistent and persistent storage, such as database, kafka, etc. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Non-persistent storage is automatically created, alongside the container and is tied to the lifecycle of the container. On Linux system, it is /var/lib/docker/ as part of container. This is referred to as local storage.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker has a concept of volume, which is essentially a file or a directory. Volumes are for persistent data. they are de-coupled from containers and are not tied to the lifecycle of any container. Volume allows process in docker container to bypass the default uionFS, and stores file or directory on host machine. It also allows different containers to share data. You may mount a volume to a container. even if container is deleted, volume persists.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, Docker creates new volumes with the built-in local driver. Local volumes are only available to containers on the node they&amp;#8217;re created on. There are also third-party drivers as plugins that provides advanced options to integrate external storage system with Docker. (NAS, SAN, etc)&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are more than 25 volume plugins that you can specify with -d switch, to cover all three categories of storage&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Block storage tends to be high performance and good for small-block random access workloads.&lt;/li&gt;&#10;&lt;li&gt;File storage is high performance, shared amongs multiple containers with NFS or SMB protocols.&lt;/li&gt;&#10;&lt;li&gt;Object storage is good for long term storage of large data blobs that do not change frequently. It is often content addressable and relatively low performance.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Note that if you share volume with multiple containers, the application needs to worry about data collision.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You may use docker volume create command to create volume. Note that there is no quota management within docker so the partition needs to be managed at operating system level.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Implementation of Volume&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Remember that Docker image is built on multi-layer file system. When we run a container, Docker places a read-write layer on top of the image, such that the active files in running container are all placed in this read-write layer. When container is deleted, so are the files. The file system in Docker is a pseudo file system implemented in unionFS. Volumes bypasses the uionFS and directly accesses the host file system. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we create a Docker volume, Docker places the volume data to /var/lib/docker/volumes and under each directory named after volume, creates a directory _data, which is attached to the corresponding container.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can even mount an NFS volume to container. Reference &lt;a href="https://forums.docker.com/t/nfs-mount-inside-docker-container-bypassing-the-host/77890" class="rank-math-link"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We mentioned UnionFS a couple times so far. UnionFS is a light-weight, layered file system. It can mount the contents of multiple directories to the same directory, to form a single file system. User can use unionFS like a directory. It is the foundation of Docker image and container and enables saving of spaces.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="444" src="https://static.digihunch.com/wp-content/uploads/2024/07/unionfs-1024x444.png" alt="" class="wp-image-11424" style="width:526px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2024/07/unionfs-1024x444.png 1024w, https://static.digihunch.com/wp-content/uploads/2024/07/unionfs-300x130.png 300w, https://static.digihunch.com/wp-content/uploads/2024/07/unionfs-768x333.png 768w, https://static.digihunch.com/wp-content/uploads/2024/07/unionfs.png 1380w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are three common types of union FS: AUFS, DeviceMapper, and OverlayFS.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;AUFS file system&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AUFS is the earliest driver that Docker uses for file system, most common in Ubuntu and Debian. To check if the system support AUFS, check out the documentation &lt;a href="https://docs.docker.com/storage/storagedriver/aufs-driver/" class="rank-math-link"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AUFS is recommended in Ubuntu or Debian. For CentOS and Redhat, it needs to be installed and make sure the command above returns aufs. To configure AUFS, create file /etc/docker/daemon.json and add:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;storage-driver&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;aufs&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then restart docker service. Run &amp;#8220;docker info&amp;#8221; and examine the Storage Driver section, as documented &lt;a href="https://docs.docker.com/storage/storagedriver/aufs-driver/" class="rank-math-link"&gt;here&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AUFS layers multiple directories on a single Linux host and presents them as a single directory. These directories are called branches in AUFS terminology, and layers in Docker terminology. The unification process is referred to as a union mount.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-full is-resized"&gt;&lt;img loading="lazy" decoding="async" width="884" height="724" src="https://static.digihunch.com/wp-content/uploads/2024/07/ubuntu-layers.png" alt="" class="wp-image-11425" style="width:538px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2024/07/ubuntu-layers.png 884w, https://static.digihunch.com/wp-content/uploads/2024/07/ubuntu-layers-300x246.png 300w, https://static.digihunch.com/wp-content/uploads/2024/07/ubuntu-layers-768x629.png 768w" sizes="auto, (max-width: 884px) 100vw, 884px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Layers of a Ubuntu container&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt; &lt;a href="https://docs.docker.com/storage/storagedriver/aufs-driver/#example-image-and-container-on-disk-constructs" class="rank-math-link"&gt;This section&lt;/a&gt; describes how the layers work and &lt;a href="https://docs.docker.com/storage/storagedriver/aufs-driver/#how-container-reads-and-writes-work-with-aufs" class="rank-math-link"&gt;this section&lt;/a&gt; describes how it reads and writes files (Copy-on-Write (CoW) strategy to maximize storage efficiency and minimize overhead). CoW characterized AUFS.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;AUFS has not been adopted in the Linux kernel mainline for lack of maintainability. So for CentOS, the recommended file system driver is devicemapper.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Devicemapper file system&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Devicemapper is a technical framework to map physical block device to virtual block device, introduced since kernel 2.6.9. So it&amp;#8217;s essentially different from AUFS. The Logical Volume Manager (LVM) in Linux is also implemented based on devicemapper.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The three critical components in devicemapper are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;mapped device: a virtual device that devicemapper provides to client&lt;/li&gt;&#10;&lt;li&gt;target device: the underlying physical device or a section of it.&lt;/li&gt;&#10;&lt;li&gt;map table: keeps track of the offset, range, etc between mapped and target devices.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Devicemapper uses target driver to block, filter, and forward I/O requests (e.g. Raid, encryption, think provisioning, etc). In thin provisioning, storage driver only assigns spaces that are needed. Docker uses snapshot technology in thin provisioning. This &lt;a class="rank-math-link" href="https://docs.docker.com/storage/storagedriver/device-mapper-driver/#how-the-devicemapper-storage-driver-works"&gt;part of the documentation&lt;/a&gt; provides further details as to how device mapper works.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="854" height="1024" src="https://static.digihunch.com/wp-content/uploads/2025/04/ubuntu-busybox-layer-854x1024.webp" alt="" class="wp-image-13114" style="width:539px;height:auto" srcset="https://static.digihunch.com/wp-content/uploads/2025/04/ubuntu-busybox-layer-854x1024.webp 854w, https://static.digihunch.com/wp-content/uploads/2025/04/ubuntu-busybox-layer-250x300.webp 250w, https://static.digihunch.com/wp-content/uploads/2025/04/ubuntu-busybox-layer-768x921.webp 768w, https://static.digihunch.com/wp-content/uploads/2025/04/ubuntu-busybox-layer.webp 1046w" sizes="auto, (max-width: 854px) 100vw, 854px" /&gt;&lt;figcaption class="wp-element-caption"&gt;Ubuntu and busybox image layers&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Devicemapper has to modes:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;loop-lvm: in dev and test environment&lt;/li&gt;&#10;&lt;li&gt;direct-lvm: recommended in production&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here is the performance &lt;a href="https://docs.docker.com/storage/storagedriver/device-mapper-driver/#device-mapper-and-docker-performance" class="rank-math-link"&gt;best practice&lt;/a&gt;. To configure devicemapper, create /etc/docker/daemon.json file and add:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;storage-driver&amp;#34;:&amp;#34;devicemapper&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;storage-opts&amp;#34;:[&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dm.directlvm_device=/dev/xdf&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dm.thinp_percent=95&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dm.thinp_metapercent=1&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dm.thinp_autoextend_threshold=80&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dm.thinp_autoextend_percent=20&amp;#34;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &amp;#34;dm.directlvm_device_force=false&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then restart docker service. Run &amp;#8220;docker info&amp;#8221; and examine the Storage Driver section to ensure direct-lvm mode is on. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Since devicemapper uses block device to store files, it is faster than directly operate on file system. It is adopted as default driver as unionFS for a long time, ensuring stable performance under Red Hat and CentOS.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;OverlayFS file system&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Earlier versions of OverlayFS (known as overlay driver) is not stable. Later version is known as overlay2, which is very stable and recommended in overlay2. It requires:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Docker version higher than 17.06.02;&lt;/li&gt;&#10;&lt;li&gt;Kernel version higher than 3.10.0-514 for CentOS and RHEL; or higher than 4.0 for other distributions of Linux;&lt;/li&gt;&#10;&lt;li&gt;Using with xfs file system with d_type turned on&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In production environment, it is recommended to moutn /var/lib/docker to separate disk or partition, to prevent the directory getting full from impacting the host OS. The option pquota is recommended for mounting options in /etc/fstab.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To configure storage driver, create file /etc/docker/daemon.json, with the following content:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-java" data-lang="java"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;{&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;storage-driver&amp;#34;&lt;/span&gt;:&lt;span style="color:#e6db74"&gt;&amp;#34;overlay2&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;storage-opts&amp;#34;&lt;/span&gt;:&lt;span style="color:#f92672"&gt;[&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;overlay2.size=20G&amp;#34;&lt;/span&gt;,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;overlay2.override_kernel_check=true&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#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;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then restart docker service. Run &amp;#8220;docker info&amp;#8221; and examine the Storage Driver section to ensure storage driver is overlay2 and d_type is true.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The way overlay2 works is similar to AUFS, involving union mount process, with lowerdir, upperdir and merged. More details are &lt;a href="https://docs.docker.com/storage/storagedriver/overlayfs-driver/#how-the-overlay2-driver-works" class="rank-math-link"&gt;here&lt;/a&gt;, including &lt;a href="https://docs.docker.com/storage/storagedriver/overlayfs-driver/#how-the-overlay-driver-works" class="rank-math-link"&gt;how overlay2 works&lt;/a&gt; with file read and file write (e.g. CopyOnWrite).&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Today, overlay2 driver is officially recommended by Docker for its stability and performance, it should be used if all the conditions are met.&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/10/docker-under-the-hood/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker components&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/11/medical-imaging-web-server-deployment-pipeline/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automatic deployment of Orthanc on AWS&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Docker components</title><link>https://static.digihunch.com/2020/10/docker-under-the-hood/</link><pubDate>Wed, 28 Oct 2020 20:23:00 -0400</pubDate><guid>https://static.digihunch.com/2020/10/docker-under-the-hood/</guid><description>&lt;p class="wp-block-paragraph"&gt;The previous &lt;a href="https://static.digihunch.com/2020/08/virtualization-3-of-3-containers/" class="rank-math-link"&gt;post&lt;/a&gt; about virtualization and containerization brought up some underlying technologies which Docker build containers on, including:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;namespaces &amp;#8211; a Linux kernel mechanism to isolate resources. It allows a process to run within an isolated environment (mnt, pid, net, ipt, uts, user, cgroup)&lt;/li&gt;&#10;&lt;li&gt;cgroups &amp;#8211; a Linux kernel mechanism to limit resource usage of a process or process group&lt;/li&gt;&#10;&lt;li&gt;unionFS (this will be further discussed under Docker storage)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this post we further discuss the components in Docker, the dominant and popular player in container technology, as shown in the diagram below:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="629" src="https://static.digihunch.com/wp-content/uploads/2024/07/docker-component-1024x629.png" alt="" class="wp-image-11422" srcset="https://static.digihunch.com/wp-content/uploads/2024/07/docker-component-1024x629.png 1024w, https://static.digihunch.com/wp-content/uploads/2024/07/docker-component-300x184.png 300w, https://static.digihunch.com/wp-content/uploads/2024/07/docker-component-768x472.png 768w, https://static.digihunch.com/wp-content/uploads/2024/07/docker-component-1536x943.png 1536w, https://static.digihunch.com/wp-content/uploads/2024/07/docker-component.png 1938w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The component names can be seen under docker install directory. It consists of three groups:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Docker related: docker, dockerd, docker-init and docker-proxy&lt;/li&gt;&#10;&lt;li&gt;Containerd related: containerd, containerd-shim and ctr&lt;/li&gt;&#10;&lt;li&gt;Container runtime: runc&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we discuss each group:&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Docker-related components&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;docker is just an implementation of docker client, it supports commands to achieve all functions between client and server. Alternatively, user may use REST API, or Docker SDK to communicate with Docker server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;dockerd is the server process, to receive requests from docker (client), SDK library or REST API caller. It executes the request and returns status to client. There are three ways for docker (client) to communicate with dockerd.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;By Unix Socket (unix://socket_path). The default socket path used by dockerd is /var/run/docker.sock, which is why only root can use docker after installation.&lt;/li&gt;&#10;&lt;li&gt;TCP request (tcp://host:port). It is recommended to configure TLS communication in production environment.&lt;/li&gt;&#10;&lt;li&gt;By file descriptor (fd://) used in systemd service.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Unix socket is the default communication method. To allow remote access to dockerd, use -H to specify HOST and PORT when starting dockerd.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;docker-init is used by Docker as PID 1 process for containers, in case it needs to recycle zombie containers. To use this, specify &amp;#8211;init when running container.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;docker-proxy is used for port mapping. When you use -p switch with docker run, this docker-proxy is the service that maps the container port to host port. It does so by modifying the iptables nat rule.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Containerd related components&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;containerd component was separated from dockerd since Docker 1.11, in compliance with OCI standard. It is responsible for life cycle management of containers, it also manages images (e.g. pulling from repo), request from dockerd to call runc, storage and network resources.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;dockerd uses UNIX socket to send request to containerd. The default socket path for containerd is /run/containerd/containerd.sock. containerd execute the task and return status to dockerd. You may also directly use containerd to manage containers.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;ctr (containderd-ctr) is the client of containerd, mostly used only in development and testing. If the environment does not have dockerd, then you can use ctr as client, to send request directly to containerd.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;containerd-shim is used to decouple containerd from the containers. containerd-shim is the parent process of containers. This is so that restarting containerd does not impact the running containers.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Container runtime&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;runc is a standard implementation of OCI container runtime. It is a command-line tool to create and run containers.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Host legacy application in Docker 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/11/docker-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker storage&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Host legacy application in Docker 2 of 2</title><link>https://static.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/</link><pubDate>Thu, 22 Oct 2020 17:54:00 -0400</pubDate><guid>https://static.digihunch.com/2020/10/host-legacy-application-in-docker-2-of-2/</guid><description>&lt;p class="wp-block-paragraph"&gt;My &lt;a href="https://static.digihunch.com/2020/09/host-legacy-application-with-docker-compose/"&gt;previous notes &lt;/a&gt;include some tricks in hosting legacy application in docker. This is a continuation from that work, after 1.5 months&amp;#8230;&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Use Case&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I decided to use docker to host application for a good reason, and let me start with what this Java-based application does as a single process. When it is up it listens to more than 70 TCP ports for different business services. Here is a simplified list:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Application service&lt;/td&gt;&lt;td&gt;TCP port to bind&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Business service A&lt;/td&gt;&lt;td&gt;8030&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Business service B&lt;/td&gt;&lt;td&gt;8040&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Business service C&lt;/td&gt;&lt;td&gt;8050&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&amp;#8230;&amp;#8230;&lt;/td&gt;&lt;td&gt;&amp;#8230;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;TCP port requirement&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The application also communicates with database and search engine on the same server. Since I am building a training environment where multiple instances of our application needs to run on a single server host. All these instances of application share the same underlying database and search engine services. With multiple instances, additional constraints are introduced. For example:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Each instance requires more than 120 configuration files. A small number of them defines what ports the process binds to. The rest of configuration files are the same across all instances.&lt;/li&gt;&#10;&lt;li&gt;The OS needs to host 6 processes of the same application all running at the same time;&lt;/li&gt;&#10;&lt;li&gt;The OS does not allow multiple processes to bind to a single TCP port (duh!);&lt;/li&gt;&#10;&lt;li&gt;It is extremely labourious to change the path for application to read configuration files from. This bad configuration also breaks the upgrade process going forward. &lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From the statements of constraints, I determine that we need a mechanism to bring running application process into an isolated environment. This is exactly the definition of container and a perfect use case for docker. The following table represents an example of how the multiple instances can be orchestrated.&lt;/p&gt;&#10;&lt;table id="tablepress-11" class="tablepress tablepress-id-11 tbody-has-connected-cells"&gt;&#10;&lt;thead&gt;&#10;&lt;tr class="row-1"&gt;&#10;&#9;&lt;th class="column-1"&gt;OS&lt;/th&gt;&lt;th class="column-2"&gt;Container ID&lt;/th&gt;&lt;th class="column-3"&gt;Application Service&lt;/th&gt;&lt;th class="column-4"&gt;container port&lt;/th&gt;&lt;th class="column-5"&gt;published port&lt;/th&gt;&#10;&lt;/tr&gt;&#10;&lt;/thead&gt;&#10;&lt;tbody class="row-striping row-hover"&gt;&#10;&lt;tr class="row-2"&gt;&#10;&#9;&lt;td rowspan="9" class="column-1"&gt;Host&lt;br /&gt;&#10;CentOS&lt;/td&gt;&lt;td rowspan="3" class="column-2"&gt;Container 1&lt;br /&gt;&#10;(Instance #1)&lt;/td&gt;&lt;td class="column-3"&gt;Business Service A&lt;/td&gt;&lt;td class="column-4"&gt;8030&lt;/td&gt;&lt;td class="column-5"&gt;9301&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-3"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service B&lt;/td&gt;&lt;td class="column-4"&gt;8040&lt;/td&gt;&lt;td class="column-5"&gt;9401&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-4"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service C&lt;/td&gt;&lt;td class="column-4"&gt;8050&lt;/td&gt;&lt;td class="column-5"&gt;9501&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-5"&gt;&#10;&#9;&lt;td rowspan="3" class="column-2"&gt;Container 2&lt;br /&gt;&#10;(Instance #2)&lt;/td&gt;&lt;td class="column-3"&gt;Business Service A&lt;/td&gt;&lt;td class="column-4"&gt;8030&lt;/td&gt;&lt;td class="column-5"&gt;9302&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-6"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service B&lt;/td&gt;&lt;td class="column-4"&gt;8040&lt;/td&gt;&lt;td class="column-5"&gt;9402&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-7"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service C&lt;/td&gt;&lt;td class="column-4"&gt;8050&lt;/td&gt;&lt;td class="column-5"&gt;9502&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-8"&gt;&#10;&#9;&lt;td rowspan="3" class="column-2"&gt;Container 3&lt;br /&gt;&#10;(Instance #3)&lt;/td&gt;&lt;td class="column-3"&gt;Business Service A&lt;/td&gt;&lt;td class="column-4"&gt;8030&lt;/td&gt;&lt;td class="column-5"&gt;9601&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-9"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service B&lt;/td&gt;&lt;td class="column-4"&gt;8040&lt;/td&gt;&lt;td class="column-5"&gt;9602&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;tr class="row-10"&gt;&#10;&#9;&lt;td class="column-3"&gt;Business Service C&lt;/td&gt;&lt;td class="column-4"&gt;8050&lt;/td&gt;&lt;td class="column-5"&gt;9603&lt;/td&gt;&#10;&lt;/tr&gt;&#10;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;!-- #tablepress-11 from cache --&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This way of orchestration allows the different instances of applications to share as much configuration files as possible, so that each process thinks that they bind to TCP ports (8030, 8040, 8050, etc), by taking advantage of Docker&amp;#8217;s ability to map ports for publishing.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Below is an example of the docker compose file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#39;3.6&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dapp1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: docker.digihunch.com/dapp:${DAPP_VER}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: dapp1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entrypoint: [&amp;#34;/opt/docker-entrypoint.sh&amp;#34;,&amp;#34;dapp&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9301:8030 # BUSINESS SERVICE A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9401:8040 # BUSINESS SERVICE B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9501:8050 # BUSINESS SERVICE C&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac_address: 2c:1f:4e:c5:9e:cf&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - INSTANCE_TAG=dapp1 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MAX_JVM_HEAP=${DAPP_HEAP:-3892M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - vcnet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /opt/dapp/etc:/opt/dapp/etc:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp1/dapp.lic:/opt/dapp/etc/dapp.lic:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp1/variables:/opt/dapp/etc/variables:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deploy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resources:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limits:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpus: &amp;#39;0.5&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reservations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tty: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dapp2:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: docker.digihunch.com/dapp:${DAPP_VER}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: dapp2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entrypoint: [&amp;#34;/opt/docker-entrypoint.sh&amp;#34;,&amp;#34;dapp&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9302:8030 # BUSINESS SERVICE A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9402:8040 # BUSINESS SERVICE B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9502:8050 # BUSINESS SERVICE C&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac_address: 2c:1f:4e:c5:9e:d0 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - INSTANCE_TAG=dapp2 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MAX_JVM_HEAP=${DAPP_HEAP:-3892M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - vcnet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /opt/dapp/etc:/opt/dapp/etc:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp2/dapp.lic:/opt/dapp/etc/dapp.lic:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp2/variables:/opt/dapp/etc/variables:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deploy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resources:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limits:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpus: &amp;#39;0.5&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reservations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tty: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; dapp3:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: docker.digihunch.com/dapp:${DAPP_VER}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; container_name: dapp3&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; entrypoint: [&amp;#34;/opt/docker-entrypoint.sh&amp;#34;,&amp;#34;dapp&amp;#34;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9601:8030 # BUSINESS SERVICE A&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9602:8040 # BUSINESS SERVICE B&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - 9603:8050 # BUSINESS SERVICE C&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mac_address: 2c:1f:4e:c5:9e:d1 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - INSTANCE_TAG=dapp3 &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - MAX_JVM_HEAP=${DAPP_HEAP:-3892M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - vcnet&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - /opt/dapp/etc:/opt/dapp/etc:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp3/dapp.lic:/opt/dapp/etc/dapp.lic:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - ./instances/dapp3/variables:/opt/dapp/etc/variables:ro&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; deploy:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; resources:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; limits:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; cpus: &amp;#39;0.5&amp;#39;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; reservations:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; memory: ${DAPP_MEM:-4096M}&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; tty: true&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; vcnet:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; driver: bridge&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; driver_opts:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; com.docker.network.enable_ipv6: &amp;#34;false&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In this compose file, the environment variables are stored in .env file in the same directory and if they are not declared, the default is specified (syntax: ${VAR:-default}). &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Helper scripts&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The docker commands are fairly long so I had to organize them into several helper scripts. For example:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;docker-entrypoint.sh: this script is the ENTRYPOINT script for container. It is responsible for:&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Initialization work that cannot be done in Dockerfile, such as setting environment variable&lt;/li&gt;&#10;&lt;li&gt;Launch the application, including pointing log file to stdout&lt;/li&gt;&#10;&lt;li&gt;Adding host entry for host.docker.internal to /etc/hosts, as a workaround to &lt;a href="https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach"&gt;this&lt;/a&gt; issue with Docker on Linux&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;build_image.sh: this script makes the image build process smoother&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;check if image to build already exist, and ask permission to delete the existing image if so;&lt;/li&gt;&#10;&lt;li&gt;build the image with Dockerfile, and create directory structure for Dockerfile to use during COPY instruction&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;start_dapp_all.sh: this script starts all containers using docker-compose up and also add required iptables rules. We need to edit PREROUTING rules in IP tables to allow traffic between host NIC interface and the docker bridge interface, created each time service is up, as pointed out in &lt;a href="https://static.digihunch.com/2020/09/host-legacy-application-with-docker-compose/"&gt;previous post&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;stop_dapp_all.sh: this script removes the relevant iptables rules and stop all containers using docker-compose. Note that when deleting routing rules by number, start from the highest rule number and work your way down, since each deletion will cause the rules to be re-numbered.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Permission&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The container uses a non-root user to run application (e.g. with su dhunch -c &amp;#8220;command&amp;#8221; from entry point script to run application as dhunch user), because the legacy application uses the same (non-root) user to perform its actions, and it is generally not advised to use root user. To ensure consistency, we need to create the dhunch user in container (in Dockerfile) so it&amp;#8217;s uid and gid aligns with those of the host. The file and directory on the host to be access by the process in container also needs to allow dhunch user to read and write. Otherwise, entry point script will fail.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the docker-compose file, we mount a file or a directory on the host to the container, and specify 😮 if it is read only mount, under volumes. We can alternatively use bind mount (check &lt;a href="https://medium.com/devops-dudes/docker-volumes-and-bind-mounts-2fb4bd9df09d"&gt;here&lt;/a&gt; for comparison). In either case, we need to keep in mind of the permission &amp;#8211; owner alignment. For example, we have the following mount statement under volumes:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&amp;#8211; /var/lib/dapp/dcontainer/archive:/var/lib/dapp/dhost/archive&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We also need the entire directory hierarchy accessible to dhunch user. To configure this correctly, we need to create the entire directory hierarchy and set proper owner to it. Here is the comparison between the bad configuration and good configuration:&lt;/p&gt;&#10;&lt;figure class="wp-block-table"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Dockerfile instruction for container&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Permission issue during mount by docker-compose&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Bad config&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;RUN mkdir -p /var/lib/dapp &amp;amp;&amp;amp; chown -R dhunch:dhunch /var/lib/dapp&lt;br&gt;&lt;/td&gt;&lt;td&gt;The directory &amp;#8220;dcontainer&amp;#8221; was not created until mount time and it is created implicitly with root as owner (since there is no user section in docker-compose, so root as default is used). The application running as dhunch user in container will have permission issue going into dcontainer directory after mount.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;&lt;strong&gt;Good config&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;RUN mkdir -p /var/lib/dapp/dcontainer/archive &amp;amp;&amp;amp; chown -R dhunch:dhunch /var/lib/dapp&lt;/td&gt;&lt;td&gt;The directory &amp;#8220;dcontainer&amp;#8221; was already created with proper permission prior to mount and the main application process running as dhunch user will not have permission issue.&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For application process running as dhunch, it also needs to write logs to stdout, so the result can be viewed from outside the container using docker logs command. The docker-entrypoint.sh script makes this happen by:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;su dhunch -c &amp;#34;ln -sf /dev/stdout $DHUNCH_LOG_DIR/dhunch.log&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;However, this command itself will run into permission issues. To fix, we need to add user dhunch to tty group (e.g. in Dockerfile as it&amp;#8217;s needed on every container):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;usermod -a -G tty dhunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For application process to write to a shared volume on host (e.g. NFS), we can either allow access through volume mapping, or for performant access, mount the NFS share directly to container with proper driver. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Java application&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For Java applications, only use the needed package (openjdk, openjdk-devel, openjdk-headless) as the Docker image size must be kept as small as possible. The headless package is for non-UI components, the devel package is for development stuff.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is also worth-noting that the upper limit of heap size (Xmx) should be set based on the reserved memory of container (specified under docker-compose under resource limit and reservation). If heap is larger than container&amp;#8217;s available memory, OOM will be triggered and the container will be killed. &lt;a href="https://developers.redhat.com/blog/2017/03/14/java-inside-docker/"&gt;This article&lt;/a&gt; has some good explanation on this.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/10/automated-deployment-pipeline-3-of-3/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Automated Deployment Pipeline 3 of 3&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/10/docker-under-the-hood/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Docker components&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Host legacy application in Docker 1 of 2</title><link>https://static.digihunch.com/2020/09/host-legacy-application-with-docker-compose/</link><pubDate>Fri, 04 Sep 2020 16:24:00 -0400</pubDate><guid>https://static.digihunch.com/2020/09/host-legacy-application-with-docker-compose/</guid><description>&lt;p class="wp-block-paragraph"&gt;This is my notes from containerizing a legacy application with Docker &lt;a href="https://static.digihunch.com/2020/05/docker-swarm-brief-notes/"&gt;compose&lt;/a&gt;. We have to run multiple instances of our application because we&amp;#8217;re unable to secure additional VMs for this single-VM education environment. The application is target of containerization, because it requires mass reconfiguration (around TCP port) to run multiple instances of the application. We want to use the same application configuration file for multiple containers, and map the TCP port to different groups of ports on the host, leveraging port mapping in Docker. On the other hand, the auxiliary services are not being containerized, such as Cassandra database and ElasticSearch because they can be shared for multiple application instances. In other words, we use Docker to isolate processes of the same application.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-prepare-environment"&gt;Prepare environment&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The CentOS server needs to have docker-ce (through YUM) as well as docker-compose (direct download). They can be installed this way:&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 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo yum install docker-ce docker-ce-cli containerd.io&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -L &lt;span style="color:#e6db74"&gt;&amp;#34;https://github.com/docker/compose/releases/latest/download/docker-compose-&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;uname -s&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;-&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;uname -m&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt; -o /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo chmod +x /usr/local/bin/docker-compose&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ sudo systemctl start docker&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Our Docker registry is not publicly available. So we need to port the Docker image we need to remote server and load it into the local registry. We first examine the registry locally:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ curl -XGET https://admin:password@docker.digihunch.com/v2/dhunch/tags/list | python -m json.tool&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once we identify the image, we export it to a tar file:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker save docker.digihunch.com/dhunch &amp;gt; dhunch_image.tar&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;SCP the file to remote server and load it locally:&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 load -i /home/dhunch/dhunch_image.tar&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker image ls&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;We need to distinguish these commands:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;docker save&lt;/strong&gt;: saves an (non-running) image with all layers to file&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;docker export&lt;/strong&gt;: saves a running or paused container to file&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;docker import&lt;/strong&gt;: import the contents from a tarball to create a filesystem image, most used with docker export&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;docker load&lt;/strong&gt;: load an image from a tar archive or STDIN, most used with docker save&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 class="wp-block-heading" id="h-build-docker-compose-file"&gt;Build docker-compose file&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I need to cater to the customer environment with a newly create docker-compose file. The customer environment includes specific storage and networking configurations. Docker compose&amp;#8217;s official documentation is &lt;a href="https://docs.docker.com/compose/compose-file/"&gt;here&lt;/a&gt;. We repeat the following commands for our troubleshooting:&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-compose up -d&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker-compose exec -it dhunch1 bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ docker container ls&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once we start the container, the status might go unhealthy after it starts. The documentation explains two reasons you&amp;#8217;re seeing an unhealthy container:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;a single run of the command takes longer than the specified timeout&lt;/li&gt;&#10;&lt;li&gt;health check fails; the health check command will retry a number of times before it declares the container as unhealthy.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In our case,&amp;nbsp; It is most likely because it does not pass a built-in health check mechanism. We need to understand where the health check was defined. There are four ways to enable health check:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Dockerfile instruction when building the image&lt;/li&gt;&#10;&lt;li&gt;Docker run command&lt;/li&gt;&#10;&lt;li&gt;Docker-compose or docker stack yaml file&lt;/li&gt;&#10;&lt;li&gt;Docker service&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With #1, unfortunately, you can&amp;#8217;t reverse engineer an image and view the Dockerfile that were used to built it and review the health check statement. What you can do is check docker events, or inspect the container, and go to the log files as specified under logPath section in the inspection result and look for HealthCheck section. We determined it is the case, then we can disable, or override the built-in healthcheck command from image, with a statement in docker compose.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For network interface, docker compose also&amp;nbsp;allows us to specify MAC address for each container with mac_address keyword (for license key). MAC address generator are available on the internet. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-entrypoint-vs-cmd"&gt;EntryPoint vs CMD&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The difference between EntryPoint and CMD is very important when launching container. Some literature also mentions RUN, which is only used when &lt;span style="text-decoration: underline;"&gt;building a new layer of images&lt;/span&gt; so it is not relevant here (in the context of launching a container from image). EntryPoint and CMD has similar functionalities both allowing you to specify a command to run. The &lt;span style="text-decoration: underline;"&gt;difference is whether they can be overwritten by command line arguments&lt;/span&gt; that user provide to docker-compose or docker run in an ad-hoc manner. As their names suggests, EntryPOINT means what is specified under it must be executed as it launches into the container, regardless of any adhoc commands. On the other hand, CMD is just an entry to save users from typing in a command every time they run docker compose or docker run. Should user prefer a different command, it can be provided as an explicit argument and it will be respected overwriting the pre-defined CMDentry in Dockerfile or command entry in docker-compose.yml.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Both CMD and EntryPoint supports shell and exec forms. More details &lt;a href="https://www.ctl.io/developers/blog/post/dockerfile-entrypoint-vs-cmd/"&gt;here&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-choice-of-networking"&gt;Choice of Networking&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With single-host deployment, the containerized application needs to communicate with other existing, non-containerized service on host, such as database or elastic search. If docker uses host network, the container shares interface with the host and it does not have its own IP address. Host network removes isolation between container and host. This allows container to run the application that was licensed to the host based on MAC address. There is also no port mapping from container to host network. Container simply uses port on host, and is subject to the availability of TCP/UDP port on host.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will have to use bridge network here. We can force MAC address the app container, and pre-generate license. For container to &lt;a href="https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach"&gt;communicate with a service on host&lt;/a&gt;, through bridge network, there are two problems to address:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Container knows the IP of the host (layer-3 connectivity, ping);&lt;/li&gt;&#10;&lt;li&gt;Making host service available to container (layer-4 connectivity, telnet);&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker creates its own interface for bridge network. If it&amp;#8217;s an unnamed network, i.e. not explicitly declared under networks section in docker compose, then interface docker0 is used. If it&amp;#8217;s a named network, then an interface name starting with br- is used.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The first problem is easier to address, we simply needs to IP address of the host on the interface. We can validate by pinging from container to host. Docker can also use &lt;strong&gt;host.docker.internal &lt;/strong&gt;to reference the host. Unfortunately, this &lt;a href="https://stackoverflow.com/questions/24319662/from-inside-of-a-docker-container-how-do-i-connect-to-the-localhost-of-the-mach"&gt;stopped working for linux&lt;/a&gt; since 18.09.3.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;It is reportedly to be fixed in 20.04 and until it is available, we may add it to manual dns. The following command outputs the entry to add to /etc/hosts in 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;&lt;span style="color:#75715e"&gt;# ip -4 addr show $(basename -a /sys/class/net/* | grep ^br-) | grep -Po &amp;#39;inet \K[\d.]+&amp;#39; | awk &amp;#39;{print $1 &amp;#34; host.docker.internal&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To do this automatically in docker compose, we need some tricks:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Store the Host IP in host environment variable ( use an export command)&lt;/li&gt;&#10;&lt;li&gt;Use compose to pass host environment variable to container environment variable&lt;/li&gt;&#10;&lt;li&gt;Have the container write its environment variable to /etc/hosts&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The compose file will contain a line like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; myenv1:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: alpine&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; command: &amp;gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; sh -c &lt;span style="color:#e6db74"&gt;&amp;#34;apk update &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; echo &lt;/span&gt;$$&lt;span style="color:#e6db74"&gt;HostDNSLine &amp;gt;&amp;gt; /etc/hosts &amp;amp;&amp;amp;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; bash&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;#network_mode: bridge&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - HostDNSLine&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;${&lt;/span&gt;HOSTDNSREC&lt;span style="color:#e6db74"&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;Note ampersand might be mistakenly displayed as &amp;amp;amp; in the above. Then we run it with the following:&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;# export HOSTDNSREC=$(echo 1.2.3.4 host.docker.internal) &amp;amp;&amp;amp; docker-compose up&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 second problem is harder to address because the service on host may not bind to docker&amp;#8217;s interface. Some services such as ssh bind to all interfaces on host and you can telnet to port 22 with any IP address the host is associated with. This is however not the case for most other services, such as Cassandra or Elastic Search. They typically only bind to main interface, such as ens192, or eth0, and not to the docker interface. In order to make the service available to container, we either need to bind these services to the docker interface, or use iptables rules as an alternative.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose it is a named network and Docker&amp;#8217;s interface name is br-90ae024d5324, and the service on host listens to port 9042, we will need&amp;nbsp; the following two commands from host:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;# sysctl -w net.ipv4.conf.br-90ae024d5324.route_localnet=1&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;# iptables -t nat -A PREROUTING -p tcp -i br-90ae024d5324 --dport 9042 -j DNAT --to-destination 127.0.0.1:9042&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;Note that docker compose can configure to run sysctl in container but not from host. If there are multiple ports, we can turn this into a shell script:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;#!/bin/bash&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tcp_port_list&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;9200 9042 8302 8303 8304 8305 8306&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;if_name&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;basename -a /sys/class/net/* | grep ^br- | head -1&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo enable route localnet on interface $if_name&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;sysctl -w net.ipv4.conf.$if_name.route_localnet&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;1&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; tcp_port in $tcp_port_list; &lt;span style="color:#66d9ef"&gt;do&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; echo open host tcp port $tcp_port to interface $if_name&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; iptables -t nat -A PREROUTING -p tcp -i $if_name --dport $tcp_port -j DNAT --to-destination 127.0.0.1:$tcp_port&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;done&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;echo &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;ip -4 addr show &lt;span style="color:#66d9ef"&gt;$(&lt;/span&gt;basename -a /sys/class/net/* | grep ^br-&lt;span style="color:#66d9ef"&gt;)&lt;/span&gt; | grep -Po &lt;span style="color:#e6db74"&gt;&amp;#39;inet \K[\d.]+&amp;#39;&lt;/span&gt; | awk &lt;span style="color:#e6db74"&gt;&amp;#39;{print $1 &amp;#34; host.docker.internal&amp;#34;}&amp;#39;&lt;/span&gt;&lt;span style="color:#66d9ef"&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;On the other hand, binding service to multiple interfaces usually require some re-configuration on the service itself. For example, if it is Elastic Search, we need to update [network.host] entry in elasticsearch.yml to include multiple IP addresses. For Cassandra, we need to update rpc_address to 0.0.0.0 or set rpc_interface in &lt;a href="https://docs.datastax.com/en/developer/java-driver/3.0/manual/address_resolution/"&gt;cassandra.yml&lt;/a&gt;.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-integration-with-storage"&gt;Integration with storage&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The application in the container need to store files to storage available to host, whether it is an NFS share or a block disk. We can use volume mapping with Docker compose, to map a path in container to a path presented to host as persistent volume. At this step, we might run into permission issues. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;By default, containers initializes as root (uid=1) within the container, and the entrypoint script launches application as root. When application writes to persistent volume, files are written as root user. In the legacy non-container setup, we expect the application to write file as dhunch user. Moreover, NFS volume will not allow writing files as root (if the server has &lt;a href="https://en.wikipedia.org/wiki/Unix_security#Root_squash"&gt;root squash&lt;/a&gt; configured). To address this, there are two approaches:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;launch container as a regular user&lt;/li&gt;&#10;&lt;li&gt;launch container as root user, then have the entrypoint script launch application as regular user (dhunch)&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For approach 1, we need to tell Docker to launch container as a regular user by specify the uid and gid for container to run application. We can specify the following envrionment variable in the compose yaml:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;user: &lt;span style="color:#e6db74"&gt;${&lt;/span&gt;CURRENT_UID&lt;span style="color:#e6db74"&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;Then we assign the environment variable before running docker-compose:&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;# export CURRENT_UID=$(id -u dhunch):$(id -g dhunch) &amp;amp;amp;&amp;amp;amp; docker-compose up&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This allows container to initialize as the regular user. However, if the entry point script needs to perform activities that requires root permission within the container, it will fail. For example, a regular user in container will not be able to update /etc/hosts;&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With approach 2, we do not specify user in docker compose so container initializes as root. Then the entry point script launches application as regular user. For example, use su command before launch Java:&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;su dhunch -c &lt;span style="color:#e6db74"&gt;&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;exec java \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -Xms512M -Xmx8192M \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -Djava.io.tmpdir=&lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/var/tmp \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -server \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -XX:CompileCommandFile=&lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/etc/hotspot_compiler \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; -jar &lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/lib/jar/jruby-complete-*.jar \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; --1.9 \&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt; &lt;/span&gt;$APP_HOME&lt;span style="color:#e6db74"&gt;/lib/rubybin/runapp.rb&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Before doing this, we need to first create user dhunch within container, and the uid and gid must match those of the host. So that when container picks up dhunch user, it converts it to the correct uid.&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;groupadd -g &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; dhunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;useradd -m -c &lt;span style="color:#e6db74"&gt;&amp;#39;regular user&amp;#39;&lt;/span&gt; -u &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; -g &lt;span style="color:#ae81ff"&gt;1011&lt;/span&gt; dhunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To further understand how uid and gid work,&lt;a href="https://medium.com/@mccode/understanding-how-uid-and-gid-work-in-docker-containers-c37a01d01cf"&gt; here&lt;/a&gt; are &lt;a href="https://medium.com/redbubble/running-a-docker-container-as-a-non-root-user-7d2e00f8ee15"&gt;two&lt;/a&gt; posts with more information.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This user ownership setup will also work for NFS. To configure NFS, we need some extra client-side configurations in the container, as well as a special volume driver for NFS. Refer to &lt;a href="https://stackoverflow.com/questions/45282608/how-to-directly-mount-nfs-share-volume-in-container-using-docker-compose-v3"&gt;this&lt;/a&gt; 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/2020/08/zookeeper/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Zookeeper Summary&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/09/intro-to-big-data-projects/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to Big Data Projects&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Virtualization 3 of 4 – Containers</title><link>https://static.digihunch.com/2020/08/virtualization-3-of-3-containers/</link><pubDate>Tue, 18 Aug 2020 20:44:35 -0400</pubDate><guid>https://static.digihunch.com/2020/08/virtualization-3-of-3-containers/</guid><description>&lt;p class="wp-block-paragraph"&gt;In broad terms, virtualization of computing resource is about isolation of resources at different levels. We have covered hypervisor-based virtualization in the &lt;a href="https://static.digihunch.com/2020/07/overview-of-virtualization/"&gt;other&lt;/a&gt; post. In this article, we continue to dive into OS level virtualization.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Remember again that the gist of virtualization is isolation of resource. To support OS level virtualization, the OS must have its own capability to isolate computing resource. There are many implementations of &lt;a href="https://en.wikipedia.org/wiki/OS-level_virtualization"&gt;OS level virtualization&lt;/a&gt;.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Linux Kernel provides low-level mechanisms some two kernel features(namespaces, cgroups and chroot) for building various lightweight tools that can virtualize the system environment. Docker is such framework that builds on chroot namespaces and cgroups.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-chroot"&gt;Chroot&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Traditionally, root directory (/) is the top directory shared amongst all processes in the OS. There was a chroot() system call that allows each process to have its own idea of root directory. A chroot is an operation that changes the apparent root directory(/) for the current running process and their children. A program that is run in such a modified environment cannot access files and commands outside that environmental directory tree. This modified environment is called a &lt;strong&gt;chroot jail&lt;/strong&gt;. By separating a process using chroot() we ensure security by restricting the process from accessing outside its environment (breaking the jail). This short &lt;a href="https://www.youtube.com/watch?v=2wSJREC7RV8"&gt;video&lt;/a&gt; is a great lab.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Although chroot() has a basic idea of isolation, it simply modifies pathname lookups for a process and its children (by prepending the new root path to any name starting with /). Relative paths can still refer any locations outside of the new root. So chroot() does not intend to defend against intentional tampering by privileged users.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-namespace-isolation"&gt;Namespace Isolation&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Namespaces are fundamentally the mechanisms to abstract, isolate, and limit the visibility that a group of processes has over various system entities such as process trees, network interfaces, user IDs and file system mounts. So there are several categories of namespaces:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Mount namespaces &amp;#8211; traditionally, there is one global mount namespace seen by all processes. The mount namespaces confine the set of filesystem mount points visible within a process namespace, enabling one process group in a mount namespace to have an exclusive view of the filesystem list, compared to another process.&lt;/li&gt;&lt;li&gt;UTS namespaces &amp;#8211; allows isolation of hostname per namespace. Each namespace can have its own hostname on the network&lt;/li&gt;&lt;li&gt;User namespaces &amp;#8211; allow a process to use unique user and group IDs&lt;/li&gt;&lt;li&gt;Cgroup namespaces &amp;#8211; processes inside a &lt;a href="https://man7.org/linux/man-pages/man7/cgroup_namespaces.7.html"&gt;cgroup namespace&lt;/a&gt; are only able to view paths relative to their namespace root.&lt;/li&gt;&lt;li&gt;IPC namespaces &amp;#8211; isolates the System V inter-process communication between namespaces, as well as POSIX message queues within each namespace. POSIX message queue allow process to exchange data in the form of messsages.&lt;/li&gt;&lt;li&gt;PID namespaces &amp;#8211; traditionally, *nix kernels spawn the init process with PID 1 during system boot, which in turn starts other user-mode process and is considered the root of the process tree (all the other processes start below this process in the tree). The PID namespace allows a process to spin off a new tree of processes under it with its own root process (PID=1). PID namespaces isolate process ID numbers, and allow duplication of PID numbers across different PID namespaces. The process IDs only needs to be unique within a PID namespace, and are assigned sequentially starting with PID 1. PID namespaces are used in containers.&lt;/li&gt;&lt;li&gt;Network namespaces &amp;#8211; traditionally, all processes in the entire OS share a single set of network interfaces and routing table entries. The routing table entries can be modified at operating system level. With network namespace, this assumption is no longer valid. Network namespace provides abstraction and virtualization of network protocol and interfaces. Each network namespace will have its own network device instances that can be configured with individual network addresses. Other network services, such as routing table, port number, are isolated as well.&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Namespaces are created with the &amp;#8220;&lt;em&gt;unshare&lt;/em&gt;&amp;#8221; command or syscall, or as new flags in a &lt;em&gt;&lt;a href="https://man7.org/linux/man-pages/man2/clone.2.html"&gt;clone&lt;/a&gt;()&lt;/em&gt; syscall. The flags are listed here in the &lt;a href="https://man7.org/linux/man-pages/man7/namespaces.7.html"&gt;man&lt;/a&gt; page for namespace. Note that the &lt;em&gt;clone()&lt;/em&gt; syscall is a more generic implementation of &lt;em&gt;fork()&lt;/em&gt; syscall.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-cgroup"&gt;Cgroup&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;cgroups is a Linux kernel feature that limits, accounts for, and isolates the resource usage (CPU, memory, disk I/O, network, etc) of a collection of processes (not to be confused with process group, which has its own meaning). Cgroup has two versions. The control groups functionality (version 1) was merged into Linux kernel mainline in version 2.6.24, released in 2008, and version 2 in kernel 4.5 (March 2016), with significant changes to the interface and internal functionality.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Using cgroups, you can allocate resources such as CPU time, network and memory. Similiar to the process model in Linux, where each process is a child to a parent and relatively descends from the init process thus forming a single-tree like structure, cgroups are hierarchical, where child cgroups inherit the attributes of the parent, but what makes it different is that multiple cgroup hierarchies can exist within a single system, with each having distinct resource prerogatives.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Applying cgroups on namespaces results in isolation of processes into containers within a system, where resources are managed distinctly. Each container is a lightweight virtual machine, all of which run as individual entities and are oblivious of other entities within the same system.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-container-implementation"&gt;Container Implementation&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Above we covered some kernel features that enables container technology. There are many ways to use these technologies to implement the isolation. We call them container runtime. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a href="https://en.wikipedia.org/wiki/LXC"&gt;LXC&lt;/a&gt; is a user space interface for those Linux kernel containment features. It allows for running isolated containers on a control host using a single kernel. Users can launch a system init for each containers, also referred to as virtual environment (as opposed to virtual machines). The author of this &lt;a href="https://www.upguard.com/blog/docker-vs-lxc"&gt;article&lt;/a&gt; regard LXC as a suprcharged chroot on Linux. LXC has rest API tool called LXD. LXC was targeting sysadmin&amp;#8217;s use cases (not developer) to isolate users&amp;#8217; own private workloads from one another. In early days Docker was built on LXC. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker&amp;#8217;s target market is developers, and it moved beyond LXC with its own execution environment called &lt;em&gt;&lt;strong&gt;libcontainer&lt;/strong&gt;&lt;/em&gt;. With the initial success of Docker, a large community (Docker, CoreOS, Google, etc) emerged around the idea of using containers as the standard unit of software delivery. They started the Open Container Initiative (OCI) to define industry standards around container runtime (runtime spec) and image format (image spec). Docker &lt;a href="https://opencontainers.org/faq/#what-has-docker-done-to-help-create-this-foundation"&gt;donated&lt;/a&gt; the &lt;a href="https://github.com/docker-archive/libcontainer"&gt;libcontainer&lt;/a&gt; codebase to run independently under OCI, as &lt;a href="https://github.com/opencontainers/runc"&gt;runc&lt;/a&gt;. Docker implements isolation using the following technologies:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&lt;li&gt;Namespace: to isolate process ID, networking, mount points, IPC, host and domain name;&lt;/li&gt;&lt;li&gt;Cgroups: to isolate the usage of CPU and memory between containers&lt;/li&gt;&lt;li&gt;UnionFS: isolate file system&lt;/li&gt;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Another container runtime technology is &lt;a href="https://en.wikipedia.org/wiki/OpenVZ"&gt;OpenVZ&lt;/a&gt;, which includes an extension of the Linux kernel. It uses container for entire operating systems (not just application and processes). All OpenVZ containers have to share the same Linux kernel version as host. The &lt;a href="https://wiki.aquasec.com/display/containers/Docker+Alternatives+-+Rkt%2C+LXD%2C+OpenVZ%2C+Linux+VServer%2C+Windows+Containers"&gt;adoption&lt;/a&gt; of OpenVZ is not high.&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Framework&lt;/td&gt;&lt;td&gt;Runtime implementation&lt;/td&gt;&lt;td&gt;Management tool&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;LXC&lt;/td&gt;&lt;td&gt;libvert&lt;br&gt;LXC&lt;/td&gt;&lt;td&gt;LXD (rest API)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;OCI&lt;/td&gt;&lt;td&gt;Docker&amp;#8217;s runc&lt;br&gt;CoreOS&amp;#8217;s rtk&lt;/td&gt;&lt;td&gt;docker engine (daemon and cli)&lt;br&gt;rtk cli&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption&gt;container runtimes&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker is now widely adopted for application hosting in production environment. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-container-and-cloud"&gt;Container and Cloud&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Public cloud vendors also has &lt;a href="https://logz.io/blog/aws-eks-vs-ecs-vs-fargate-understand-differences/"&gt;managed services&lt;/a&gt; around Docker. Here are some examples:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Managed Container&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Image Registry&lt;/strong&gt;&lt;/td&gt;&lt;td&gt;&lt;strong&gt;Managed Orchestration&lt;/strong&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;AWS&lt;/td&gt;&lt;td&gt;Elastic Container Service&lt;/td&gt;&lt;td&gt;Elastic Container Registry&lt;/td&gt;&lt;td&gt;Elastic Kubernetes Services&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Azure&lt;/td&gt;&lt;td&gt;Container Instances&lt;/td&gt;&lt;td&gt;Container Registry&lt;/td&gt;&lt;td&gt;Azure Kubernetes Service&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;GCP&lt;/td&gt;&lt;td&gt;CloudRun&lt;/td&gt;&lt;td&gt;Container Registry&lt;/td&gt;&lt;td&gt;Google Kubernetes Engine&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Digital Ocean&lt;/td&gt;&lt;td&gt;N/A&lt;/td&gt;&lt;td&gt;Container Registry&lt;/td&gt;&lt;td&gt;Kubernetes&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption&gt;Container services from public cloud&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cloud service was originally developed with VM as a unit of computing resource to service. OS level virtualization allows container to be a unit of computing resource. All these new technologies breed the serverless architecture and cloud-native deployment model. This has significant impact on the creation and delivery of software services. The &lt;a href="https://landscape.cncf.io/"&gt;cloud native landscape&lt;/a&gt; page illustrates more tools around containers.&lt;br&gt;&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/08/cloud-storage-overview/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Cloud storage overview&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/08/virtualization-4-of-4-networking/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Virtualization 4 of 4 – Networking&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Docker network in different modes</title><link>https://static.digihunch.com/2020/07/dockersnetwork/</link><pubDate>Wed, 01 Jul 2020 20:19:00 -0400</pubDate><guid>https://static.digihunch.com/2020/07/dockersnetwork/</guid><description>&lt;p class="wp-block-paragraph"&gt;Reading notes of &amp;#8220;Docker DeepDive&amp;#8221;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker networking is backed by libnetwork, which is an implementation of &lt;a href="https://github.com/moby/libnetwork/blob/master/docs/design.md"&gt;Container Network Model&lt;/a&gt; (CNM), an open-source pluggable architecture designed to provide networking to containers. Libnetwork also provides native service discovery and basic container load balancing solution. Docker networking also involves some drivers that extend the CNM model with specific network topology implementation.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Sandbox&lt;/strong&gt; &amp;#8211; an isolated network stack, including Ethernet interfaces, ports, routing tables, and DNS config, usually implemented through Linux namespace.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Endpoints&lt;/strong&gt; &amp;#8211; behave like regular network adapters, and can only be connected to a single network at a time. It connects sandbox to network. Endpoint is implemented in veth pair in Linux.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Networks&lt;/strong&gt; &amp;#8211; software implementation of an 802.1 bridge (aka switch). They group together, and isolate, a collection of endpoints that need to communicate.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://www.dclessons.com/uploads/2019/09/Docker-7.4.png" alt=""/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker company separates network project out from its container project, as a plugin called libnetwork, which is developed in Golang and compliant to CNM. Libnetwork is the official implementation of CNM.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Libnetwork supports the following network modes:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-stripes"&gt;&lt;table class="has-background" style="background-color:#e9fbe5"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;network mode&lt;/td&gt;&lt;td&gt;mechanism&lt;/td&gt;&lt;td&gt;use case&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;null&lt;/td&gt;&lt;td&gt;no network is provided to containers&lt;/td&gt;&lt;td&gt;quarantined environment for security&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;bridge&lt;/td&gt;&lt;td&gt;containers communicate with each other through bridge&lt;/td&gt;&lt;td&gt;containers needs to communicate with each other or with host service&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;host&lt;/td&gt;&lt;td&gt;process in container has access to host network stack and use host port&lt;/td&gt;&lt;td&gt;container needs to use host network stack (e.g. licence by mac address)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;container&lt;/td&gt;&lt;td&gt;place containers in a single net namespace so they can communicate with each other as localhost&lt;/td&gt;&lt;td&gt;proxy, kubernetes&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Linux veth comes in pairs to connect virtual network devices. For example, connect two net namespaces to allow intercommunication. Linux bridge is a virtual device, to connect two net namespaces.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://developers.redhat.com/blog/wp-content/uploads/2018/10/veth.png" alt="Introduction to Linux interfaces for virtual networking - Red Hat Developer"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Dockers ships with several built-in drivers, known as native drivers or local drivers, such as bridge, overlay and macvlan on Linux. There are also 3rd-party network drivers for docker (aka remote drivers).&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-host-network"&gt;Host network&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In this mode libnetwork will not create network and net namespace for container. Container process shares the network configuration of the host, and therefore uses the ports on host. Other than network sharing, other aspects (e.g. process, file system, hostname, etc) are separated from host.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-bridge-networks"&gt;Bridge networks&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This type of network only exist on a single Docker host and can only connect containers that are on the same host. The word bridge refers to 802.1d bridge (layer 2 switch), which is used to connect multiple network interfaces.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Every Docker host gets a default single-host network, called &lt;span style="text-decoration: underline;"&gt;bridge&lt;/span&gt; on Linux. This is the network that all new containers will attach to by default.&lt;br&gt;Docker networks built with the bridge driver on Linux hosts are based on the linux bridge technology that has existed in the Linux kernel for a while. They&amp;#8217;re high performance and extremely stable. Linux &lt;em&gt;&lt;strong&gt;brctl&lt;/strong&gt;&lt;/em&gt; tool can inspect the linux bridge.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Bridge networks allows container on the same host to communicate with each other. Port mapping allows network connectivity between container and host. Traffic hitting host port will be redirected to container port.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-multi-host-overlays"&gt;Multi-host overlays&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Cross-host networking usually uses an overlay network, which builds a mesh between host and employs a large block of IP addresses within that mesh. A mesh network is a local network topology in which the infrastructure nodes connect directly, dynamically and non-hierarchically to as many other nodes as possible and cooperate with one another to efficiently route data from/to clients.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;You can attach a service to overlay network, which spans across multiple Docker hosts so that containers on different hosts can communicate &lt;span style="text-decoration: underline;"&gt;at layer 2&lt;/span&gt;. They are much better alternatives than bridge network for container-to-container communication. Overlay networking is very common due to its scalability. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The trick is basically the layer 2 frame of the overlay network is encapsulated into layer 3 datagram transmitted across underlay network, at layer 3. This is achieved through VXLAN tunnels, which allows you to create a virtual Layer 2 network on top of an existing Layer 3 infrastructure. VXLAN is an encapsulation technology that existing routers and network infrastructure just see as regular IP/UDP packets without issue.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To create the virtual Layer 2 overlay network, a VXLAN tunnel is created through the underlying Layer 3 IP infrastructure (aka underlay network). Each end of the VXLAN tunnel is terminated by a &lt;strong&gt;&lt;span style="text-decoration: underline;"&gt;VXLAN Tunnel Endpoint (VTEP)&lt;/span&gt;&lt;/strong&gt;. It&amp;#8217;s this VTEP that performs the encapsulation/de-encapsulation.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-vxlan-networking"&gt;VXLAN networking&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To accomplish overlay network across multiple hosts, a new network sandbox was created on each host. A sandbox is like a container, but instead of running an application, it runs an isolated network stack &amp;#8211; one that&amp;#8217;s sandboxed from the network stack of the host itself.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A &lt;span style="text-decoration: underline;"&gt;&lt;strong&gt;&lt;em&gt;virtual switch&lt;/em&gt;&lt;/strong&gt;&lt;/span&gt; (aka virtual bridge) called Br0 is created inside the sandbox. A &lt;strong&gt;&lt;em&gt;VTEP&lt;/em&gt;&lt;/strong&gt; is also created with one end plumbed into the Br0 virtual switch, and the other end plumbed into the host network stack (VTEP). The end in the host network gets an IP address on the underlay network the host is connected to and is bound to a UDP socket on port 4789. The two VTEPs on each host create the overlay via a VXLAN tunnel.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Each container then gets its own virtual Ethernet (veth) adapter that is also plumbed into the local Br0 virtual switch.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s go over an example in the following diagram, where container C1 with an overlay IP needs to communicate to another container C2, with a different overlay IP, sitting on a different node (Docker host). Each node has its own underlay IP.&lt;/p&gt;&#10;&lt;figure class="wp-block-image"&gt;&lt;img decoding="async" src="https://img1.wsimg.com/isteam/ip/ada6c322-5e3c-4a32-af67-7ac2e8fbc7ba/8.jpg/:/cr=t:0%25,l:0%25,w:100%25,h:100%25/rs=w:1280" alt=""/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;IP communication details:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;C1 creates the IP datagram with destination IP (C2) and sends it over its veth interface, which is connected to the Br0 virtual switch on the host node. &lt;/li&gt;&#10;&lt;li&gt;The virtual switch doesn&amp;#8217;t know where to send the datagram, as it doesn&amp;#8217;t have an entry in its ARP table that corresponds to the destination IP address. As a result, it floods the packet to all ports. The VTEP interface connected to Br0 knows how to forward the frame, so responds with its own MAC address. &lt;/li&gt;&#10;&lt;li&gt;This is a proxy APR reply and results in the Br0 switch learning how to forward the packet. So it updates its ARP mapping the destination IP address to the MAC address of the local VTEP.&lt;/li&gt;&#10;&lt;li&gt;The VTEP knows about C2 because all newly started containers have their network details propagated to the other nodes in the Swarm using the network&amp;#8217;s built-in gossip protocol. When the packet arrives at node2&lt;/li&gt;&#10;&lt;li&gt;The VTEP encapsulates the frame so it can be sent over the underlay transport infrastructure, by adding a VXLAN header to the Ethernet frame. The VXLAN header contains the VXLAN network ID (VNID) which is used to map frames from VLANs to VXLANs and vice versa.&lt;/li&gt;&#10;&lt;li&gt;Each VLAN gets mapped to VNID, so that the packet can be de-encapsulated on the receiving end and forwarded to the correct VLAN. This is how network isolation is maintained. The encapsulation also wraps the frame in a UDP packet with the IP address of the remote VTEP on node2 in the destination IP field, and the UDP port 4789 socket information. The underlying network does not know that it is transporting data frames for the overlay network.&lt;/li&gt;&#10;&lt;li&gt;When the packet arrives at node2, the kernel sees that it&amp;#8217;s addressed to UDP port 4789. The kernel also knows that it has a VTEP interface bound to this socket. As a result, it sends the packet to the VTEP, which reads the VNID, de-encapsulates the packet, and sends it on to its own local Br0 switch on the VLAN that corresponds the VNID. From there it is delivered to container C2&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker also supports Layer 3 routing within the same overlay network. For example, you can create an overlay network with two subnets, and Docker will take care of routing between them. Two subnets will require two virtual switches, Br0 and Br1, being created inside the sandbox, and routing happens by default.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-plugging-into-existing-vlans"&gt;Plugging into existing vLANs&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The built-in MACVLAN driver was created for onnect containerized apps to external physical network. A good example is partially containerized app, in which the containerized parts will need a way to communicate with the non-containerized parts still running on existing physical networks.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To connect the container interface through the host interface to an external network, the host NIC needs to be in promiscuous mode. For public cloud, this is most likely prohibited. For data centers, this depends on the network policy.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker MACVLAN driver is built on top of Linux kernel driver with the same name. As such, it supports VLAN trunking. This means we can create multiple MACVLAN networks and connect containers on the same Docker host to them.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="392" height="230" src="https://static.digihunch.com/wp-content/uploads/2020/07/image-2.png" alt="" class="wp-image-1169" style="width:540px;height:317px"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For connectivity issues between containers, it&amp;#8217;s worth checking both the daemon logs (on host) and container logs.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-service-discovery"&gt;Service discovery&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;allows all containers and Swarm services to locate each other by name, as long as they are on the same network. This leverages Docker&amp;#8217;s embedded DNS server as well as a DNS resolver in each container.&lt;br&gt;Each Swarm Service and standalone container started with the &amp;#8211;name flag will register its name and IP address with the Docker DNS service.&lt;br&gt;This name resolution, however, only works within the same network.&lt;br&gt;It is also possible to configure Swarm services and standalone containers with customized DNS options in case embedded Docker DNS server cannot resolve a query (/etc/resolv.conf)&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="381" height="103" src="https://static.digihunch.com/wp-content/uploads/2020/07/image-3.png" alt="" class="wp-image-1170" style="width:547px;height:148px"/&gt;&lt;/figure&gt;&#10;&lt;h3 class="wp-block-heading" id="h-ingress-load-balancing"&gt;Ingress load balancing&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Services published via ingress mode (by default, as opposed to host mode) can be accessed from any node in the Swarm, even nodes not running a service replica. Ingress mode uses a layer 4 routing mesh called the Service Mesh or the Swarm Mode Service Mesh.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="396" height="206" src="https://static.digihunch.com/wp-content/uploads/2020/07/image-4.png" alt="" class="wp-image-1171" style="width:557px;height:290px"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Updates:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The most common network modes that I use are host and bridge. With host network mode, container exposes ports on the interface of the host machine. Containers talk to each other via that interface. With bridge network, containers have their own namespace of networking separate from the one from the interface of the hosts, with a bridge getting the two networks connected.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-reference"&gt;Reference&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker Deep dive&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large is-resized"&gt;&lt;img loading="lazy" decoding="async" width="830" height="1024" src="https://static.digihunch.com/wp-content/uploads/2023/01/docker-deep-dive-830x1024.jpeg" alt="" class="wp-image-7915" style="width:209px;height:258px" srcset="https://static.digihunch.com/wp-content/uploads/2023/01/docker-deep-dive-830x1024.jpeg 830w, https://static.digihunch.com/wp-content/uploads/2023/01/docker-deep-dive-243x300.jpeg 243w, https://static.digihunch.com/wp-content/uploads/2023/01/docker-deep-dive-768x947.jpeg 768w, https://static.digihunch.com/wp-content/uploads/2023/01/docker-deep-dive.jpeg 1000w" sizes="auto, (max-width: 830px) 100vw, 830px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2020/06/iterate-through-cassandra-table-with-datastax-python-driver/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;DataStax Python Driver&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/07/emc-productlines/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;EMC Isilon storage product&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Docker Compose, Docker Stack and Docker Swarm</title><link>https://static.digihunch.com/2020/05/docker-swarm-brief-notes/</link><pubDate>Sun, 24 May 2020 21:58:03 -0400</pubDate><guid>https://static.digihunch.com/2020/05/docker-swarm-brief-notes/</guid><description>&lt;p class="wp-block-paragraph"&gt;This posting covers some basic docker orchestration tools.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Docker Compose&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Docker Compose&amp;#8217;s predecessor is a tool called Fig developed by Orchard, which was acquired by Docker in 2014, with Fig renamed to Docker Compose. Docker Compose is the official container management tool. It is essentially a python script that parses yaml file, to make Docker API calls to manage containers dynamically. It is installed along with Docker on MacOS and Windows. On Linux, you will have to download package with curl command and install manually. Docker Compose has three versions so far and we should create new template with v3. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Docker compose yaml template consists of three parts:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;services&lt;/strong&gt;: similar to docker run&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;build: specify Dockerfile to build image&lt;/li&gt;&#10;&lt;li&gt;cap_add, cap_drop: specify kernel capabilities (e.g. NET_ADMIN, SYS_ADMIN)&lt;/li&gt;&#10;&lt;li&gt;command: override default startup command by container&lt;/li&gt;&#10;&lt;li&gt;container_name&lt;/li&gt;&#10;&lt;li&gt;depends_on&lt;/li&gt;&#10;&lt;li&gt;devices: map host device to container&lt;/li&gt;&#10;&lt;li&gt;dns&lt;/li&gt;&#10;&lt;li&gt;dns_search:&lt;/li&gt;&#10;&lt;li&gt;entryppoint: override entry point from image&lt;/li&gt;&#10;&lt;li&gt;env_file: specify file that stores environment variable&lt;/li&gt;&#10;&lt;li&gt;environment: specify environment variable&lt;/li&gt;&#10;&lt;li&gt;image: specify the location of image&lt;/li&gt;&#10;&lt;li&gt;pid: share the PID namespace with host&lt;/li&gt;&#10;&lt;li&gt;ports: expose network ports. HOST:CONTAINER&lt;/li&gt;&#10;&lt;li&gt;networks&lt;/li&gt;&#10;&lt;li&gt;volumes: mount host volume to container&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;networks&lt;/strong&gt;: similar to docker network create&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;volumes&lt;/strong&gt;: similar to docker volume create&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here is a typical structure of docker compose yaml template (wordpress):&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;version: &amp;#34;3.8&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;services:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mysql:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image:mysql:5.7&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; volumes:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - mysql_data:/var/lib/mysql&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; restart: always&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MYSQL_ROOT_PASSWORD:root&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MYSQL_DATABASE:mywordpress&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MYSQL_USER:digihunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; MYSQL_PASSWORD:hunchdigi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; wordpress:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; depends_on:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - mysql&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; image: wordpress:php7.4&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ports:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &amp;#34;8080:80&amp;#34;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; restart:always&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; environment:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WORDPRESS_DB_HOST:mysql:3306&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WORDPRESS_DB_USER:digihunch&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WORDPRESS_DB_PASSWORD: hunchdigi&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; WORDPRESS_DB_NAME: digihunch &#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;networks:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; frontend:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; backend:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;volumes&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; mysql-data: {}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;In summary, Docker Compose is an orchestration tool for &lt;strong&gt;&lt;span style="text-decoration: underline;"&gt;single host&lt;/span&gt;&lt;/strong&gt;, typically seen in development and test environment with dependencies between services.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Docker Stack&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A stack is a set of related services and infrastructure that gets deployed and managed as a unit. A docker stack file has the same format as Docker Compose file, with the only requirement that the version: key specify a value of 3.0. The other difference between Docker Stacks and Docker Compose, is that stacks do not support builds. All images have to be built prior to deploying the stack.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;From the stack file, Docker first executes the network section and create networks that do not exist. Then it goes through other elements. A service is a JSON collection(dictionary) that contains a bunch of keys. The image key is the only mandatory key in the service objects, which will be pulled from Docker Hub by default. Ports key maps the port of Swarm to the port of each service replica. By default, all ports are mapped using ingress mode. This means they&amp;#8217;ll be mapped and accesible from every node in the Swarm -even nodes not running a replica. The alternative is host mode, where ports are only mapped on Swarm nodes running replicas for the service.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The environment key lets you inject environment variables into services replica.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The secrets key defines two secrets &amp;#8211; revprox_cert and revprox_key. These must be defined in the top-level secrets key, and must exist on the system. Secrets get mounted into service replicas as a regular file. The secrets defined in this service will be mounted in each service replica as /run/secrets/revprox_cert and /run/secrets/revprox_key, unless otherwise specified.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The volumes key is used to mount pre-created volumes and host directories into a service replica.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The networks key ensures that all replicas for the service will be attached to the front-tier network. The network specified here must be defined in the networks top-level key, and if it doesn’t already exist, Docker will create it as an overlay.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The service also defines a placement constraint under the deploy key. This ensures that replicas for this service will always run on Swarm worker nodes. Placement constraints are a form of topology-aware scheduling, and can be a great way of influencing scheduling decisions.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When Docker stops a container, it issues a SIGTERM to the process with PID 1 inside the container. The container (its PID 1 process) then has a 10-second grace period to perform any clean-up operations. If it doesn’t handle the signal, it will be forcibly terminated after 10 seconds with a SIGKILL. The stop_grace_period property overrides this 10 second grace period.”&lt;br&gt;Although you may scale a docker service as part of a stack with scale command, it is not recommended. Instead, stack file should be used as the ultimate source of truth (declarative method vs imperative method). All changes to the stack should be made to the stack file, and the updated stack file used to redeploy the app.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading"&gt;Docker Swarm&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;For multi-host cluster, Docker Swarm facilitates the deployment of micro-services. Docker Swarm is:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;a &lt;span style="text-decoration: underline;"&gt;cluster&lt;/span&gt; of Docker hosts: enterprise-grade, secure communication, PKI with automation, dynamic addition of nodes&lt;/li&gt;&#10;&lt;li&gt;an &lt;span style="text-decoration: underline;"&gt;orchestration engine&lt;/span&gt;, with deployment automation, deploying native swarm apps (using Docker API) and Kubernetes apps.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Docker nodes can be physical servers, VMs, cloud instances, etc. Nodes are configured as managers or workers. Managers look after the control plane of the cluster, and dispatches tasks to workers. Managers forms a distributed management cluster on its own, and they use Raft protocol to ensure consistency. Workers accept tasks from managers and execute them. Swarm mandatorily uses TLS to encrypt communications, authenticate nodes, and authorize roles, with Automatic key rotation.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1024" height="388" src="https://static.digihunch.com/wp-content/uploads/2020/05/swarm-node-1024x388.webp" alt="" class="wp-image-13095" srcset="https://static.digihunch.com/wp-content/uploads/2020/05/swarm-node-1024x388.webp 1024w, https://static.digihunch.com/wp-content/uploads/2020/05/swarm-node-300x114.webp 300w, https://static.digihunch.com/wp-content/uploads/2020/05/swarm-node-768x291.webp 768w, https://static.digihunch.com/wp-content/uploads/2020/05/swarm-node-1536x582.webp 1536w, https://static.digihunch.com/wp-content/uploads/2020/05/swarm-node-2048x777.webp 2048w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The atomic unit of scheduling on a swarm is the service. When a container is wrapped in a service, we call it a task or a replica, and the service construct adding things like scaling, rolling updates and simple rollbacks.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To initialize a swarm, we need to have the following ports open. Then we can initialize the first manager node, join additional manager nodes, and then join workers.&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;2377/tcp: for secure client-to-swarm communication&lt;/li&gt;&#10;&lt;li&gt;7946/tcp &amp;amp; udp: for control plane gossip&lt;/li&gt;&#10;&lt;li&gt;4789/udp: for VXLAN-based overlay networks&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;A Docker node can exist either in single-engine mode as stand alone, or in swarm mode as part of a swarm. Service only exist in swarm mode. Running docker swarm init on a Docker host in single-engine mode will switch that node into swarm mode, create a new swarm, and make the node the first manager of the swarm. Then additional nodes can be joined as managers or workers.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Swarm managers have native support for high availability, through an active-passive, multi-manager HA. Only one manager is considered active (the leader), which is the only one that will ever issue live commands against the swarm. If a passive manager receives commands for the swarm, it proxies them across to the leader.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Managers are either leaders or followers. This is Raft terminalogy because swarm uses an impelementation of the Raft consensus althorithm to power manager HA. As to HA, the following two best practices apply:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;deploy an odd number of managers&lt;/li&gt;&#10;&lt;li&gt;don&amp;#8217;t deploy too many managers (3 or 5 recommended, never more than 7)&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Having an odd number of managers reduces the chances of split-brain conditions. Having less than 7 managers ensures that achieving consensus is quick.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With a service, we can specify name, port mappings, network to attach to, and images, as well as desired state for an application service. It is recommended in production environment to use docker-compose template to specify service. Services have replication mode, and the default is replicated. This will deploy a desired number of replicas and distribute them as evenly as possible across the cluster. The other mode is global, which runs a single replica on every node in the swarm.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Running &amp;#8220;docker service scale&amp;#8221; command can scale the number of service replicas from 5 to 10, which in the background updates the service&amp;#8217;s desired state to the newly specified number of replicas. Behind the scenes, Swarm also runs a scheduling algorithm that defaults to balancing replicas as evenly as possible across the node in the swarm. Docker makes it super easy to push updates to deployed applications. With rolling update, you may specify number of replicas to update at a time, and cool-off period per update.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;a class="rank-math-link" href="https://upcloud.com/community/stories/docker-swarm-vs-kubernetes-comparison-of-the-two-giants-in-container-orchestration/"&gt;Here&lt;/a&gt; is a great article on the difference between Docker Swarm and Kubernetes.&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/05/revamp-ansible-directory-for-scalability-1-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Ansible at scale 1 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2020/05/ansible-directory-for-scalability-2-of-2/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Ansible at scale 2 of 2&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>