<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Metrics on Digi Hunch</title><link>https://static.digihunch.com/tag/metrics/</link><description>Recent content in Metrics on Digi Hunch</description><generator>Hugo -- gohugo.io</generator><language>en-US</language><lastBuildDate>Wed, 02 Apr 2025 13:59:03 -0400</lastBuildDate><atom:link href="https://static.digihunch.com/tag/metrics/index.xml" rel="self" type="application/rss+xml"/><item><title>Kubernetes Operator</title><link>https://static.digihunch.com/2022/04/kubernetes-operator/</link><pubDate>Thu, 07 Apr 2022 09:39:00 -0400</pubDate><guid>https://static.digihunch.com/2022/04/kubernetes-operator/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-operator.webp" alt="Featured image of post Kubernetes Operator" /&gt;&lt;p class="wp-block-paragraph"&gt;Kubernetes has a number of tools to automate the deployment of a single workload. In previous posts, we had covered &lt;a href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;Helm&lt;/a&gt; and &lt;a href="https://static.digihunch.com/2022/01/fluxcd-continuous-deployment-with-gitops/"&gt;Kustomize&lt;/a&gt;. What are left unresolved is how to maintain the status of workload after deployment is completed. In this post, I will give an introduction to Kubernetes Operator. Compared with Helm (templating approach) and Kustomize (patching approach), Kubernetes Operator follows the &lt;a href="https://kubernetes.io/docs/concepts/extend-kubernetes/operator/"&gt;operator pattern&lt;/a&gt;. Operators are usually provided by the developer of the application.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading" id="h-operator-pattern"&gt;Operator Pattern&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In Kubernetes, we know that a controller takes care of routine tasks to ensure that desired state expressed by Kubernetes resource types matches the current state. One example is that the Deployment controller ensures the number of pods running matches the amount specified in the replica field. Controller is the key to ensure that resources can be managed by declarative manifests for Kubernetes resources. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Kubernetes makes use of controller pattern throughout its own design. One of its key component, Controller Manager, is a collection of many controllers. Each controller is in charge of a control loop, responsible for listening the object it manages. Another component, Kube-scheduler, is also a special type of Controller. The kube-scheduler monitors unscheduled Pod and health of nodes and determines the best Node to schedule the new Pod to. Then it writes the decision to etcd store for kubelet to execute.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;This controller pattern is fairly successful in what it does and we can extend the use of it. Beyond the built-in resource types, we can create our own custom resource definitions (CRDs), and create controllers that watches for the manifest that declares custom resources (CRs). The controller ensures that the resource status matches their specifications. This is also known as reconciliation, which is implemented as a control loop. Operator pattern can be illustrated in the diagram below:&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://github.com/cncf/tag-app-delivery/raw/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/img/02_1_operator_pattern.png" alt="Operator Design Pattern"/&gt;&lt;figcaption class="wp-element-caption"&gt;Operator Pattern&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Technically, there is no difference between a controller and an operator. What makes an Operator (used to install workload) different than a native Kubernetes controller, are two things. First, an Operator usually needs CRDs because the built-in resource types are insufficient. Second, the operator reflects the domain knowledge to keep the target workload running. For example, stateful workloads such as database needs their operational steps executed in certain orders.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;On &lt;a href="https://github.com/cncf/tag-app-delivery/blob/eece8f7307f2970f46f100f51932db106db46968/operator-wg/whitepaper/Operator-WhitePaper_v1-0.md"&gt;Operator Pattern&lt;/a&gt;, CNCF published a &lt;a href="https://www.cncf.io/wp-content/uploads/2021/07/CNCF_Operator_WhitePaper.pdf"&gt;whitepaper&lt;/a&gt; with a deeper review. This white paper is the best reference for a good understanding of the Operator Pattern.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Custom Resource Definition&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The built-in controllers work with built-in objects (pre-defined APIs). Custom operators usually need their own APIs to function. To extend Kubernetes API, we define the schema of these APIs in the form of CRDs (&lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation-rules"&gt;custom resource definitions&lt;/a&gt;) using &lt;a href="https://kubernetes.io/docs/tasks/extend-kubernetes/custom-resources/custom-resource-definitions/#validation"&gt;OpenAPIv3&lt;/a&gt; standard. Then, we can declare Custom Resources (CRs) in compliance with the schema. The OpenAPIv3 schema in the CRD resource tells validating web hook (&lt;a href="https://static.digihunch.com/2022/01/kubernetes-admission-control/"&gt;admission control&lt;/a&gt;) how to validate the schema when we send an CR in to API server.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When we work with third-party operators, they usually provide CRDs along with the operator implementation. For example, in my &lt;a href="https://github.com/digihunch/wordpress-operator"&gt;operator example&lt;/a&gt; project, we have a minimalist CRD &lt;a href="https://github.com/digihunch/wordpress-operator/blob/main/config/crd/bases/wordpress.digihunch.com_wordpresses.yaml"&gt;WordPress&lt;/a&gt; with one property: sqlRootPassword and we can declare a CR as in &lt;a href="https://github.com/digihunch/wordpress-operator/blob/main/config/samples/wordpress_v1_wordpress.yaml"&gt;this&lt;/a&gt; example. For a more realistic use case, we can take a look at &lt;a href="https://github.com/kiali/kiali-operator/blob/master/crd-docs/crd/kiali.io_kialis.yaml"&gt;Kiali CRD&lt;/a&gt;. In the next section, we&amp;#8217;ll use it along with Kiali operator to install Kiali. &lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operator Usage&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Like &lt;a href="https://artifacthub.io/"&gt;Artifact Hub&lt;/a&gt; to Helm, &lt;a href="https://operatorhub.io/"&gt;OperatorHub&lt;/a&gt; is a public registry of most used Kubernetes Operators. In this section, we will take an example of using Operators. We will install Kiali as an add-on to Istio using Kiali CR and operator, which also depends on Prometheus to be installed using Prometheus Operator first. Note that the Kiali installation outlined in this section is not the the &lt;a href="https://istio.io/latest/docs/ops/integrations/kiali/#option-1-quick-start"&gt;quick-start&lt;/a&gt; install manifests from Istio&amp;#8217;s &lt;a href="https://github.com/istio/istio/tree/master/samples/addons"&gt;sample&lt;/a&gt; directory. For Kiali on production system we have to customize the &lt;a href="https://kiali.io/docs/installation/installation-guide/"&gt;installation&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose we have installed Istio, we can then install Prometheus operator using Helm. The Prometheus operator will install Prometheus. Then we use Helm again to install Kiali operator. The Kiali operator will watch for creation of Kiali CRD, to deploy services:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ helm install -f prometheus-values.yaml --namespace istio-system --repo https://prometheus-community.github.io/helm-charts --version 13.6.0 istio-prometheus prometheus --insecure-skip-tls-verify&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ helm install -f kiali-operator-values.yaml --namespace kiali-operator --repo https://kiali.org/helm-charts --version 1.45.0 kiali-op kiali-operator --create-namespace&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl apply -f kiali-cr.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;I include example content for each file in the commands above on Github gist (&lt;a href="https://gist.github.com/digihunch/448180c019310a5dadb700c1bcdb0772"&gt;prometheus-values.yalm&lt;/a&gt;, &lt;a href="https://gist.github.com/digihunch/5574aba4aa9fc1aa15257bd6e811bf5b"&gt;kiali-operator-values.yaml&lt;/a&gt; and &lt;a href="https://gist.github.com/digihunch/2fd0884f5999416c8baf4197ee5790f3"&gt;kiali-cr.yaml&lt;/a&gt;). For more options for installing Kiali, refer to &lt;a href="https://kiali.io/docs/installation/installation-guide/install-with-helm/"&gt;their&lt;/a&gt; documentation.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use this example to install Kiali and it includes two Operators, the Prometheus Operator and the Kiali Operator. The Prometheus Operator is one of the first ever written Kubernetes Operator. As soon as the operator is deployed, it starts to deploy the operator service. For the Kiali operator, we need to deploy Kiali CR after the Kiali Operator has been deployed. Both are valid patterns.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Operator Development&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Operator is powerful. However, authoring an Operator is not a trivial effort. One usually start with a framework. A framework creates a body of boiler plate code that has the pattern implemented and allows developers to enrich the functions following the pattern. The white paper introduced three frameworks:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;CNCF &lt;a href="https://operatorframework.io/"&gt;Operator Framework&lt;/a&gt; &amp;#8211; aims at Operator Developers with an SDK, a scaffolding tool and a test harness. It currently supports three project types: Golang, Helm and Ansible. CNCF Operator framework consists of SDK and OLM. &lt;/li&gt;&#10;&lt;li&gt;Kopf (Kubernetes Operator Pythonic Framework) &amp;#8211; an easy-to-use framework in Python that abstracts away most of the low-level Kubernetes API communications hassle.&lt;/li&gt;&#10;&lt;li&gt;kubebuilder &amp;#8211; helps build a Manager similar to the native kube-controller-manager. For difference with OperatorSDK, read &lt;a href="https://sdk.operatorframework.io/docs/faqs/#what-are-the-the-differences-between-kubebuilder-and-operator-sdk"&gt;here&lt;/a&gt;.&lt;/li&gt;&#10;&lt;li&gt;Metacontroller: lightweight Kubernetes Controller as a Service&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In &lt;a href="https://www.cncf.io/projects/operator-framework/"&gt;CNCF&lt;/a&gt; Operator Framework, the Operator SDK supports development using &lt;a href="https://sdk.operatorframework.io/docs/building-operators/ansible/"&gt;Ansible&lt;/a&gt;, &lt;a href="https://sdk.operatorframework.io/docs/building-operators/helm/"&gt;Helm&lt;/a&gt; and &lt;a href="https://sdk.operatorframework.io/docs/building-operators/"&gt;Golang&lt;/a&gt;. The author of &lt;a href="https://www.velotio.com/engineering-blog/getting-started-with-kubernetes-operators-helm-based-part-1"&gt;this&lt;/a&gt; post makes a general comparison as follows:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Type &lt;/th&gt;&lt;th&gt;Best use case&lt;/th&gt;&lt;th&gt;Underlying technology&lt;/th&gt;&lt;th&gt;Amt of Effort&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Helm&lt;/td&gt;&lt;td&gt;Stateless workload&lt;/td&gt;&lt;td&gt;Helm Charts&lt;/td&gt;&lt;td&gt;Med&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Ansible&lt;/td&gt;&lt;td&gt;Stateless workload&lt;/td&gt;&lt;td&gt;Ansible Roles and Playbooks&lt;/td&gt;&lt;td&gt;Med&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Golang&lt;/td&gt;&lt;td&gt;Stateful workload&lt;/td&gt;&lt;td&gt;Code developed in Golang&lt;/td&gt;&lt;td&gt;High&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The aforementioned &lt;a href="https://github.com/kiali/kiali-operator"&gt;Kiali operator&lt;/a&gt; is an example of Operator developed in Ansible. The &lt;a href="https://github.com/prometheus-operator/prometheus-operator"&gt;prometheus operator&lt;/a&gt;, is developed in Golang as the workload can be stateful depending on configuration. One needs to know how to develop operator in Golang in order to tackle the most complicated situations. This is requires some serious development effort. The documentation with a quick start section is available &lt;a href="https://sdk.operatorframework.io/docs/building-operators/golang/quickstart/"&gt;here&lt;/a&gt;. Even that is not very straightforward. RedHat, the maintainer of the CNCF &lt;a href="https://cloud.redhat.com/learn/topics/operators"&gt;Operator&lt;/a&gt; framework has a good blog &lt;a href="https://developers.redhat.com/articles/2021/08/04/managing-stateful-applications-kubernetes-operators-golang#"&gt;post&lt;/a&gt; on how to develop an Operator in Golang. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The example requires some development knowledge to go through. On my MacOS (Intel) I have to configure the following prerequisites:&lt;/p&gt;&#10;&lt;ol class="wp-block-list"&gt;&#10;&lt;li&gt;Install gcc, using command: xcode-select &amp;#8211;install&lt;/li&gt;&#10;&lt;li&gt;Install the right version of golang. You can find the version &lt;a href="https://sdk.operatorframework.io/docs/contribution-guidelines/developer-guide/#prerequisites"&gt;here&lt;/a&gt;. The MacOS has a version of golang installed already so I had to install version 1.17 and link to it: brew install go@1.17 &amp;amp;&amp;amp; brew link &amp;#8211;force go@1.17&lt;/li&gt;&#10;&lt;li&gt;Install operator-sdk with home brew: brew install operator-sdk&lt;/li&gt;&#10;&lt;li&gt;When you run &amp;#8220;operator-sdk version&amp;#8221;, ensure the result shows a golang version that matches your installation.&lt;/li&gt;&#10;&lt;li&gt;If you need to push docker image, also connect to docker registry by running: docker login&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can create our working directory, initialize the repository and create boilerplate code (scaffolding) with these commands:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ mkdir wordpress-operator &lt;span style="color:#f92672"&gt;&amp;amp;&amp;amp;&lt;/span&gt; cd wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ operator-sdk init --domain digihunch.com --repo github.com/digihunch/wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ operator-sdk create api --group wordpress --version v1 --kind WordPress --resource --controller&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;With the repo initialized, we can go to the section &amp;#8220;Defining the API&amp;#8221; and &amp;#8220;Implementing the Controller&amp;#8221;. The blog post does not cover every code editing needed to bring up wordpress. You are supposed to go to the author&amp;#8217;s &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest"&gt;repository&lt;/a&gt; to fit the changes into your own repo. The author&amp;#8217;s repo has a few more &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/tree/master/controllers"&gt;controllers&lt;/a&gt; such as &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/blob/master/controllers/common.go"&gt;common.go&lt;/a&gt; and &lt;a href="https://github.com/priyanka19-98/wordpress-operator-latest/blob/master/controllers/mysql.go"&gt;mysql.go&lt;/a&gt;. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;At the end of the lab, you should be able to run the controller and bring up wordpress. I used my own &lt;a href="https://github.com/digihunch/wordpress-operator"&gt;repository&lt;/a&gt; for this lab and have made the code changes for this lap in a couple &lt;a href="https://github.com/digihunch/wordpress-operator/commit/5540d7e045bf4da1ea1d140f1b9fd189fd9f2cc9"&gt;commits&lt;/a&gt;. To test locally with the code:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ git clone git@github.com:digihunch/wordpress-operator.git&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ cd wordpress-operator&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ make install run&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can validate wordpress install from a new terminal as the instruction shows:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ kubectl create -f config/samples/wordpress_v1_wordpress.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;$ minikube service wordpress --url&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;For Developers that requires more details, RedHat has an &lt;a href="https://www.redhat.com/cms/managed-files/cl-oreilly-kubernetes-operators-ebook-f21452-202001-en_2.pdf?extIdCarryOver=true&amp;amp;sc_cid=701f2000001Css5AAC"&gt;eBook&lt;/a&gt; for Kubernetes Operators, in supplement to the &lt;a href="https://cloud.redhat.com/learn/topics/operators"&gt;documentation&lt;/a&gt;. As DevOps professional, I&amp;#8217;m mainly concerned with understanding how Operator works and using Operators correctly.&lt;/p&gt;&#10;&lt;h2 class="wp-block-heading"&gt;Too many Tools?&lt;/h2&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Now we seem to have too many choice of tools when it comes to deploying workload on Kubernetes. Kustomize and Helm can deploy simple workloads. Operator can deploy stateful workloads, as well as keep the workload status in check. Further, we have FluxCD and ArgoCD based on GitOps workflow.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When assessing a tool, we should think about the complexity of the workload deployed. If it is a single stateless workload, Kustomize or Helm should be sufficient. If it is not very simple but still stateless, we can consider using Helm charts developed by the community. For multiple workloads, we can build our own top-level chart to combine existing sub-charts created by the community.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Helm is essentially a package manager. It does not follow controller pattern and therefore will not monitor the current status of deployment. Helm has other limitations compared to Operator. For example, as a templating scheme, it reaches limitation when dealing with complex logic, even with the help of its helper functions. It is also hard to reason through the template code when we have to troubleshoot a deployment. Refer to &lt;a href="https://thenewstack.io/we-pushed-helm-to-the-limit-then-built-a-kubernetes-operator/"&gt;this&lt;/a&gt; blog post for the author&amp;#8217;s experience with Helm.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;If we want our deployment to be fully declarative and continuous, then we will follow the Operator pattern by using a Kubernetes Operator. When we have many workloads of different levels of complexity, we can combine them with GitOps tool. Operator is one of the underlying technologies behind GitOps.&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background has-fixed-layout"&gt;&lt;thead&gt;&lt;tr&gt;&lt;th&gt;Workload profile&lt;/th&gt;&lt;th&gt;Just Installation&lt;/th&gt;&lt;th&gt;Installation and Maintain Status&lt;/th&gt;&lt;/tr&gt;&lt;/thead&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;Single stateless workload&lt;/td&gt;&lt;td&gt;Helm or Kustomize&lt;/td&gt;&lt;td&gt;Operator (using Ansible or Helm)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Single stateful workload&lt;/td&gt;&lt;td&gt;Helm or Kustomize&lt;/td&gt;&lt;td&gt;Operator (using Golang)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Multiple workloads&lt;/td&gt;&lt;td&gt;Helm (e.g. build parent chart)&lt;/td&gt;&lt;td&gt;GitOps in combination with Operator, Helm and Kustomize&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The table above helps refine deployment requirement. It&amp;#8217;s not a recommendation, but rather a model of analyzing deployment requirement.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2022/03/autoscaling-in-kubernetes-from-metric-based-to-event-driven/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Autoscaling on Kubernetes Platform&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2022/04/knative-introduction-serving/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Knative Serving Introduction&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Logging and Monitoring in Kubernetes with PLG stack</title><link>https://static.digihunch.com/2021/10/logging-and-monitoring-in-kubernetes-with-plg-stack/</link><pubDate>Wed, 13 Oct 2021 21:29:00 -0400</pubDate><guid>https://static.digihunch.com/2021/10/logging-and-monitoring-in-kubernetes-with-plg-stack/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-k8s-plg.webp" alt="Featured image of post Logging and Monitoring in Kubernetes with PLG stack" /&gt;&lt;p class="wp-block-paragraph"&gt;We&amp;#8217;ve checked out the the actors in PLG stack (Promtail, Loki, Node Exporter, Prometheus, Grafana) and whipped up a quick pipeline on MacOS. Now I&amp;#8217;m going a little further to implement the same PLG stack (Prometheus Loki and Grafana) in a Kubernetes cluster. This setup is for demo only, therefore no persistent storage is enabled.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-test-workload"&gt;Test Workload&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I host a deployment of Flog with three pods running on Minikube. Flog is an open-source emulating log generation behaviour of an application. On the Minikube cluster we start the deployment as below:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;minikube start --driver&lt;span style="color:#f92672"&gt;=&lt;/span&gt;hyperkit --container-runtime&lt;span style="color:#f92672"&gt;=&lt;/span&gt;containerd --memory&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;12288&lt;/span&gt; --cpus&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl create ns obsv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv create deployment flog --image&lt;span style="color:#f92672"&gt;=&lt;/span&gt;mingrammer/flog --replicas&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt; -- flog -f rfc3164 -l -d 300ms&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv get po&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The Pods will come up in a heartbeat. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I will use &lt;a href="https://static.digihunch.com/2021/07/helm-configuration-management-for-kubernetes-resources/"&gt;helm&lt;/a&gt; to install the objects required for logging and metrics pipelines. There are multiple Helm charts for each components. Some high-level charts (usually with a name suffix of -stack) contain several other resource as sub-charts. They are created as one-stop-shop for multiple components but I found none of them serve my exact purpose. For example, both &lt;a href="https://artifacthub.io/packages/helm/grafana/loki-stack"&gt;loki-stack&lt;/a&gt; and &lt;a href="https://artifacthub.io/packages/helm/prometheus-community/kube-prometheus-stack"&gt;kube-prometheus-stack&lt;/a&gt; include Grafana. But I only need one instance of Grafana. Therefore I stick to the low-level charts.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt; &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-log-shipping"&gt;Log Shipping&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Add a helm repo, and install loki and promtail. Note that we need to specify correct loki address when installing Promtail. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add grafana https://grafana.github.io/helm-charts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo update&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade --namespace obsv --install loki grafana/loki&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade --namespace obsv --install promtail grafana/promtail --set &lt;span style="color:#e6db74"&gt;&amp;#34;config.lokiAddress=http://loki.obsv.svc.cluster.local:3100/loki/api/v1/push&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;When installing Promtail, a DaemonSet is created on the Node. The default configuration applies appropriate configuration and tagging strategy for Kubernetes Pod and Node. So the only customization I specified is Loki address. We can then check logging with Loki. To do so, first expose port 3100 to host, and then use logcli (e.g. on MacOS) to query for logs:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv port-forward service/loki 3100:3100&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli labels&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli labels pod&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli query &lt;span style="color:#e6db74"&gt;&amp;#39;{pod=&amp;#34;flog-775d5fc5c8-p4rlx&amp;#34;}&amp;#39;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Log lines should be pumped to Loki a minute after Loki comes up. The logcli labels command should display the tags. The logcli query command should return the log lines. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-metrics"&gt;Metrics&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;I use Premetheus with node exporter. In its &lt;a href="https://prometheus.io/docs/introduction/overview/#architecture"&gt;architecture&lt;/a&gt;, Prometheus contain the server, the pushgateway, and alertmanager. The &lt;a href="https://github.com/prometheus-community/helm-charts"&gt;helm chart&lt;/a&gt; for &lt;a href="https://artifacthub.io/packages/helm/prometheus-community/prometheus"&gt;Prometheus&lt;/a&gt; contains all of those components. It also has a dependency repo for &lt;a href="https://artifacthub.io/packages/helm/prometheus-community/kube-state-metrics"&gt;kube-state-metrics&lt;/a&gt;. To install:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add prometheus-community https://prometheus-community.github.io/helm-charts&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm repo add kube-state-metrics https://kubernetes.github.io/kube-state-metrics&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm --namespace&lt;span style="color:#f92672"&gt;=&lt;/span&gt;obsv install prometheus prometheus-community/prometheus&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl -n obsv get svc&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Once installed, a DaemonSet for Prometheus Node Exporter is created. The exporter is already configured by default for Kubernetes monitoring. The Prometheus server is also configured, on port 80 by default. it needs to be forwarded in order to access from Browser:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl --namespace obsv port-forward service/prometheus-server 9100:80&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To verify installation of Prometheus, browse to localhost:9100 to examine the metrics.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-visualization"&gt;Visualization&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Last but not least, I will configure Grafana. The repo has been added already so we&amp;#8217;ll just install the chart:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm upgrade --namespace obsv --install grafana grafana/grafana&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl get secret --namespace obsv grafana -o jsonpath&lt;span style="color:#f92672"&gt;=&lt;/span&gt;&lt;span style="color:#e6db74"&gt;&amp;#34;{.data.admin-password}&amp;#34;&lt;/span&gt; | base64 --decode ; echo&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The second command retrieves the credential. To access the web portal, we need port forwarding again:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;kubectl port-forward --namespace obsv service/grafana 3000:80&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;To verify installation, browse to http://localhost:3000 and log in as user admin with the password above. Then add two data sources:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Type: Prometheus, URL: http://prometheus-server.obsv.svc.cluster.local:80&lt;/li&gt;&#10;&lt;li&gt;Type: Loki, URL: http://loki.obsv.svc.cluster.local:3100&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Then we can explore data using both data sources.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://d33wubrfki0l68.cloudfront.net/0862f7967545b9ebe1041764e9427a8bf0f44a08/6b8ba/assets/img/uploads/2020/04/image2.png" alt="grafana workflow"/&gt;&lt;figcaption class="wp-element-caption"&gt;PLG stack&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;h3 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In the last two posts I reviewed the setups for PLG stack in Kubernetes, from a regular environment to k8s cluster. Fluentd, Prometheus are both CNCF projects. The PLG stack seems to be more adopted than EFK but both have their own advantages. Welcome to the PLG vs EFK debate.&lt;/p&gt;&#10;&lt;nav class="wp-post-navigation" aria-label="Post navigation"&gt;&#10;&lt;a rel="prev" href="https://static.digihunch.com/2021/10/intro-to-plg-stack-prometheus-loki-and-grafana/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Intro to PLG stack -Prometheus, Loki and Grafana&lt;/strong&gt;&lt;/a&gt;&#10;&lt;a rel="next" href="https://static.digihunch.com/2021/10/notes-on-azure/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Azure Deets&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Intro to PLG stack -Prometheus, Loki and Grafana</title><link>https://static.digihunch.com/2021/10/intro-to-plg-stack-prometheus-loki-and-grafana/</link><pubDate>Sun, 03 Oct 2021 12:59:00 -0400</pubDate><guid>https://static.digihunch.com/2021/10/intro-to-plg-stack-prometheus-loki-and-grafana/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-plg-intro.webp" alt="Featured image of post Intro to PLG stack -Prometheus, Loki and Grafana" /&gt;&lt;p class="wp-block-paragraph"&gt;Last month we discussed &lt;a href="https://static.digihunch.com/2021/09/log-shipping-in-kubernetes-with-efk/"&gt;log shipping with EFK&lt;/a&gt;. This week I spent sometime checking out its alternative Loki. Having been exposed to the &lt;a href="https://static.digihunch.com/2018/09/log-shipping-through-elk/"&gt;ELK stack&lt;/a&gt; extensively, I am also interested in exploring the counterparts in this new stack, such as Prometheus, Loki and Grafana. So I need to address the issues of shipping both metrics and logs.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Let&amp;#8217;s start by clarifying the terms:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Grafana&lt;/strong&gt; is a visualizer. It supports many backends such as Prometheus, Loki, Elasticsearch, CloudWatch and Azure Monitor. It is the flagship product of Grafana Labs.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Prometheus&lt;/strong&gt; is a time-series database and alerting platform. To push metrics to Premethus, you can either integrate your application with client library (in their term, &lt;em&gt;instrumenting&lt;/em&gt;), or configure an existing &lt;a href="https://prometheus.io/docs/instrumenting/exporters/"&gt;exporters&lt;/a&gt; for a third party application such as PostgreSQL. Prometheus &lt;em&gt;collects&lt;/em&gt; and &lt;em&gt;stores&lt;/em&gt; its metrics as time series data ( i.e. metrics information is stored with the timestamp at which it was recorded, alongside optional key-value pairs called labels) and it comes with basic visualization capability. Premetheus is a CNCF project since 2016 and is maintained by Grafana Labs.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Loki&lt;/strong&gt; is a log aggregation system, also developed by Grafana Labs. Loki does not index the contents of the logs. Instead it groups entries into streams, and indexes a set of labels for each log stream. You can use grafana or logcli to consume the logs. Loki supports clients such as Fluentd, Fluentbit, Logstash and Promtail.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;&lt;strong&gt;Promtail&lt;/strong&gt; is a log collection agent built for Loki.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The roles of the components such as Prometheus, Loki, Grafana and Promtail are similar to the ELK stack. Grafana resembles Kibana. Promtail resembles Filebeat. Premethus exporters resemble Metricbeat. Both Premetheus and Loki resemble Elasticsearch in some aspects. Premetheus keeps metrics and Loki persists log streams. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;These tools are heavily used in Kubernetes. For a simple start, I&amp;#8217;d like to just configure two minimally working pipelines on my MacBook without any containerization. My example setup is to achieve the followings:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;Fake up some log lines and ship them to Loki&lt;/li&gt;&#10;&lt;li&gt;Ship OS metrics to Prometheus&lt;/li&gt;&#10;&lt;li&gt;Display the metrics and logs with Grafana&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Here&amp;#8217;s the diagram of what we want to implement:&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;&lt;svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="491px" viewBox="-0.5 -0.5 491 106" style="max-width:100%;max-height:106px;"&gt;&lt;defs&gt;&lt;/defs&gt;&lt;g&gt;&lt;rect x="0" y="0" width="490" height="105" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;path d="M 206.37 30 L 220 30 L 210 30 L 223.63 30" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 201.12 30 L 208.12 26.5 L 206.37 30 L 208.12 33.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 228.88 30 L 221.88 33.5 L 223.63 30 L 221.88 26.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="110" y="15" width="90" height="30" rx="4.5" ry="4.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: 88px; height: 1px; padding-top: 30px; margin-left: 111px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Promtail&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="155" y="34" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Promtail&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 206.37 70 L 220 70 L 210 70 L 223.63 70" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 201.12 70 L 208.12 66.5 L 206.37 70 L 208.12 73.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 228.88 70 L 221.88 73.5 L 223.63 70 L 221.88 66.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="110" y="55" width="90" height="30" rx="4.5" ry="4.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: 88px; height: 1px; padding-top: 70px; margin-left: 111px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Node Exporter&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="155" y="74" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Node Exporter&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 326.37 30 L 340 30 L 340 50 L 353.63 50" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 321.12 30 L 328.12 26.5 L 326.37 30 L 328.12 33.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 358.88 50 L 351.88 53.5 L 353.63 50 L 351.88 46.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="230" y="15" width="90" height="30" rx="4.5" ry="4.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: 88px; height: 1px; padding-top: 30px; margin-left: 231px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Loki&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="34" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Loki&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 326.37 70 L 340 70 L 340 50 L 353.63 50" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 321.12 70 L 328.12 66.5 L 326.37 70 L 328.12 73.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;path d="M 358.88 50 L 351.88 53.5 L 353.63 50 L 351.88 46.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="230" y="55" width="90" height="30" rx="4.5" ry="4.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: 88px; height: 1px; padding-top: 70px; margin-left: 231px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Prometheus&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="275" y="74" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Prometheus&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;rect x="360" y="35" width="90" height="30" rx="4.5" ry="4.5" fill="#fff2cc" stroke="#d6b656" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 88px; height: 1px; padding-top: 50px; margin-left: 361px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Grafana&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="405" y="54" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Grafana&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 80 30 L 100 30 L 90 30 L 103.63 30" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 108.88 30 L 101.88 33.5 L 103.63 30 L 101.88 26.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="10" y="15" width="70" height="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 30px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Log File&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="34" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Log File&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;path d="M 80 70 L 100 70 L 90 70 L 103.63 70" fill="none" stroke="#000000" stroke-miterlimit="10" pointer-events="stroke"&gt;&lt;/path&gt;&lt;path d="M 108.88 70 L 101.88 73.5 L 103.63 70 L 101.88 66.5 Z" fill="#000000" stroke="#000000" stroke-miterlimit="10" pointer-events="all"&gt;&lt;/path&gt;&lt;rect x="10" y="55" width="70" height="30" fill="#ffffff" stroke="#000000" pointer-events="all"&gt;&lt;/rect&gt;&lt;g transform="translate(-0.5 -0.5)"&gt;&lt;switch&gt;&lt;foreignObject style="overflow: visible; text-align: left;" pointer-events="none" width="100%" height="100%" requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;div xmlns="http://www.w3.org/1999/xhtml" style="display: flex; align-items: unsafe center; justify-content: unsafe center; width: 68px; height: 1px; padding-top: 70px; margin-left: 11px;"&gt;&lt;div style="box-sizing: border-box; font-size: 0; text-align: center; "&gt;&lt;div style="display: inline-block; font-size: 12px; font-family: Helvetica; color: #000000; line-height: 1.2; pointer-events: all; white-space: normal; word-wrap: normal; "&gt;Collectors&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;/foreignObject&gt;&lt;text x="45" y="74" fill="#000000" font-family="Helvetica" font-size="12px" text-anchor="middle"&gt;Collectors&lt;/text&gt;&lt;/switch&gt;&lt;/g&gt;&lt;/g&gt;&lt;switch&gt;&lt;g requiredFeatures="http://www.w3.org/TR/SVG11/feature#Extensibility"&gt;&lt;/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;This is simple enough to build a quick and dirty setup&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-node-exporter"&gt;Configure Node Exporter&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can use home brew to install node_exporter on MacOS. For Linux, use the equivalent package management tool:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install node_exporter&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew services start node_exporter&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl http://localhost:9100/metrics | grep &lt;span style="color:#e6db74"&gt;&amp;#34;node_&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;The node_exporter collect metrics and make it available for scrape using port 9100 as shown above. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-prometheus"&gt;Configure Prometheus&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Install Prometheus with homebrew and start the service:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install prometheus&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew services start prometheus&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;&lt;meta charset="utf-8"&gt;If curl to port 9090 with GET returns &amp;#8220;Found&amp;#8221;, then Prometheus is successfully installed. We also want to configure it so it scrapes node exporter for metrics. Edit the configuration file, in my case, /usr/local/etc/prometheus.yml, by adding the followings:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-yaml" data-lang="yaml"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;job_name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;node&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;static_configs&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - &lt;span style="color:#f92672"&gt;targets&lt;/span&gt;: [&lt;span style="color:#e6db74"&gt;&amp;#39;localhost:9100&amp;#39;&lt;/span&gt;]&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;This tells Prometheus to scrape metrics at specified interval. Restart Prometheus and browse to http://localhost:9090/ for Prometheus UI. Click on Status -&amp;gt; Targets and you can see the node as an export. Click on Graph and Execute a query for example &amp;#8220;node_memory_free_bytes&amp;#8221; and click on Graph. You should see a plot of the metric value.&lt;/p&gt;&#10;&lt;figure class="wp-block-image size-large"&gt;&lt;img loading="lazy" decoding="async" width="1057" height="801" src="https://static.digihunch.com/wp-content/uploads/2021/10/image-1.png" alt="" class="wp-image-2759"/&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We can later configure to display the chart in Grafana.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-generate-log-lines"&gt;Generate log lines &lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use a tool called &lt;a href="https://hub.docker.com/r/mingrammer/flog"&gt;flog&lt;/a&gt; to generate fake logs:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew tap mingrammer/flog&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install flog&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;flog -f rfc3164 -l -d 300ms -t log -o /tmp/test.log -w&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The command above produces log in RFC3164 format to /tmp/test.log, one line every 300ms. The log does not rotate.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-loki"&gt;Configure Loki&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Install Loki with homebrew and start the service. We want to configure Loki before Promtail so it is ready to receive logs. You might as well install LogCLI to interact with Loki.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install loki&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install logcli&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew services start loki&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If nc to port 3100 returns success, then Loki is successfully installed. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-promtail"&gt;Configure Promtail&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Install promtail using homebrew:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install promtail&#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 Promtail is not installed as a &lt;a href="https://github.com/Homebrew/homebrew-services"&gt;homebrew&lt;/a&gt; service. Although it can be manually configured as a service, I&amp;#8217;d rather stay focused and use command line just for the time being. A copy of configuration file is located in /usr/local/etc/promtail-local-config.yaml but it needs to be modified first with a job to tell it where to scrap the log lines. The configuration looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;server:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; http_listen_port: &lt;span style="color:#ae81ff"&gt;9080&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; grpc_listen_port: &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;positions:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; filename: /tmp/positions.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;clients:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - url: http://localhost:3100/loki/api/v1/push&#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;scrape_configs:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;- job_name: app&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; static_configs:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - targets:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; - localhost&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; labels:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; job: applogs&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; __path__: /tmp/test.log&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then, use the command as in the &lt;a href="https://grafana.com/docs/loki/latest/getting-started/get-logs-into-loki/"&gt;documentation&lt;/a&gt; to start promtail:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;promtail -config.file /usr/local/etc/promtail-local-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;Port 9080 will be open when Promtail is running. Promtail will push logs to Loki. You can use &lt;a href="https://grafana.com/docs/loki/latest/getting-started/logcli/"&gt;LogCLI&lt;/a&gt; to interact with Loki and see the latest log lines pushed to Loki. &lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;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;logcli labels job&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;logcli query &lt;span style="color:#e6db74"&gt;&amp;#39;{job=&amp;#34;applogs&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;However, unlike Prometheus, Loki itself does not have any visualization capabilities. We will need Grafana to display the log nicely on the web.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-configure-grafana"&gt;Configure Grafana&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We use Grafana to visualize both the logs and metrics. To install Grafana on Mac:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew install grafana&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;brew services start grafana&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If curl to port 3000 with GET returns &amp;#8220;Found&amp;#8221;, then Grafana is successfully installed. Browse to localhost:3000, with default credential admin and admin. From the UI, add two data sources. For the first data source, specify Prometheus as type and localhost:9000 as destination. For the second, specify Loki as the destination http://localhost:3100&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The steps to see logs in Loki is pretty much the same as on &lt;a href="https://grafana.com/docs/loki/latest/getting-started/grafana/"&gt;this&lt;/a&gt; page of its documentation.&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="1394" height="1055" src="https://static.digihunch.com/wp-content/uploads/2021/10/image-3.png" alt="" class="wp-image-2766"/&gt;&lt;figcaption class="wp-element-caption"&gt;Exploring logs&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Exploring metrics data is similar. Click on Explore on the side bar, then select Prometheus from the dropdown as data source. Then execute a query such as &amp;#8220;&lt;meta charset="utf-8"&gt;node_memory_free_bytes&amp;#8221;.&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" src="https://static.digihunch.com/wp-content/uploads/2021/10/image-2.png" alt="" class="wp-image-2764" width="840" height="513"/&gt;&lt;figcaption class="wp-element-caption"&gt;Explore Metrics&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;With this quick and dirty configuration, we established a good understanding of what Prometheus, Loki and Grafana do. Next, we will move all these configurations to K8s cluster and understand some specific points of configurations.&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/file-storage-vs-object-storage/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous 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;a rel="next" href="https://static.digihunch.com/2021/10/logging-and-monitoring-in-kubernetes-with-plg-stack/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Logging and Monitoring in Kubernetes with PLG stack&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item><item><title>Log Shipping in Kubernetes with EFK stack</title><link>https://static.digihunch.com/2021/09/log-shipping-in-kubernetes-with-efk/</link><pubDate>Sat, 04 Sep 2021 21:50:00 -0400</pubDate><guid>https://static.digihunch.com/2021/09/log-shipping-in-kubernetes-with-efk/</guid><description>&lt;img src="https://static.digihunch.com/wp-content/uploads/2025/04/feature-elk-logshipping.webp" alt="Featured image of post Log Shipping in Kubernetes with EFK stack" /&gt;&lt;p class="wp-block-paragraph"&gt;I first worked on &lt;a href="https://static.digihunch.com/2018/09/log-shipping-through-elk/"&gt;log shipping&lt;/a&gt; with ELK stack three years ago. In the context of Kubernetes cluster, log shipping has similar challenges. In this post I will discuss the set up of log shipping with Kubernetes cluster using EFK stack&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-logging-architecture"&gt;Logging Architecture&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;As &lt;a href="https://static.digihunch.com/2021/08/docker-desktop-a-single-node-kubernetes-cluster/"&gt;discussed&lt;/a&gt;, if the Kubernetes cluster has a runtime in compliant with CRI (e.g. &lt;em&gt;containerd&lt;/em&gt;), then the stdout and stderr of the Pod is stored on the node, in the location /var/log/containers/.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;When creating log shipping solution, it is important to use a compliant cluster (e.g. minikube) to ensure what you develop will work across environments. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The Kubernetes document has a section on &lt;a href="https://kubernetes.io/docs/concepts/cluster-administration/logging/#cluster-level-logging-architectures"&gt;logging architecture&lt;/a&gt; which is a good start point. It outlines several different patterns. Logging at the node level is turned on by default and does not require special configuration, as explained in the section above. The EFK pattern is close to the diagram under &lt;a href="https://kubernetes.io/docs/concepts/cluster-administration/logging/#cluster-level-logging-architectures"&gt;using a node logging agent&lt;/a&gt; for cluster-level logging.&lt;/p&gt;&#10;&lt;div class="wp-block-image"&gt;&#10;&lt;figure class="aligncenter"&gt;&lt;img decoding="async" src="https://d33wubrfki0l68.cloudfront.net/2585cf9757d316b9030cf36d6a4e6b8ea7eedf5a/1509f/images/docs/user-guide/logging/logging-with-node-agent.png" alt="Using a node level logging agent"/&gt;&lt;figcaption class="wp-element-caption"&gt;Node logging agent&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;/div&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The diagram above is stolen from Kubernetes documentation. In EFK stack, the agent is a daemonset running fluentd Pod. &lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-efk-stack"&gt;EFK stack&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;While we can use ELK (Elasticsearch, Logstash, Kibana) stack for log shipping, EFK (Elasticsearch, Fluentd, Kibana) is generally recommended in Kubernetes cluster. We compare the two in the following table:&lt;/p&gt;&#10;&lt;figure class="wp-block-table is-style-regular"&gt;&lt;table class="has-white-background-color has-background has-fixed-layout"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td&gt;&lt;/td&gt;&lt;td&gt;ELK&lt;/td&gt;&lt;td&gt;EFK&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Development language&lt;/td&gt;&lt;td&gt;All in Java. Logstash and Filebeat both require JVM. Managed as an open-source project by Elastic company&lt;/td&gt;&lt;td&gt;Fluentd in Ruby and does not require JVM to run. Fluentd is a CNCF project built to integrate with Kubernetes.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Typical pattern&lt;/td&gt;&lt;td&gt;Filebeat acts as a lightweight collector to monitor the source log. Logstash as aggregator to receive from filebeat, and push to Elasticsearch&lt;/td&gt;&lt;td&gt;The fluentd Pod can be configured to serve as forwarder and aggregator based on configuration. fluentd-forwarder is deployed as daemonset on node, and ship the result to fluentd-aggregator, which may run in a separate cluster. The fluentd-aggregator pushes processed results to Elasticsearch.&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Metrics&lt;/td&gt;&lt;td&gt;use metric beat for data collection&lt;/td&gt;&lt;td&gt;scrape metrics from prometheus server&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td&gt;Event Routing&lt;/td&gt;&lt;td&gt;tag-based&lt;/td&gt;&lt;td&gt;if-then statement&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;figcaption class="wp-element-caption"&gt;ELK and EFK stacks comparison&lt;/figcaption&gt;&lt;/figure&gt;&#10;&lt;p class="wp-block-paragraph"&gt;The main problems in log shipping are:&lt;/p&gt;&#10;&lt;ul class="wp-block-list"&gt;&#10;&lt;li&gt;column mapping : identify column patterns in each log line and map them to appropriate column in Elasticsearch.&lt;/li&gt;&#10;&lt;li&gt;multi-line processing: identify when a logging entry spread across multiple lines and process accordingly.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Suppose we want to congregate the logs from stdout and stderr of PostgreSQL pods. The raw output in /var/log/container on the node, looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:46.75370563Z stdout F server stopped&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:46.757173069Z stderr F postgresql-repmgr 15:27:46.75 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Starting PostgreSQL in background...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:46.883126928Z stderr F postgresql-repmgr 15:27:46.88 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Registering Primary...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.017164653Z stderr F postgresql-repmgr 15:27:47.01 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Loading custom scripts...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.023334611Z stderr F postgresql-repmgr 15:27:47.02 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Loading user&lt;span style="color:#960050;background-color:#1e0010"&gt;&amp;#39;&lt;/span&gt;s custom files from /docker-entrypoint-initdb.d ...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.026169813Z stderr F postgresql-repmgr 15:27:47.02 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Starting PostgreSQL in background...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.343607487Z stderr F postgresql-repmgr 15:27:47.34 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Stopping PostgreSQL...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.448111425Z stdout F waiting &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; server to shut down.... &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;2021-08-28T15:27:47.448172479Z stdout F server stopped&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.453722807Z stderr F postgresql-repmgr 15:27:47.45 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; ** PostgreSQL with Replication Manager setup finished! **&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.453829953Z stdout F&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.503516746Z stderr F postgresql-repmgr 15:27:47.50 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; Starting PostgreSQL in background...&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.532558987Z stdout F waiting &lt;span style="color:#66d9ef"&gt;for&lt;/span&gt; server to start....2021-08-28 15:27:47.532 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: pgaudit extension initialized&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.533307459Z stdout F 2021-08-28 15:27:47.533 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: listening on IPv4 address &lt;span style="color:#e6db74"&gt;&amp;#34;0.0.0.0&amp;#34;&lt;/span&gt;, port &lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.533466407Z stdout F 2021-08-28 15:27:47.533 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: listening on IPv6 address &lt;span style="color:#e6db74"&gt;&amp;#34;::&amp;#34;&lt;/span&gt;, port &lt;span style="color:#ae81ff"&gt;5432&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.537987947Z stdout F 2021-08-28 15:27:47.537 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: listening on Unix socket &lt;span style="color:#e6db74"&gt;&amp;#34;/tmp/.s.PGSQL.5432&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.547956465Z stdout F 2021-08-28 15:27:47.547 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: redirecting log output to logging collector process&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.548005463Z stdout F 2021-08-28 15:27:47.547 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; HINT: Future log output will appear in directory &lt;span style="color:#e6db74"&gt;&amp;#34;/opt/bitnami/postgresql/logs&amp;#34;&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.551741571Z stdout F 2021-08-28 15:27:47.551 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;275&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: database system was shut down at 2021-08-28 15:27:47 GMT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.558012894Z stdout F 2021-08-28 15:27:47.557 GMT &lt;span style="color:#f92672"&gt;[&lt;/span&gt;273&lt;span style="color:#f92672"&gt;]&lt;/span&gt; LOG: database system is ready to accept connections&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.618577092Z stdout F &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;2021-08-28T15:27:47.618708978Z stdout F server started&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.630065958Z stderr F postgresql-repmgr 15:27:47.62 INFO &lt;span style="color:#f92672"&gt;==&lt;/span&gt;&amp;gt; ** Starting repmgrd **&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.638116348Z stderr F &lt;span style="color:#f92672"&gt;[&lt;/span&gt;2021-08-28 15:27:47&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;NOTICE&lt;span style="color:#f92672"&gt;]&lt;/span&gt; repmgrd &lt;span style="color:#f92672"&gt;(&lt;/span&gt;repmgrd 5.2.1&lt;span style="color:#f92672"&gt;)&lt;/span&gt; starting up&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.65317627Z stderr F INFO: set_repmgrd_pid&lt;span style="color:#f92672"&gt;()&lt;/span&gt;: provided pidfile is /opt/bitnami/repmgr/tmp/repmgr.pid&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;2021-08-28T15:27:47.653232015Z stderr F &lt;span style="color:#f92672"&gt;[&lt;/span&gt;2021-08-28 15:27:47&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;NOTICE&lt;span style="color:#f92672"&gt;]&lt;/span&gt; starting monitoring of node &lt;span style="color:#e6db74"&gt;&amp;#34;orthweb-postgresql-ha-postgresql-0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ID: 1000&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;2021-08-28T15:27:47.681683703Z stderr F &lt;span style="color:#f92672"&gt;[&lt;/span&gt;2021-08-28 15:27:47&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;NOTICE&lt;span style="color:#f92672"&gt;]&lt;/span&gt; monitoring cluster primary &lt;span style="color:#e6db74"&gt;&amp;#34;orthweb-postgresql-ha-postgresql-0&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ID: 1000&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;2021-08-28T15:28:11.742865958Z stderr F &lt;span style="color:#f92672"&gt;[&lt;/span&gt;2021-08-28 15:28:11&lt;span style="color:#f92672"&gt;]&lt;/span&gt; &lt;span style="color:#f92672"&gt;[&lt;/span&gt;NOTICE&lt;span style="color:#f92672"&gt;]&lt;/span&gt; new standby &lt;span style="color:#e6db74"&gt;&amp;#34;orthweb-postgresql-ha-postgresql-1&amp;#34;&lt;/span&gt; &lt;span style="color:#f92672"&gt;(&lt;/span&gt;ID: 1001&lt;span style="color:#f92672"&gt;)&lt;/span&gt; has connected&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From this snippet of log, we can see each line in stdout or stderr is appended with a timestamp. There are multi-line log entries but each is still appended with a timestamp. This is just how kubernetes keeps the log file for Pod stdout and stderr. To handle that, we need to first take out the real log line, and then process multi-line.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We will go over the installation of EFK stack and the mechanism to address the two challenges above.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-install-elasticsearch-and-kibana"&gt;Install Elasticsearch and Kibana&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;To install Elasticsearch, we use the helm chart provided by the official repository:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;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 elastic https://helm.elastic.co&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;If we run multiple pods on the same hosts, then we need some customized values in order to get the installation to work. The values.yaml file looks like this:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-js" data-lang="js"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#f92672"&gt;---&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;antiAffinity&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;soft&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:#a6e22e"&gt;esJavaOpts&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;-Xmx128m -Xms128m&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:#960050;background-color:#1e0010"&gt;#&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;Allocate&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;smaller&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;chunks&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;of&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;per&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;pod&lt;/span&gt;.&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#a6e22e"&gt;resources&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;requests&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;100m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;512M&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;limits&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;cpu&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;1000m&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;memory&lt;/span&gt;&lt;span style="color:#f92672"&gt;:&lt;/span&gt; &lt;span style="color:#e6db74"&gt;&amp;#34;512M&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;Then we can &amp;#8220;preview&amp;#8221; what values are used for installation, with helm&amp;#8217;s template command:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm template elasticsearch elastic/elasticsearch -f values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The effect of the antiAffinity property allows multiple Elasticsearch Pod to be scheduled on the same node. This is not required in production with multiple nodes. To install Elasticsearch and Kibana, run:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install elasticsearch elastic/elasticsearch -f values.yaml&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;helm install kibana elastic/kibana&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;The Kibana service is exposed on port 5601 of the cluster. To access the port on cluster, we need to run port-forward command 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;kubectl port-forward deployment/kibana-kibana &lt;span style="color:#ae81ff"&gt;5601&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 class="wp-block-heading" id="h-install-fluentd"&gt;Install Fluentd&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;There are different ways to configure Fluentd. For example, in the forwarder-aggregator pattern, a forwarder Pod is a Daemonset on each Kubernetes node. The forwarder pushes to the aggregator, at port 24224. &lt;a href="https://docs.fluentd.org/deployment/high-availability"&gt;This&lt;/a&gt; page has some configuration details. This pattern is similar to filebeat -&amp;gt; logstash pattern in ELK stack.&lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;In our case, we use a simplified pattern, with a fluentd daemonset acting as collector and then forward to Elasticsearch. Similarly, in ELK stack we can use filebeat to push to Elasticsearch without Logstash. The only reason is the entire architecture isn&amp;#8217;t as complicated as requiring an aggregator. &lt;/p&gt;&#10;&lt;p class="wp-block-paragraph"&gt;We need to create configmap 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-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;ConfigMap&lt;/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;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;fluentd-cm&lt;/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;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.kubernetes.io/component&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;forwarder&lt;/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.kubernetes.io/instance&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fluentd&lt;/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.kubernetes.io/managed-by&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;Helm&lt;/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.kubernetes.io/name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fluentd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;helm.sh/chart&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fluentd-1.3.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;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;meta.helm.sh/release-name&lt;/span&gt;: &lt;span style="color:#ae81ff"&gt;fluentd&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;meta.helm.sh/release-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;data&lt;/span&gt;:&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#f92672"&gt;fluentd.conf&lt;/span&gt;: |&lt;span style="color:#e6db74"&gt;&#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; # Ignore fluentd own events&#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;lt;match fluent.**&amp;gt;&#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; @type null&#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;lt;/match&amp;gt;&#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;&#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; # HTTP input for the liveness and readiness probes&#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;lt;source&amp;gt;&#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; @type http&#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; port 9880&#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;lt;/source&amp;gt;&#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;&#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; # Throw the healthcheck to the standard output instead of forwarding it&#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;lt;match fluentd.healthcheck&amp;gt;&#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; @type null&#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;lt;/match&amp;gt;&#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;&#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; # Get the logs from the containers running in the node&#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;lt;source&amp;gt;&#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; @type tail&#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; read_from_head true&#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; tag kubernetes.*&#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; path /var/log/containers/orthweb-postgresql-ha-postgresql-**.log&#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; pos_file /opt/bitnami/fluentd/logs/buffers/fluentd-docker.pos&#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;lt;parse&amp;gt;&#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; @type regexp&#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; expression ^(?&amp;lt;time&amp;gt;\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.[^Z]*Z)\s(?&amp;lt;stream&amp;gt;[^\s]+)\s(?&amp;lt;character&amp;gt;[^\s])\s(?&amp;lt;message&amp;gt;.*)$&#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;lt;/parse&amp;gt;&#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;lt;/source&amp;gt;&#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; # enrich with kubernetes metadata&#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;lt;filter kubernetes.**&amp;gt;&#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; @type kubernetes_metadata&#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; @id filter_kube_metadata&#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; kubernetes_url &amp;#34;#{ENV[&amp;#39;FLUENT_FILTER_KUBERNETES_URL&amp;#39;] || &amp;#39;https://&amp;#39; + ENV.fetch(&amp;#39;KUBERNETES_SERVICE_HOST&amp;#39;) + &amp;#39;:&amp;#39; + ENV.fetch(&amp;#39;KUBERNETES_SERVICE_PORT&amp;#39;) + &amp;#39;/api&amp;#39;}&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; verify_ssl &amp;#34;#{ENV[&amp;#39;KUBERNETES_VERIFY_SSL&amp;#39;] || true}&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; ca_file &amp;#34;#{ENV[&amp;#39;KUBERNETES_CA_FILE&amp;#39;]}&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; skip_labels &amp;#34;#{ENV[&amp;#39;FLUENT_KUBERNETES_METADATA_SKIP_LABELS&amp;#39;] || &amp;#39;false&amp;#39;}&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; skip_container_metadata &amp;#34;#{ENV[&amp;#39;FLUENT_KUBERNETES_METADATA_SKIP_CONTAINER_METADATA&amp;#39;] || &amp;#39;false&amp;#39;}&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; skip_master_url &amp;#34;#{ENV[&amp;#39;FLUENT_KUBERNETES_METADATA_SKIP_MASTER_URL&amp;#39;] || &amp;#39;false&amp;#39;}&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; skip_namespace_metadata &amp;#34;#{ENV[&amp;#39;FLUENT_KUBERNETES_METADATA_SKIP_NAMESPACE_METADATA&amp;#39;] || &amp;#39;false&amp;#39;}&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; &amp;lt;/filter&amp;gt;&#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;&#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;&#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;lt;match kubernetes.var.log.containers.orthweb-postgresql-ha-postgresql-**.log&amp;gt;&#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; @type elasticsearch&#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; include_tag_key true&#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; host &amp;#34;elasticsearch-master.default.svc.cluster.local&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; port &amp;#34;9200&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; index_name &amp;#34;postgresql-logs&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; &amp;lt;buffer&amp;gt;&#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; @type file&#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; path /opt/bitnami/fluentd/logs/buffers/orthanc-logs.buffer&#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; flush_thread_count 2&#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; flush_interval 5s&#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;lt;/buffer&amp;gt;&#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;lt;/match&amp;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 can create the resource, with helm chart pointing to the config map:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;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 fluentd bitnami/fluentd --set aggregator.enabled&lt;span style="color:#f92672"&gt;=&lt;/span&gt;false --set forwarder.configMap&lt;span style="color:#f92672"&gt;=&lt;/span&gt;fluentd-cm&#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 validate the index creation on Elasticsearch:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;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;kubectl port-forward service/elasticsearch-master 9200&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;curl -XGET localhost:9200/_cat/indices&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p class="wp-block-paragraph"&gt;From Kibana, we can forward the port as instructed above, and browse to the UI. Once logged on to Kibana, we need to add index pattern first before being able to view the content of index.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-how-about-fluent-bit"&gt;How about Fluent Bit&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;Fluentd has an even more lightweight brother Fluent Bit, also a CNCF project, designed by the same team, for environments with tighter resource restrictions. The technical differences are outlined on &lt;a href="https://docs.fluentbit.io/manual/about/fluentd-and-fluent-bit"&gt;this&lt;/a&gt; page outlines the technical differences. In terms of use case, Fluentd is a log aggregator and Fluent Bit is simply a forwarder. In terms of ecosystem, Fluentd has a stronger ecosystem whereas Fluent Bit is more seen in IoT devices. Read &lt;a href="https://logz.io/blog/fluentd-vs-fluent-bit/"&gt;this&lt;/a&gt; post for more comparison.&lt;/p&gt;&#10;&lt;h3 class="wp-block-heading" id="h-summary"&gt;Summary&lt;/h3&gt;&#10;&lt;p class="wp-block-paragraph"&gt;EFK stack (Elasticsearch, Fluentd and Kibana) on Kubernetes is a natural choice for ELK users. Fluentd is a CNCF project created for integration with Kubernetes. It is good alternative to enterprise solution such as &lt;a href="https://docs.fluentd.org/v/0.12/articles/free-alternative-to-splunk-by-fluentd"&gt;Splunk&lt;/a&gt;. There are lots of &lt;a href="https://docs.fluentd.org/input"&gt;plugins&lt;/a&gt; supported and &lt;a href="https://docs.fluentd.org/v/0.12/articles"&gt;articles&lt;/a&gt; on configuration. While developing a solution I had to spend time reading the &lt;a href="https://docs.fluentd.org/input"&gt;input plugin&lt;/a&gt; documentations. &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/creating-tls-certificate-kubernetes/"&gt;&lt;span class="wp-post-navigation-label"&gt;Previous 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;a rel="next" href="https://static.digihunch.com/2021/09/single-node-kubernetes-cluster-minikube/"&gt;&lt;span class="wp-post-navigation-label"&gt;Next Post&lt;/span&gt;&lt;strong class="wp-post-navigation-title"&gt;Local multi-node cluster – Minikube, MicroK8s and KinD&lt;/strong&gt;&lt;/a&gt;&#10;&lt;/nav&gt;&#10;</description></item></channel></rss>