<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Julien Acroute</title>
    <description>The latest articles on DEV Community by Julien Acroute (@vampouille).</description>
    <link>https://dev.to/vampouille</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F589821%2F481b67ed-7089-4aef-8d0a-d0f89ec96c16.png</url>
      <title>DEV Community: Julien Acroute</title>
      <link>https://dev.to/vampouille</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/vampouille"/>
    <language>en</language>
    <item>
      <title>Beyond the Buzz: Embracing the Magic of eBPF in Kubernetes</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Wed, 10 Apr 2024 14:02:32 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/beyond-the-buzz-embracing-the-magic-of-ebpf-in-kubernetes-45md</link>
      <guid>https://dev.to/camptocamp-ops/beyond-the-buzz-embracing-the-magic-of-ebpf-in-kubernetes-45md</guid>
      <description>&lt;h2&gt;
  
  
  Beyond the Buzz: Embracing the Magic of eBPF in Kubernetes
&lt;/h2&gt;

&lt;p&gt;In a time where the buzz around Artificial Intelligence (AI) seems to overshadow everything else, this year's KubeCon Europe offered a refreshing perspective. While AI continues to be a hot topic, some in the Kubernetes community are starting to feel a bit tired of it. With all the hype and uncertainty surrounding AI, another hero has emerged: eBPF (Extended Berkeley Packet Filter).&lt;/p&gt;

&lt;h3&gt;
  
  
  AI: A Distant Shining Horizon
&lt;/h3&gt;

&lt;p&gt;AI has certainly added some excitement to discussions about cloud-native technologies, from automating cluster troubleshooting to hosting AI on Kubernetes. But not everyone is fully on board. While AI has proved to be of great assistance - e.g. suggesting, fixing and reviewing code written by humans-  some folks worry that too much focus on AI might distract from more practical, ready-to-implement advancements. The feeling is clear: while AI offers lots of possibilities, the horizon is a bit uncertain and overly hyped.&lt;/p&gt;

&lt;h3&gt;
  
  
  eBPF: Here and Now in Kubernetes Innovation
&lt;/h3&gt;

&lt;p&gt;In contrast, eBPF is all about practical innovation. It's a technology that delivers real results, right here, right now. We need solutions for network security, observability, and performance today, and that's where eBPF shines. Unlike the abstract promises of AI, eBPF offers concrete tools and methods to improve Kubernetes environments immediately. For example, when it comes to network security, eBPF-powered tools like Cilium can do the job without needing the complexity of AI.&lt;br&gt;
This shift towards valuing what's immediately useful over what's exciting but distant was noticeable at KubeCon. As we dive deeper into what eBPF can do, it becomes clear why this technology has captured the attention of the Kubernetes community.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Way Forward with eBPF
&lt;/h3&gt;

&lt;p&gt;By embracing eBPF, the Kubernetes community isn't just adopting new tools; it's championing a philosophy of practical, tangible progress. As we explore the latest eBPF innovations – from better security to revolutionary observability tools – we see the benefits eBPF brings to Kubernetes. It's a journey grounded in reality, offering not just a vision of the future, but a roadmap to get there.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redefining Efficiency: eBPF's Radical Return to Linux's Roots
&lt;/h3&gt;

&lt;p&gt;eBPF represents a refreshing departure from the conventional approach of stacking layer upon layer in pursuit of functionality, often resulting in bloated, resource-intensive systems, even for simple tasks like rendering a webpage or routing network packets between nodes or clusters. With eBPF, we're venturing back into the depths of the Linux system, where innovation meets efficiency. Here, we witness a paradigm shift—a departure from the status quo. The results speak for themselves: a &lt;a href="https://isovalent.com/blog/post/tetragon-release-10/#process-execution-tracking-at-less2percent-overhead"&gt;nearly negligible overhead&lt;/a&gt; and remarkable responsiveness. In fact, eBPF brings us so close to real-time processing that it's revolutionizing how we think about performance in cloud-native environments. We're not just optimizing; we're redefining what's possible, and eBPF is leading the charge.&lt;/p&gt;

&lt;h3&gt;
  
  
  eBPF talks from KubeCon 2024
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Cilium: Connecting, Observing, and Securing Service Mesh and Beyond with eBPF&lt;br&gt;
Speakers: Liz Rice, Christine Kim, Nico Meisenzahl, Vlad Ungureanu&lt;br&gt;
&lt;a href="https://youtu.be/wq1TxZw1AaY?si=JTyhE333QfsGht0T"&gt;https://youtu.be/wq1TxZw1AaY?si=JTyhE333QfsGht0T&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Dealing with eBPF’s Observability Data Deluge&lt;br&gt;
Speaker: Anna Kapuścińska&lt;br&gt;
&lt;a href="https://youtu.be/yWB8n_e4N14?si=OyMJEKzbxS5zxA5P"&gt;https://youtu.be/yWB8n_e4N14?si=OyMJEKzbxS5zxA5P&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Unlock Energy Consumption in the Cloud with eBPF&lt;br&gt;
Speaker: Leonard Pahlke&lt;br&gt;
&lt;a href="https://youtu.be/lW9pZoKRJVs?si=rX5CQMaFuZBm8bBT"&gt;https://youtu.be/lW9pZoKRJVs?si=rX5CQMaFuZBm8bBT&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Fast and Efficient Log Processing with Wasm and eBPF&lt;br&gt;
Speaker: Michael Yuan&lt;br&gt;
&lt;a href="https://youtu.be/4u7nUpZxr3g?si=pkcpoEwOeDaH5HcE"&gt;https://youtu.be/4u7nUpZxr3g?si=pkcpoEwOeDaH5HcE&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;No 'Soup' for You! Enforcing Network Policies for Host Processes via eBPF&lt;br&gt;
Speaker: Vinay Kulkarni&lt;br&gt;
&lt;a href="https://youtu.be/AWAf3H4Qwq8?si=qVqQfWb3J_905BCJ"&gt;https://youtu.be/AWAf3H4Qwq8?si=qVqQfWb3J_905BCJ&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;eBPF’s Abilities and Limitations: The Truth&lt;br&gt;
Speakers: Liz Rice &amp;amp; John Fastabend&lt;br&gt;
&lt;a href="https://youtu.be/tClsqnZMN6I?si=TyMFTMk4Q45K6T2v"&gt;https://youtu.be/tClsqnZMN6I?si=TyMFTMk4Q45K6T2v&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>ebpf</category>
      <category>ia</category>
    </item>
    <item>
      <title>Use Tetragon to Limit Network Usage for a set of Binary</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Thu, 03 Aug 2023 07:39:02 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/use-tetragon-to-limit-network-usage-for-a-set-of-binary-2l4e</link>
      <guid>https://dev.to/camptocamp-ops/use-tetragon-to-limit-network-usage-for-a-set-of-binary-2l4e</guid>
      <description>&lt;h2&gt;
  
  
  A matter of trust
&lt;/h2&gt;

&lt;p&gt;Many interesting software are coming from the community, many are distributed through the package manager of the operating system. But for the others, you can download them from Github release pages, use snap or homebrew to cite a few. But this last installation method bypasses the security team that tries to improve the security of your operating system. By doing so, you are implicitly trusting the author he is not distributing malware or implementing backdoors. How many tools did you install by hand? Do you really trust all of them? Confidence is very important, yet it would be nice to limit capabilities for a set of binary that you don't fully trust. In this blog post, we will use &lt;a href="https://github.com/cilium/tetragon"&gt;Tetragon&lt;/a&gt;  to forbid network usage for tools that don't need to.&lt;/p&gt;

&lt;h2&gt;
  
  
  Goal
&lt;/h2&gt;

&lt;p&gt;We will separate tools installed locally into two families. On one side, tools that need network access in a specific directory. Another directory for tools that don't need internet access. &lt;/p&gt;

&lt;p&gt;For example the following tools use network sockets:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/derailed/k9s"&gt;k9s&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://kubernetes.io/fr/docs/tasks/tools/install-kubectl/"&gt;kubectl&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://helm.sh/"&gt;helm&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;while these do not: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://jqlang.github.io/jq/"&gt;jq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/mikefarah/yq"&gt;yq&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://jless.io/"&gt;jless&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will move the last tools in a specific directory &lt;code&gt;~/bin-no-network/&lt;/code&gt; and use &lt;a href="https://github.com/cilium/tetragon"&gt;Tetragon&lt;/a&gt; to inject a policy in the kernel as a &lt;a href="https://ebpf.io/"&gt;eBPF&lt;/a&gt; program to kill any binary located in &lt;code&gt;~/bin-no-network/&lt;/code&gt; trying to open a network socket.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tetragon installation
&lt;/h2&gt;

&lt;p&gt;Tetragon is "Kubernetes-aware" but it can also be used outside Kubernetes on a regular workstation. You can deploy Tetragon as a container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;--name&lt;/span&gt; tetragon &lt;span class="nt"&gt;--rm&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt;                 &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;--pid&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host &lt;span class="nt"&gt;--cgroupns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;host &lt;span class="nt"&gt;--privileged&lt;/span&gt;          &lt;span class="se"&gt;\&lt;/span&gt;
    &lt;span class="nt"&gt;-v&lt;/span&gt; /sys/kernel/btf/vmlinux:/var/lib/tetragon/btf &lt;span class="se"&gt;\&lt;/span&gt;
    quay.io/cilium/tetragon:v0.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because Tetragon needs to inject code in the kernel, we need to bypass most of the docker isolation mechanism. We only use the packaging feature of docker to avoid installation of system libraries and the binary. So you will have to trust ;-) this tetragon binary because it will run like a process running as root on your workstation. Note the mount point: &lt;code&gt;/sys/kernel/btf/vmlinux&lt;/code&gt;, this is a kind of bridge to eBPF kernel features.&lt;/p&gt;

&lt;h2&gt;
  
  
  eBPF Principle
&lt;/h2&gt;

&lt;p&gt;When using eBPF, applications are always composed of two parts, one deployed in the Kernel as an eBPF program and another running as a regular program in "user space". The user space program (Tetragon) will inject a small eBPF program in the kernel to intercept some system calls. This means that every application that uses this system call will trigger this eBPF program that can observe or modify the system call result. A specific data structure is then created in the kernel: a ring buffer. This is used by the eBPF program to store some interesting data. Finally, the user space program Tetragon, which has also read-only access to this data structure, will be able to retrieve information gathered by the eBPF program.&lt;br&gt;
One good point with this architecture is that evaluation of rules is done within the kernel and does not require communication with the user space program. The only drawback is that information observed by the eBPF program can be overridden by new incoming information if the user space part does not read information fast enough. This is how ring buffers are designed.&lt;/p&gt;
&lt;h2&gt;
  
  
  Writing a Policy
&lt;/h2&gt;

&lt;p&gt;Our goal is to forbid network usage coming from binaries in a specific folder.&lt;br&gt;
For this we will need a CLI to interact with tetragon. We can use the &lt;code&gt;tetra&lt;/code&gt; binary in the docker container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;alias &lt;/span&gt;&lt;span class="nv"&gt;tetra&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"docker exec -ti tetragon tetra"&lt;/span&gt;
&lt;span class="nv"&gt;$ &lt;/span&gt;tetra version
server version: v0.10.0
cli version: v0.10.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Even if we are not using Kubernetes, we still need to write some yaml as Tetragon only understands TracingPolicy objects. A TracingPolicy is a kubernetes custom resource to install hooks in the kernel and actions.&lt;/p&gt;

&lt;p&gt;Let's start with a TracingPolicy from the &lt;a href="https://tetragon.cilium.io/docs/use-cases/network-observability/"&gt;Tetragon documentation&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cilium.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TracingPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;connect"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kprobes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp_connect"&lt;/span&gt;
    &lt;span class="na"&gt;syscall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sock"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp_close"&lt;/span&gt;
    &lt;span class="na"&gt;syscall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sock"&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp_sendmsg"&lt;/span&gt;
    &lt;span class="na"&gt;syscall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sock"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This Tracing policy will just observe network events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;socket creation (tcp_connect)&lt;/li&gt;
&lt;li&gt;traffic in the socket (tcp_sendmsg)&lt;/li&gt;
&lt;li&gt;socket close (tcp_close)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we need to add: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an action when this kind of event is detected&lt;/li&gt;
&lt;li&gt;a filter to only apply this action if this is generated from a binary located in our specific directory &lt;code&gt;~/bin-no-network/&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We need to add a &lt;code&gt;selectors&lt;/code&gt; section in the TracingPolicy and use the &lt;code&gt;matchBinaries&lt;/code&gt; selector to apply this policy only for binary in the &lt;code&gt;~/bin-no-network/&lt;/code&gt; folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matchBinaries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;In"&lt;/span&gt;
        &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/jacroute/bin-no-network/curl"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/jacroute/bin-no-network/jq"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Unfortunately, only 'In' and 'NotIn' operator are &lt;a href="https://github.com/cilium/tetragon/pull/686/files#diff-0b6c66cf370068fe2e441466d6124582732e8b67dd0daddccf72c7eb2bef16d4R994-R1000"&gt;implemented&lt;/a&gt; for the &lt;code&gt;matchBinaries&lt;/code&gt; selector. &lt;a href="https://github.com/cilium/tetragon/issues/1278"&gt;We cannot use the 'Prefix' operator&lt;/a&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;    &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matchBinaries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Prefix"&lt;/span&gt;
        &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/jacroute/bin-no-network/"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So the TracingPolicy should look like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;cilium.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TracingPolicy&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;connect"&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;kprobes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp_connect"&lt;/span&gt;
    &lt;span class="na"&gt;syscall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sock"&lt;/span&gt;
    &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nl"&gt;&amp;amp;selector&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;matchBinaries&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;operator&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;In"&lt;/span&gt;
        &lt;span class="na"&gt;values&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/jacroute/bin-no-network/curl"&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/home/jacroute/bin-no-network/jq"&lt;/span&gt;
      &lt;span class="na"&gt;matchActions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Sigkill&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp_close"&lt;/span&gt;
    &lt;span class="na"&gt;syscall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sock"&lt;/span&gt;
    &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*selector&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;call&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tcp_sendmsg"&lt;/span&gt;
    &lt;span class="na"&gt;syscall&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sock"&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;int&lt;/span&gt;
    &lt;span class="na"&gt;selectors&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;*selector&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy the TracingPolicy
&lt;/h2&gt;

&lt;p&gt;We first need to transfer the file to the container. Suppose that you stored the TracingPolicy in &lt;code&gt;bin-no-network.yaml&lt;/code&gt; file, you can transfer the policy using the &lt;code&gt;docker cp&lt;/code&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker &lt;span class="nb"&gt;cp &lt;/span&gt;bin-no-network.yaml tetragon:/tmp/bin-no-network.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then we can use the &lt;code&gt;tetra&lt;/code&gt; cli to deploy this policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;tetra tracingpolicy add /tmp/bin-no-network.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing the TracingPolicy
&lt;/h2&gt;

&lt;p&gt;Now we can test if the policy is blocking network access for the two binaries listed in the policy.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;mkdir&lt;/span&gt; ~/bin-no-network/
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /usr/bin/curl ~/bin-no-network/
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;cp&lt;/span&gt; /usr/bin/jq ~/bin-no-network/
&lt;span class="nv"&gt;$ &lt;/span&gt;~/bin-no-network/curl google.fr
&lt;span class="o"&gt;[&lt;/span&gt;1] 122677 killed ~/bin-no-network/curl google.fr
&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"{}"&lt;/span&gt; | ~/bin-no-network/jq
&lt;span class="o"&gt;{}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;curl&lt;/code&gt; tried to open a socket but the process was killed. &lt;code&gt;jq&lt;/code&gt; does not try this kind of system call and was not killed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automatically populating the Policy
&lt;/h2&gt;

&lt;p&gt;We can build a shell script to maintain the list of binaries synchronized with the content of the &lt;code&gt;~/bin-no-network/&lt;/code&gt; directory. Using &lt;code&gt;find&lt;/code&gt; we can list binaries in this folder:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;find ~/bin-no-network/ &lt;span class="nt"&gt;-executable&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f
/home/jacroute/bin-no-network/curl
/home/jacroute/bin-no-network/jq
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then with awk with can add surrounding spaces and quotes needed to integrate the yaml:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;find ~/bin-no-network/ &lt;span class="nt"&gt;-executable&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{ print "          - \""$0"\""}'&lt;/span&gt;
          - &lt;span class="s2"&gt;"/home/jacroute/bin-no-network/curl"&lt;/span&gt;
          - &lt;span class="s2"&gt;"/home/jacroute/bin-no-network/jq"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally, integrate this in the yaml and inject the policy in the kernel with the following script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;

docker &lt;span class="nb"&gt;exec &lt;/span&gt;tetragon tetra sensors &lt;span class="nb"&gt;rm &lt;/span&gt;connect

&lt;span class="nv"&gt;policy&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;mktemp&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; /dev/shm&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt; &amp;gt; &lt;/span&gt;&lt;span class="nv"&gt;$policy&lt;/span&gt;&lt;span class="sh"&gt;
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
  name: "connect"
spec:
  kprobes:
  - call: "tcp_connect"
    syscall: false
    args:
    - index: 0
      type: "sock"
    selectors: &amp;amp;selector
    - matchBinaries:
      - operator: "In"
        values:
&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;find ~/bin-no-network/ &lt;span class="nt"&gt;-executable&lt;/span&gt; &lt;span class="nt"&gt;-type&lt;/span&gt; f | &lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{ print "          - \""$0"\""}'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="sh"&gt;
      matchActions:
      - action: Sigkill
  - call: "tcp_close"
    syscall: false
    args:
    - index: 0
      type: "sock"
    selectors: *selector
  - call: "tcp_sendmsg"
    syscall: false
    args:
    - index: 0
      type: "sock"
    - index: 2
      type: int
    selectors: *selector
&lt;/span&gt;&lt;span class="no"&gt;EOF

&lt;/span&gt;docker &lt;span class="nb"&gt;cp&lt;/span&gt; &lt;span class="nv"&gt;$policy&lt;/span&gt; tetragon:/tmp/policy.yaml
docker &lt;span class="nb"&gt;exec &lt;/span&gt;tetragon tetra tracingpolicy add /tmp/policy.yaml
&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nv"&gt;$policy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Building trust in your relationship with your workstation XD
&lt;/h2&gt;

&lt;p&gt;After a first date with Tetragon during the &lt;a href="https://www.kcdfrance.fr/"&gt;Kubernetes Community Days&lt;/a&gt; during 2023, I was seduced by the way it's implemented: most of the time new features are implemented on top of others creating a complex multi layered cathedral. Perfect for a wedding, but I prefer simplicity. &lt;/p&gt;

&lt;p&gt;Using eBPF to enforce "security" seems to be very efficient from the performance point of view. Bypassing Tetragon security by denial of service attacks is unlikely. For example, the sigkill action triggered by the Tetragon’s eBPF program is done synchronously, in the kernel and not at user space, which makes it hard to bypass. In other words, the security mechanism is fully and autonomously implemented at kernel level.&lt;/p&gt;

&lt;p&gt;This mechanism proves to be effective in blocking network activity and building trust in our beloved workstation even when running potentially malicious binaries.&lt;/p&gt;

&lt;p&gt;Using Tetragon in a Kubernetes context is the next step. The plan is to observe the behavior of an application in a development environment (files read/write, command executed) and generate a profile. Then, promote this profile from development to production, and enforce an &lt;em&gt;allow-only&lt;/em&gt; behavior using Tetragon.&lt;/p&gt;

</description>
      <category>tetragon</category>
      <category>security</category>
      <category>ebpf</category>
      <category>workstations</category>
    </item>
    <item>
      <title>Using ArgoCD Pull Request Generator to review application modifications</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Tue, 11 Apr 2023 14:29:12 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/using-argocd-pull-request-generator-to-review-application-modifications-236e</link>
      <guid>https://dev.to/camptocamp-ops/using-argocd-pull-request-generator-to-review-application-modifications-236e</guid>
      <description>&lt;p&gt;As a developer, when modifications are pushed to a feature branch, you and your team want to test this new feature. If you have the chance to work with a stateless application, you can deploy another instance of the application with modifications from the feature branch.&lt;/p&gt;

&lt;p&gt;An interesting feature of &lt;a href="https://github.com/argoproj/argo-cd"&gt;ArgoCD&lt;/a&gt; is the &lt;a href="https://argocd-applicationset.readthedocs.io/en/stable/Generators-Pull-Request/"&gt;Pull Request Generator&lt;/a&gt;. It's a generator for &lt;a href="https://argocd-applicationset.readthedocs.io/en/stable/"&gt;ApplicationSet&lt;/a&gt;. An ApplicationSet is a template of ArgoCD &lt;a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/application.yaml"&gt;Application&lt;/a&gt; associated with a generator. Generator can be a &lt;a href="https://argocd-applicationset.readthedocs.io/en/stable/Generators-Git/#git-generator-directories"&gt;directory&lt;/a&gt;: an application will be created for every sub-folder. There is also the &lt;a href="https://argocd-applicationset.readthedocs.io/en/stable/Generators-Cluster/"&gt;Cluster generator&lt;/a&gt; that deploy the same Application but in every cluster managed by ArgoCD.&lt;/p&gt;

&lt;p&gt;The syntax for the Pull Request generator is quite simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ApplicationSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapps&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pullRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;camptocamp&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myrepository&lt;/span&gt;
        &lt;span class="na"&gt;tokenRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-token&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;token&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="s"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Before testing this feature what is expected ?&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A new ArgoCD Application is created when a pull request is created with a "deploy" label&lt;/li&gt;
&lt;li&gt;In the template of the Application we can use metadata of the pull request: ID, title, description, labels, source and target branch name, commit ref, …&lt;/li&gt;
&lt;li&gt;A comment is added to the PR when the Application is deployed and Synced with the available URLs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now we will see how to implement this ;-)&lt;/p&gt;

&lt;h1&gt;
  
  
  Workflow before Implementation
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Source Code Repository
&lt;/h2&gt;

&lt;p&gt;Let's start with a simple application repository: &lt;code&gt;git@github.com:camptocamp/frontend.git&lt;/code&gt;&lt;br&gt;
Let's consider that this repository has a github action that builds a container image when a pull request is opened. The image is tagged with the short commit hash and the concatenation of the branch name and the short commit hash. For example, if the feature branch is name &lt;code&gt;update_lib&lt;/code&gt; with the last commit: &lt;code&gt;4a9b29e&lt;/code&gt;, the following tags will be generated: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;4a9b29e&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;update_lib-4a9b29e&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Deployment Repository
&lt;/h2&gt;

&lt;p&gt;There is another git repository: &lt;code&gt;git@github.com:camptocamp/argocd-project-foo-apps.git&lt;/code&gt; to describe what needs to be deployed in each kubernetes cluster (dev, int, …). In this repository, we have one folder per environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps
├── dev
│   ├── backend
│   └── frontend
├── int
│   ├── backend
│   └── frontend
└── prod
    ├── backend
    └── frontend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are using an ApplicationSet to deploy every component defined in this git repository using the directory generator. For example, for "dev" env:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ApplicationSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo-dev&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;git&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;directories&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/dev/*&lt;/span&gt;
      &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git@github.com:camptocamp/argocd-project-foo-apps.git&lt;/span&gt;
      &lt;span class="na"&gt;revision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo-dev-{{path.basename}}&lt;/span&gt; &lt;span class="c1"&gt;# sub folder name&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;{{path}}'&lt;/span&gt; &lt;span class="c1"&gt;# full path&lt;/span&gt;
        &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git@github.com:camptocamp/argocd-project-foo-apps.git&lt;/span&gt;
        &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
      &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo-dev&lt;/span&gt;
        &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ApplicationSet will create one ArgoCD Application per folder in &lt;code&gt;apps/dev/&lt;/code&gt;. Imagine that we have the following structure in git :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;apps
└── dev
    ├── backend
    │   ├── Chart.yaml
    │   └── values.yaml
    ├── database
    │   ├── Chart.yaml
    │   └── values.yaml
    └── frontend
        ├── Chart.yaml
        └── values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This will generate 3 Argocd Applications : &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;foo-dev-database&lt;/li&gt;
&lt;li&gt;foo-dev-backend&lt;/li&gt;
&lt;li&gt;foo-dev-frontend&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  Review App Workflow
&lt;/h1&gt;

&lt;p&gt;When a pull request is opened for a specific application, for example the frontend, we want to deploy a new instance of this specific component. We will monitor pull requests on the frontend repository.&lt;br&gt;
So we want to create an additional ApplicationSet to deploy the frontend if a pull request is created with the label &lt;code&gt;deploy&lt;/code&gt; in the frontend git repository.&lt;/p&gt;
&lt;h1&gt;
  
  
  Implementation
&lt;/h1&gt;
&lt;h2&gt;
  
  
  Create a token to access GitHub API
&lt;/h2&gt;

&lt;p&gt;First step is to &lt;a href="https://github.com/settings/tokens"&gt;create a token&lt;/a&gt; to access the frontend repository.&lt;/p&gt;

&lt;p&gt;Then we need to deploy this token as a kubernetes secret in the cluster :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;export &lt;/span&gt;&lt;span class="nv"&gt;GITHUB_TOKEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;ghp_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
kubectl create secret generic github-token &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--from-literal&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nv"&gt;$GITHUB_TOKEN&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-o&lt;/span&gt; yaml &lt;span class="nt"&gt;--dry-run&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;client &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; github-token-secret.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you follow the GitOps principles to deploy manifests in the cluster, you can commit this file.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a webhook
&lt;/h2&gt;

&lt;p&gt;For a better user experience, we can &lt;a href="https://github.com/Vampouille/demo-argocd-review-app/settings/hooks"&gt;setup a webhook&lt;/a&gt; that will notify ArgoCD when something changes on Github.&lt;br&gt;
This webhook needs to be defined in the source code repository, where pull requests are created.&lt;br&gt;
Go to the repository "Settings" and then "Webhooks". Click on the "Add webhook" button.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The Payload URL is the URL to access ArgoCD with the path &lt;code&gt;/api/webhook&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The Content Type should be set to &lt;code&gt;application/json&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;It's a good idea to set a "Secret", just use a random string, we will use this random string in the next step.&lt;/li&gt;
&lt;li&gt;Regarding events, we need to individually selects events and choose "Pull requests" events.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Deploy the new ApplicationSet
&lt;/h2&gt;

&lt;p&gt;This new ApplicationSet will use the "Pull request" generator, this "generator" will monitor pull requests on the source code repository. Just create and commit a file with the following ApplicationSet manifest and deploy this new manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argoproj.io/v1alpha1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ApplicationSet&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo-dev-review-frontend&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;argocd&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;generators&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;pullRequest&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;github&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;owner&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;camptocamp&lt;/span&gt;
        &lt;span class="na"&gt;repo&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;frontend&lt;/span&gt; &lt;span class="c1"&gt;# This is the application source code repo&lt;/span&gt;
        &lt;span class="na"&gt;tokenRef&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;secretName&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;github-token&lt;/span&gt;
          &lt;span class="na"&gt;key&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;token&lt;/span&gt;
        &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;deploy&lt;/span&gt; &lt;span class="c1"&gt;# label on PR that trigger review app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;foo-dev-frontend-{{branch}}-{{number}}'&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;source&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;repoURL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;git@github.com:camptocamp/argocd-project-foo-apps.git&lt;/span&gt;
        &lt;span class="na"&gt;targetRevision&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;main&lt;/span&gt;
        &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/dev/frontend&lt;/span&gt;
        &lt;span class="na"&gt;helm&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;image.tag"&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{branch}}-{{head_sha}}"&lt;/span&gt; &lt;span class="c1"&gt;# override of the image tag&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;ingress.prefix"&lt;/span&gt;
            &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;{{branch}}"&lt;/span&gt; &lt;span class="c1"&gt;# add a prefix to the URL &lt;/span&gt;
      &lt;span class="na"&gt;project&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;default&lt;/span&gt;
      &lt;span class="na"&gt;destination&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;server&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;https://kubernetes.default.svc&lt;/span&gt;
        &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;foo-dev&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ApplicationSet will deploy a new instance of the frontend for each pull request with the &lt;code&gt;deploy&lt;/code&gt; label. The image tag will be overridden with the branch name and the short commit hash and a unique prefix will be added to the URL to avoid conflicts with other instances. Finally, the name of the release is also unique as it's the same as the Application name: &lt;code&gt;foo-dev-frontend-{{branch}}-{{number}}&lt;/code&gt;, this should also avoid conflicts on object names.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure ArgoCD to use secret for webhook
&lt;/h2&gt;

&lt;p&gt;The last step is to protect the ArgoCD webhook with a password. The ArgoCD Helm chart allows setting a secret for the github webhook endpoint : &lt;code&gt;configs.secret.githubSecret&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Testing
&lt;/h2&gt;

&lt;p&gt;Now it's time to test. For this step, you just need to make a modification in the source repository and create a pull request for this. Please wait until the container image is built. Then by adding the &lt;code&gt;deploy&lt;/code&gt; label a new ArgoCD app should be created :-)&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;This new ArgoCD feature is very interesting. Maybe it can help to have feedback on the pull requests to see the status of the review app. It can also be interesting to have the list of URLs available. ArgoCD already shows this information in the webUI. So maybe just a link to the ArgocD application is enough.&lt;/p&gt;

&lt;p&gt;Camptocamp will participate in the KubeCon at Amsterdam from 18 to 21 april. Maybe we can meet there and discuss development workflows that really help developers.&lt;/p&gt;

</description>
      <category>argocd</category>
      <category>devops</category>
      <category>pullrequest</category>
    </item>
    <item>
      <title>Integrate an Application with Prometheus Operator and Package with a Helm Chart</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Mon, 19 Jul 2021 08:59:28 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/integrate-an-application-with-prometheus-operator-and-package-with-a-helm-chart-1159</link>
      <guid>https://dev.to/camptocamp-ops/integrate-an-application-with-prometheus-operator-and-package-with-a-helm-chart-1159</guid>
      <description>&lt;p&gt;In the previous posts, we saw:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to implement metrics in applications&lt;/li&gt;
&lt;li&gt;how to run the monitoring stack locally&lt;/li&gt;
&lt;li&gt;how to test and debug metrics generated by a simple Python Flask application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this post we will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;use Kubernetes Custom Resources to integrate our application with the Prometheus Operator&lt;/li&gt;
&lt;li&gt;define some alerts based on the metrics generated by the application&lt;/li&gt;
&lt;li&gt;deploy a custom dashboard in Grafana&lt;/li&gt;
&lt;li&gt;package everything in a Helm chart, including a Grafana dashboard&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Connect Prometheus to your Application
&lt;/h2&gt;

&lt;p&gt;Prometheus will retrieve metrics from &lt;em&gt;Pods&lt;/em&gt; with a &lt;code&gt;/metrics&lt;/code&gt; HTTP endpoint. If the Prometheus Operator is deployed in your Kubernetes Cluster, the discovery of the &lt;em&gt;Pods&lt;/em&gt; is done by deploying one of the following custom Kubernetes objects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#servicemonitor" rel="noopener noreferrer"&gt;&lt;em&gt;ServiceMonitor&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#podmonitor" rel="noopener noreferrer"&gt;&lt;em&gt;PodMonitor&lt;/em&gt;&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Using &lt;em&gt;ServiceMonitor&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;When a &lt;em&gt;ServiceMonitor&lt;/em&gt; object is deployed, Prometheus will create one target per address defined in the &lt;em&gt;Endpoints&lt;/em&gt; object linked to the &lt;em&gt;Service&lt;/em&gt;. This means every &lt;em&gt;Pod&lt;/em&gt; is in a ready status used by the &lt;em&gt;Service&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For example, if you have the following &lt;em&gt;Service&lt;/em&gt; and &lt;em&gt;Deployment&lt;/em&gt; in your cluster:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Service&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ClusterIP&lt;/span&gt;
  &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;
    &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;80&lt;/span&gt;
    &lt;span class="na"&gt;protocol&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;TCP&lt;/span&gt;
    &lt;span class="na"&gt;targetPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;apps/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Deployment&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
  &lt;span class="na"&gt;template&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
        &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
        &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
    &lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ghcr.io/camptocamp/course_docker_backend:python&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;containerPort&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8080&lt;/span&gt;
          &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;As defined in the &lt;em&gt;Deployment&lt;/em&gt;’s &lt;code&gt;spec.template.metadata.labels&lt;/code&gt; field, &lt;em&gt;Pods&lt;/em&gt; will have the following labels: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;component: backend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;instance: app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name: containers-my-app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;em&gt;Service&lt;/em&gt; has a selector that matches labels of the &lt;em&gt;Pod&lt;/em&gt;. Therefore the &lt;em&gt;Service&lt;/em&gt; will load balance traffic to &lt;em&gt;Pods&lt;/em&gt; deployed by the &lt;em&gt;Deployment&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;ServiceMonitor&lt;/em&gt; objects also use a selector to discover which &lt;em&gt;Services&lt;/em&gt; need to be monitored. Prometheus will scrape metrics from every &lt;em&gt;Pods&lt;/em&gt; behind selected &lt;em&gt;Services&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;For example, to retrieve metrics from our &lt;em&gt;Pods&lt;/em&gt;, we can deploy the following &lt;em&gt;ServiceMonitor&lt;/em&gt; object:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring.coreos.com/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceMonitor&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
    &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
  &lt;span class="na"&gt;endpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Prometheus Operator will search for &lt;em&gt;Services&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in the &lt;code&gt;my-app&lt;/code&gt; namespace,&lt;/li&gt;
&lt;li&gt;with the following labels:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;component: backend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;instance: app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name: containers-my-app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;with a port named: &lt;code&gt;http&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;It will then use the &lt;em&gt;Service&lt;/em&gt; selector to find &lt;em&gt;Pods&lt;/em&gt;. As a result, one target per &lt;em&gt;Pod&lt;/em&gt; will be created in the Prometheus configuration.&lt;/p&gt;

&lt;h3&gt;
  
  
  Using &lt;em&gt;PodMonitor&lt;/em&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;PodMonitor&lt;/em&gt; objects use a selector to find &lt;em&gt;Pods&lt;/em&gt; directly. No &lt;em&gt;Service&lt;/em&gt; needs to be deployed.&lt;/p&gt;

&lt;p&gt;For our &lt;em&gt;Pods&lt;/em&gt;, we can use the following &lt;em&gt;PodMonitor&lt;/em&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring.coreos.com/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PodMonitor&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;acomponent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
    &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;namespaceSelector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchNames&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
  &lt;span class="na"&gt;podMetricsEndpoints&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;port&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The Prometheus operator will search for &lt;em&gt;Pods&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;in the &lt;code&gt;my-app&lt;/code&gt; namespace,&lt;/li&gt;
&lt;li&gt;with the following labels:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;component: backend&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;instance: app&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;name: containers-my-app&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;with a port named: &lt;code&gt;webapp&lt;/code&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;For each &lt;em&gt;Pod&lt;/em&gt;, a new target will be added to the Prometheus configuration.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Alerts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why use Alerts?
&lt;/h3&gt;

&lt;p&gt;Gathering and storing metrics is very useful to investigate when something goes wrong. But there are often some modifications of one or a combination of metrics before a service becomes completely unusable.&lt;/p&gt;

&lt;p&gt;A common example of this is remaining free disk space. Fixing hard thresholds with arbitrary values on disk space is usually inefficient (you might actually end up with 95%, 100% and 101% thresholds). What needs to be monitored is actually the estimated time left until the disk is full, which can be obtained by running &lt;a href="https://prometheus.io/docs/prometheus/latest/querying/functions/#predict_linear" rel="noopener noreferrer"&gt;a time regression on the metric&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Some other examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;For an online store, having no purchases during the afternoon is unusual, maybe there is an issue that blocks users in the payment process.&lt;/li&gt;
&lt;li&gt;If the ratio of HTTP responses with code 500 suddenly increases, investigation is needed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the purpose of alerts: when such events are detected, the system notifies the right person, allowing you to keep your eyes off dashboards.&lt;/p&gt;

&lt;p&gt;After investigating and finding the root cause, you should always ask yourself if you can build an alert to detect such a case. There is also the possibility of increasing the observability if some metrics are missing.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to define Alerts?
&lt;/h3&gt;

&lt;p&gt;The Prometheus Operator allows the definition of alerts with a custom Kubernetes object: &lt;a href="https://github.com/prometheus-operator/prometheus-operator/blob/master/Documentation/api.md#prometheusrule" rel="noopener noreferrer"&gt;&lt;em&gt;PrometheusRule&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In this custom resource, you can define multiple alerts: &lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring.coreos.com/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PrometheusRule&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webapp&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-app&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
    &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;kube-prometheus-stack&lt;/span&gt;
    &lt;span class="na"&gt;release&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom&lt;/span&gt;
&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;groups&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;rules&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;alert&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;NoProductViewSince1h&lt;/span&gt;
          &lt;span class="na"&gt;expr&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;(view_total - view_total offset 1h) &amp;lt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;
          &lt;span class="na"&gt;for&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;5m&lt;/span&gt;
          &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;severity&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Critical&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;In the example above, only one alert is defined. &lt;/p&gt;

&lt;p&gt;Find below what needs to be defined for usual cases for each alert:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;alert&lt;/code&gt;: the alert name&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;expr&lt;/code&gt;: a PromQL expression that triggers an alert if it returns something. This is why most of the time a threshold is used. With the &lt;code&gt;offset&lt;/code&gt; function we compute the views from the past hour, if the result is above the threshold &lt;code&gt;1&lt;/code&gt;, then an alert is created and pushed to AlertManager.&lt;/li&gt;
&lt;li&gt;Optional &lt;code&gt;labels&lt;/code&gt;: a set of labels, usually used for alert severity&lt;/li&gt;
&lt;li&gt;Optional &lt;code&gt;for&lt;/code&gt;: delays triggering the alert. The PromQL expression must return some sample during the duration of the field &lt;code&gt;for&lt;/code&gt;, before an alert is triggered.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;There are many selectors involved in this process: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;selector on &lt;em&gt;ServiceMonitor&lt;/em&gt; to find &lt;em&gt;Services&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;selector on &lt;em&gt;Service&lt;/em&gt; to find &lt;em&gt;Pods&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;selector on &lt;em&gt;PodMonitor&lt;/em&gt; to find &lt;em&gt;Pods&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There are also selectors to discover &lt;em&gt;ServiceMonitor&lt;/em&gt;, &lt;em&gt;PodMonitor&lt;/em&gt;, and &lt;em&gt;PrometheusRule&lt;/em&gt; objects. Those selectors are defined in the &lt;em&gt;Prometheus&lt;/em&gt; object using the following fields:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Discovery of &lt;em&gt;ServiceMonitor&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spec.serviceMonitorSelector&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.serviceMonitorNamespaceSelector&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Discovery of &lt;em&gt;PodMonitor&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spec.podMonitorSelector&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.podMonitorNamespaceSelector&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Dicovery of &lt;em&gt;PrometheusRule&lt;/em&gt;:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;spec.ruleSelector&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spec.ruleNamespaceSelector&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fprometheus-operator%2Fprometheus-operator%2Fraw%2Fmaster%2FDocumentation%2Fcustom-metrics-elements.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fgithub.com%2Fprometheus-operator%2Fprometheus-operator%2Fraw%2Fmaster%2FDocumentation%2Fcustom-metrics-elements.png" alt="Selectors used by Prometheus Operator"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Check Selectors
&lt;/h3&gt;

&lt;p&gt;If the target is not discovered by Prometheus:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Check that your &lt;em&gt;ServiceMonitor&lt;/em&gt; or &lt;em&gt;PodMonitor&lt;/em&gt; is deployed in a &lt;em&gt;Namespace&lt;/em&gt; that matches the namespace selector in the &lt;em&gt;Prometheus&lt;/em&gt; object.&lt;/li&gt;
&lt;li&gt;Check that labels on your &lt;em&gt;ServiceMonitor&lt;/em&gt; or &lt;em&gt;PodMonitor&lt;/em&gt; match the selector in the &lt;em&gt;Prometheus&lt;/em&gt; object.&lt;/li&gt;
&lt;li&gt;Check that the selector on your &lt;em&gt;ServiceMonitor&lt;/em&gt; or &lt;em&gt;PodMonitor&lt;/em&gt; matches labels defined in the &lt;em&gt;Service&lt;/em&gt; or &lt;em&gt;Pod&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Check that the &lt;em&gt;Service&lt;/em&gt; or &lt;em&gt;Pod&lt;/em&gt; are deployed in the &lt;em&gt;Namespace&lt;/em&gt; selected by the namespace selector defined in the &lt;em&gt;ServiceMonitor&lt;/em&gt; or &lt;em&gt;PodMonitor&lt;/em&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In order to check a label selector, you can use the &lt;code&gt;-l&lt;/code&gt; option of &lt;code&gt;kubectl&lt;/code&gt;. For example, to check the following selector in a &lt;em&gt;ServiceMonitor&lt;/em&gt;:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

  &lt;span class="na"&gt;selector&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;matchLabels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;component&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;backend&lt;/span&gt;
      &lt;span class="na"&gt;instance&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;app&lt;/span&gt;
      &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containers-my-app&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;run the following command:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

kubectl &lt;span class="nt"&gt;-l&lt;/span&gt; &lt;span class="nv"&gt;component&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;backend,instance&lt;span class="o"&gt;=&lt;/span&gt;app,name&lt;span class="o"&gt;=&lt;/span&gt;containers-my-app get service


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h3&gt;
  
  
  Check Port Name or Number
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Check that the port number or name matches a port defined in a &lt;em&gt;Service&lt;/em&gt; or a &lt;em&gt;Pod&lt;/em&gt;. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;ServiceMonitor&lt;/em&gt; references either an incoming port defined in the &lt;em&gt;Service&lt;/em&gt; or a &lt;em&gt;Pod&lt;/em&gt; port:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;ServiceMonitor.spec.endpoints.port&lt;/code&gt; references the name of a &lt;em&gt;Service&lt;/em&gt; port: &lt;code&gt;Service.spec.ports.name&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;
&lt;code&gt;ServiceMonitor.spec.endpoints.targetPort&lt;/code&gt; references a &lt;em&gt;Pod&lt;/em&gt; port: &lt;code&gt;Pod.spec.containers.ports.containerPort&lt;/code&gt; or &lt;code&gt;Pod.spec.containers.ports.name&lt;/code&gt; &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;PodMonitor&lt;/em&gt; references port defined on &lt;em&gt;Pod&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;PodMonitor.spec.podMetricsEndpoints.port&lt;/code&gt; reference the name of a &lt;em&gt;Pod&lt;/em&gt; port: &lt;code&gt;Pod.spec.containers.ports.name&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that Prometheus will only use &lt;em&gt;Pods&lt;/em&gt; with a Ready state.&lt;/p&gt;
&lt;h2&gt;
  
  
  Create Grafana Dashboards as ConfigMaps
&lt;/h2&gt;

&lt;p&gt;Grafana includes an auto discovery mechanism for dashboards. Any &lt;em&gt;ConfigMap&lt;/em&gt; with a label &lt;code&gt;grafana_dashboard=1&lt;/code&gt; is loaded into Grafana.&lt;/p&gt;

&lt;p&gt;The following &lt;em&gt;ConfigMap&lt;/em&gt; will create a minimal dashboard in Grafana. Note that this &lt;em&gt;ConfigMap&lt;/em&gt; needs to be deployed in the same &lt;em&gt;Namespace&lt;/em&gt; as Grafana. &lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-dashboard&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;grafana_dashboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dashboard.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{ "title": "Product Views",&lt;/span&gt;
      &lt;span class="s"&gt;"time": { "from": "now-6h", "to": "now" },&lt;/span&gt;
      &lt;span class="s"&gt;"editable": false,&lt;/span&gt;
      &lt;span class="s"&gt;"panels": [ {&lt;/span&gt;
          &lt;span class="s"&gt;"gridPos": { "h": 9, "w": 12, "x": 0, "y": 0 },&lt;/span&gt;
          &lt;span class="s"&gt;"id": 2,&lt;/span&gt;
          &lt;span class="s"&gt;"targets": [ {&lt;/span&gt;
              &lt;span class="s"&gt;"exemplar": true,&lt;/span&gt;
              &lt;span class="s"&gt;"expr": "rate(view_total[2m])",&lt;/span&gt;
              &lt;span class="s"&gt;"interval": "",&lt;/span&gt;
              &lt;span class="s"&gt;"legendFormat": "{{product}}",&lt;/span&gt;
              &lt;span class="s"&gt;"refId": "A"&lt;/span&gt;
            &lt;span class="s"&gt;} ],&lt;/span&gt;
          &lt;span class="s"&gt;"title": "Product View",&lt;/span&gt;
          &lt;span class="s"&gt;"type": "timeseries"&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;]&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
  
  
  Package Monitoring Objects with Applications using Helm Charts
&lt;/h2&gt;

&lt;p&gt;Including monitoring objects in Application Helm Charts is a good way to maintain the observability layer of an application. The monitoring components can be versioned with application packaging. Also the deployment of monitoring can follow the same workflow as the application.&lt;/p&gt;

&lt;p&gt;I will not explain how to package an application, but I’ll demonstrate how to include the following elements: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Dashboard: &lt;em&gt;ConfigMap&lt;/em&gt; with dashboard code&lt;/li&gt;
&lt;li&gt;Alerts: &lt;em&gt;PrometheusRule&lt;/em&gt; with alerts definition&lt;/li&gt;
&lt;li&gt;Metrics endpoint: &lt;em&gt;PodMonitor&lt;/em&gt; or &lt;em&gt;ServiceMonitor&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In the chart’s &lt;code&gt;values.yaml&lt;/code&gt;, add a new section for monitoring:&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;monitoring&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;alerts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
  &lt;span class="na"&gt;dashboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;The &lt;code&gt;monitoring.alerts&lt;/code&gt; values controls the deployment of the &lt;em&gt;PrometheusRule&lt;/em&gt; object. The deployment of the &lt;em&gt;ConfigMap&lt;/em&gt; for the dashboard is controlled by &lt;code&gt;monitoring.dashboard&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For the &lt;em&gt;PodMonitor&lt;/em&gt; or &lt;em&gt;ServiceMonitor&lt;/em&gt; objects, we can check if the Prometheus Operator is installed using the &lt;a href="https://helm.sh/docs/chart_template_guide/builtin_objects/" rel="noopener noreferrer"&gt;.Capabilities.APIVersions.Has&lt;/a&gt; function:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- if .Capabilities.APIVersions.Has "servicemonitor.monitoring.coreos.com/v1"&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring.coreos.com/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ServiceMonitor&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Additionally, for alerts and dashboards, we can check the "values" set on the Helm release:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- if .Capabilities.APIVersions.Has "prometheusrule.monitoring.coreos.com/v1"&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- if .Values.monitoring.alerts&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;monitoring.coreos.com/v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;PrometheusRule&lt;/span&gt;
&lt;span class="nn"&gt;...&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;A common workflow to maintain a dashboard is to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;edit dashboards in the Grafana Web UI&lt;/li&gt;
&lt;li&gt;copy the JSON model from Web UI&lt;/li&gt;
&lt;li&gt;paste the JSON to a file in the Helm Chart&lt;/li&gt;
&lt;li&gt;commit and push modifications&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;⚠ If the JSON representation of the dashboard is stored in the &lt;em&gt;ConfigMap&lt;/em&gt; code, you will have to indent the content properly:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;my-dashboard&lt;/span&gt;
  &lt;span class="na"&gt;labels&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;grafana_dashboard&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1"&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;dashboard.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
    &lt;span class="s"&gt;{ "title": "Product Views",&lt;/span&gt;
      &lt;span class="s"&gt;"time": { "from": "now-6h", "to": "now" },&lt;/span&gt;
      &lt;span class="s"&gt;"editable": false,&lt;/span&gt;
      &lt;span class="s"&gt;"panels": [ {&lt;/span&gt;
          &lt;span class="s"&gt;"gridPos": { "h": 9, "w": 12, "x": 0, "y": 0 },&lt;/span&gt;
          &lt;span class="s"&gt;"id": 2,&lt;/span&gt;
          &lt;span class="s"&gt;"targets": [ {&lt;/span&gt;
              &lt;span class="s"&gt;"exemplar": true,&lt;/span&gt;
              &lt;span class="s"&gt;"expr": "rate(view_total[2m])",&lt;/span&gt;
              &lt;span class="s"&gt;"interval": "",&lt;/span&gt;
              &lt;span class="s"&gt;"legendFormat": "{{product}}",&lt;/span&gt;
              &lt;span class="s"&gt;"refId": "A"&lt;/span&gt;
            &lt;span class="s"&gt;} ],&lt;/span&gt;
          &lt;span class="s"&gt;"title": "Product View",&lt;/span&gt;
          &lt;span class="s"&gt;"type": "timeseries"&lt;/span&gt;
        &lt;span class="s"&gt;}&lt;/span&gt;
      &lt;span class="s"&gt;]&lt;/span&gt;
    &lt;span class="s"&gt;}&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;It is generally easier to store the dashboard code in a dedicated file and then load the contents in the &lt;em&gt;ConfigMap&lt;/em&gt; with some Helm templating functions:&lt;/p&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;p&gt;&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- if .Capabilities.APIVersions.Has "prometheusrule.monitoring.coreos.com/v1"&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;br&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- if .Values.monitoring.dashboard&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;&lt;br&gt;
&lt;span class="s"&gt;…&lt;/span&gt;&lt;br&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;&lt;br&gt;&lt;br&gt;
  &lt;span class="na"&gt;dashboard.json&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;&lt;br&gt;&lt;br&gt;
    &lt;span class="s"&gt;{{ .Files.Get "dashboard.json" | trim | nindent 4 }}&lt;/span&gt;&lt;br&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;br&gt;
&lt;span class="pi"&gt;{{&lt;/span&gt;&lt;span class="nv"&gt;- end&lt;/span&gt; &lt;span class="pi"&gt;}}&lt;/span&gt;&lt;/p&gt;

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;h2&gt;
&lt;br&gt;
  &lt;br&gt;
  &lt;br&gt;
  Conclusion&lt;br&gt;
&lt;/h2&gt;

&lt;p&gt;Deploying monitoring stacks —Prometheus, Grafana, AlertManager, ElasticSearch, Loki, …— provides much more observability in a project, but at the cost of consuming more resources. Developers not using these features is a waste of resources. The project also has poor observability because only system metrics and maybe http metrics are retrieved. Adding application specific metrics and even business metrics allows you to build beautiful dashboards with colors and graphs that are very fun to report during boring review meetings.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ffu21rl2xyylx5778gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F9ffu21rl2xyylx5778gb.png" alt="grafana"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>helm</category>
      <category>kubernetes</category>
      <category>alerting</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Testing Application Monitoring Locally with a Docker Composition</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Wed, 14 Apr 2021 13:37:35 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/testing-application-monitoring-locally-with-a-docker-composition-47hn</link>
      <guid>https://dev.to/camptocamp-ops/testing-application-monitoring-locally-with-a-docker-composition-47hn</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/camptocamp-ops/implement-prometheus-metrics-in-a-flask-application-p18"&gt;previous post&lt;/a&gt;, we saw how to implement metrics in a simple Python Flask application. In this post we will see how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;start a local monitoring stack with a Docker composition&lt;/li&gt;
&lt;li&gt;configure Prometheus to scrape metrics from our application&lt;/li&gt;
&lt;li&gt;build a Grafana dashboard&lt;/li&gt;
&lt;li&gt;commit the Dashboard definition so other developers can also use it&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8agspyl7u60gvuptmaj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fl8agspyl7u60gvuptmaj.png" alt="final-grafana-dashboard"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Before launching an application in a Kubernetes cluster, we need to make sure that it generates the right metrics. This will be done using a Prometheus stack running in a Docker composition.&lt;/p&gt;

&lt;p&gt;This test Prometheus stack will be made of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt;: Core component that will:

&lt;ul&gt;
&lt;li&gt;scrape application metrics &lt;/li&gt;
&lt;li&gt;store metrics in a time series database&lt;/li&gt;
&lt;li&gt;expose metrics for use with Grafana&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://grafana.com/docs/grafana/latest/" rel="noopener noreferrer"&gt;Grafana&lt;/a&gt;: Visualization of Prometheus metrics &lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;To be able to run this Docker composition, you will need to &lt;a href="https://docs.docker.com/get-docker/" rel="noopener noreferrer"&gt;install Docker&lt;/a&gt; and &lt;a href="https://docs.docker.com/compose/install/" rel="noopener noreferrer"&gt;install docker-compose&lt;/a&gt;.&lt;/p&gt;

&lt;h1&gt;
  
  
  Start Prometheus
&lt;/h1&gt;

&lt;p&gt;Let's write a &lt;code&gt;docker-compose.yml&lt;/code&gt; file that will start a Docker container for Prometheus:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.26.0&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;em&gt;Be sure to use only spaces for indentation in the YAML file, not tabs.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Start the Docker composition with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This composition starts Prometheus with the default configuration and allows access to the web interface on &lt;a href="http://localhost:9090/" rel="noopener noreferrer"&gt;http://localhost:9090/&lt;/a&gt;.&lt;br&gt;
You will be redirected to the "Graph" page where you can query metrics using the &lt;a href="https://prometheus.io/docs/prometheus/latest/querying/examples/" rel="noopener noreferrer"&gt;PromQL&lt;/a&gt; format.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8johj968a72mq5voiazq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8johj968a72mq5voiazq.png" alt="prometheus-home"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="http://localhost:9090/targets" rel="noopener noreferrer"&gt;Status → Targets&lt;/a&gt; menu, you can find all exporters from which Prometheus retrieves metrics.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftujuneaq13su1j2miiim.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftujuneaq13su1j2miiim.png" alt="prometheus-targets-menu"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the default setup, there is only one defined target, which points to the &lt;a href="http://localhost:9090/metrics" rel="noopener noreferrer"&gt;metrics endpoint of Prometheus itself&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb41nrewopvgkt6da8wy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqb41nrewopvgkt6da8wy.png" alt="prometheus-default-target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now it's time to configure Prometheus to retrieve metrics from our application!&lt;/p&gt;

&lt;p&gt;Prometheus is running in a container but needs to access the application that runs on the host. We will start the application with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

flask run &lt;span class="nt"&gt;--host&lt;/span&gt; 0.0.0.0


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;With the new &lt;code&gt;--host&lt;/code&gt; option, our application is running on all network interfaces, including the interface used by containers. From a container, you can connect to the host with the special IP address &lt;code&gt;172.17.0.1&lt;/code&gt; (the bridge address for the &lt;code&gt;172.17.0.0&lt;/code&gt; network, linked to the host). So let's create a &lt;code&gt;prometheus.yml&lt;/code&gt; file that contains the Prometheus configuration:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;scrape_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;job_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;view_buy&lt;/span&gt;
    &lt;span class="na"&gt;metrics_path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/metrics&lt;/span&gt;
    &lt;span class="na"&gt;static_configs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;targets&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;172.17.0.1:5000&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;line 2:&lt;/em&gt; define the name of the Prometheus exporter; here we use the name of the application&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;line 5:&lt;/em&gt; IP address and port of the application&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;The &lt;code&gt;prometheus.yml&lt;/code&gt; file must be in the same folder as &lt;code&gt;docker-compose.yaml&lt;/code&gt;.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;We edit the &lt;code&gt;docker-compose.yaml&lt;/code&gt; as follow to inject the Prometheus configuration in the container:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.26.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Let’s restart the Docker composition with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You should now be able to see a target that points to the application running on the host on the &lt;a href="http://localhost:9090/targets" rel="noopener noreferrer"&gt;target list page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35dc5u6pas9vra53388c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F35dc5u6pas9vra53388c.png" alt="prometheus-app-target"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This target has a static label with the name of the application set in the &lt;code&gt;prometheus.yml&lt;/code&gt; file: &lt;code&gt;job="view_buy"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;We can now display all metrics from this target on the &lt;a href="http://localhost:9090/graph?g0.range_input=1h&amp;amp;g0.expr=%7Bjob%3D%22view_buy%22%7D&amp;amp;g0.tab=1" rel="noopener noreferrer"&gt;Graph page&lt;/a&gt;. In order to query the time series for our label, you just need to surround the label and value with curly braces: &lt;code&gt;{job="view_buy"}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fid0c2r4939w3we5xeocg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fid0c2r4939w3we5xeocg.png" alt="prometheus-target-metrics"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's time to generate some traffic!&lt;/p&gt;

&lt;p&gt;Open 3 terminals and run the following commands:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;watch -n 1 "curl localhost:5000/view/product1"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;watch -n 2 "curl localhost:5000/view/product2"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;watch -n 3 "curl localhost:5000/buy/product1"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the &lt;a href="http://localhost:9090/graph" rel="noopener noreferrer"&gt;Graph page&lt;/a&gt; of the Prometheus Web UI, you can query the &lt;code&gt;view_total&lt;/code&gt; metric using the following PromQL expression: &lt;a href="http://localhost:9090/graph?g0.range_input=1h&amp;amp;g0.expr=view_total&amp;amp;g0.tab=1" rel="noopener noreferrer"&gt;&lt;code&gt;view_total&lt;/code&gt;&lt;/a&gt;. A visual representation of the metrics is available in the &lt;a href="http://localhost:9090/graph?g0.range_input=1h&amp;amp;g0.expr=view_total&amp;amp;g0.tab=0" rel="noopener noreferrer"&gt;Graph tab&lt;/a&gt;, just under the "Execute" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiwm85f6bo362r9km96o.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffiwm85f6bo362r9km96o.png" alt="prometheus-graph"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Start Grafana
&lt;/h1&gt;

&lt;p&gt;Add a new service to the Docker composition by editing the &lt;code&gt;docker-compose.yml&lt;/code&gt; file:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.26.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:7.5.2&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;We need to create the &lt;code&gt;datasource.yaml&lt;/code&gt; file with the following content:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;datasources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Prometheus&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prometheus&lt;/span&gt;
    &lt;span class="na"&gt;access&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;proxy&lt;/span&gt;
    &lt;span class="na"&gt;isDefault&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;http://prometheus:9090/&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This file configures the connection between Grafana and Prometheus. The hostname of the Prometheus container is &lt;code&gt;prometheus&lt;/code&gt; because this is the name of the service in the Docker composition. Restart the Docker composition with:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;

docker-compose up &lt;span class="nt"&gt;-d&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can now access the &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;Grafana Web UI&lt;/a&gt; using the default login/password &lt;code&gt;admin/admin&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj18vvu0zedij46um4u58.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj18vvu0zedij46um4u58.png" alt="grafana-login"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Skip the password change for now:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfndwrewmza84i3pw89r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvfndwrewmza84i3pw89r.png" alt="grafana-skip"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foybg0p03azxgp881j6cl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foybg0p03azxgp881j6cl.png" alt="grafana-explore"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the &lt;a href="http://localhost:3000/explore" rel="noopener noreferrer"&gt;Explore view&lt;/a&gt;, you can use the following expression to visualize the number of views for each product: &lt;a href="http://localhost:3000/explore?orgId=1&amp;amp;left=%5B%22now-1h%22,%22now%22,%22Prometheus%22,%7B%22exemplar%22:true,%22expr%22:%22view_total%22%7D%5D" rel="noopener noreferrer"&gt;&lt;code&gt;view_total&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1x12ucs9sco47bs3qh07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1x12ucs9sco47bs3qh07.png" alt="grafana-promql"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;view_total&lt;/code&gt; metric is a &lt;em&gt;Counter&lt;/em&gt;, a metric that always increases. As suggested by Grafana, it is better to display a &lt;a href="http://localhost:3000/explore?orgId=1&amp;amp;left=%5B%22now-1h%22,%22now%22,%22Prometheus%22,%7B%22exemplar%22:true,%22expr%22:%22rate%28view_total%5B5m%5D%29%22%7D%5D" rel="noopener noreferrer"&gt;temporal derivative of the value&lt;/a&gt;, which can be achieved using the &lt;code&gt;rate()&lt;/code&gt; function.&lt;/p&gt;

&lt;p&gt;Now we have more meaningful information about the number of views of products. The unit is &lt;em&gt;view per second&lt;/em&gt;. For example, "product2" has 0.5 view per second, which makes sense as it corresponds to one view each 2 seconds.&lt;/p&gt;

&lt;p&gt;Finally, in the legend, you can find the labels of each &lt;em&gt;time series&lt;/em&gt;, one per product.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyleo29qtjkf5er8fw9w.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwyleo29qtjkf5er8fw9w.png" alt="grafana-rate"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's create a dashboard now.&lt;/p&gt;

&lt;h1&gt;
  
  
  Creating a Grafana Dashboard
&lt;/h1&gt;

&lt;p&gt;In the Grafana web UI, you can &lt;a href="http://localhost:3000/dashboard/new" rel="noopener noreferrer"&gt;create a new dashboard&lt;/a&gt;. Choose "Add an empty panel". Then type the following expression: &lt;code&gt;rate(view_total[5m])&lt;/code&gt; and validate with 'Shift+Enter'.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k0qso61x2gta3vm14k1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6k0qso61x2gta3vm14k1.png" alt="grafana-add-panel"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;To improve the legend, you can define the "Legend Format" and extract label values with the expression &lt;code&gt;{{&amp;lt;label name&amp;gt;}}&lt;/code&gt;. For our application, we will use &lt;code&gt;{{product}}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobkxl7aieibcxpaxav1k.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fobkxl7aieibcxpaxav1k.png" alt="grafana-dashboard1"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The legend looks better as we now only have the name of the product. It's also possible to add a prefix and a suffix, using e.g.: &lt;code&gt;Product {{product}} views&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Now you can click on the "Apply" button to save modifications of the dashboard. You can then add a new panel for purchases. Finally, click on the save 💾 button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vobxqtc8aevfpovjcz7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2vobxqtc8aevfpovjcz7.png" alt="grafana-dashboard2"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Persisting Metrics and Dashboards
&lt;/h1&gt;

&lt;p&gt;If you stop or restart the current Docker composition, you will lose all the metrics retrieved by Prometheus and the Dashboard you just built. To persist data, with this local setup, we will use Docker volumes.&lt;/p&gt;

&lt;p&gt;We will first create a Docker volume to persist Prometheus metrics. The time series database is stored in &lt;code&gt;/prometheus/&lt;/code&gt; inside the container so we will mount the volume on this path:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.26.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus:/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:7.5.2&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;
&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;line 7:&lt;/em&gt; we add a volume to persist the content of &lt;code&gt;/prometheus&lt;/code&gt; folder&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;last 2 lines:&lt;/em&gt; a top level &lt;code&gt;volumes&lt;/code&gt; key is used to define volumes used in the Docker composition&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, you can apply changes with &lt;code&gt;docker-compose.yml&lt;/code&gt;. Metrics are now persisted across restarts of the Docker composition. Note that mounting the volume resets all metrics previously retrieved.&lt;/p&gt;

&lt;p&gt;In order to persist the Grafana dashboard, we will create a file that contains the definition of the dashboard:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Switch back to the &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;Grafana web ui&lt;/a&gt;,&lt;/li&gt;
&lt;li&gt;Open your dashboard&lt;/li&gt;
&lt;li&gt;Go to settings ⚙ and then "JSON Model"&lt;/li&gt;
&lt;li&gt;Copy the JSON definition of the dashboard&lt;/li&gt;
&lt;li&gt;Paste the copied content into a new &lt;code&gt;dashboard.json&lt;/code&gt; file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faz5agpva942zzfjaxfmz.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Faz5agpva942zzfjaxfmz.png" alt="grafana-dashboard-settings"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5db0mmwgoxdx884it601.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5db0mmwgoxdx884it601.png" alt="grafana-json-model"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8g9810lcu281vm77d2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy8g9810lcu281vm77d2m.png" alt="grafana-json-model-copy"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The last step is to mount this dashboard in the Grafana container.&lt;/p&gt;

&lt;p&gt;Create a &lt;code&gt;dashboards.yaml&lt;/code&gt; file with the following contents:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;

&lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;provisionned&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;dashboards'&lt;/span&gt;
    &lt;span class="na"&gt;orgId&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;
    &lt;span class="na"&gt;folder&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;folderUid&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;file&lt;/span&gt;
    &lt;span class="na"&gt;disableDeletion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;editable&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;
    &lt;span class="na"&gt;updateIntervalSeconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;
    &lt;span class="na"&gt;allowUiUpdates&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;
    &lt;span class="na"&gt;options&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;/var/lib/grafana/dashboards&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;This file tells Grafana to load every dashboard defined in the &lt;code&gt;/var/lib/grafana/dashboards&lt;/code&gt;. Finally, modify the Docker composition to include &lt;code&gt;dashboards.yaml&lt;/code&gt; and &lt;code&gt;dashboard.json&lt;/code&gt; in the Grafana container:&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;

&lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;3'&lt;/span&gt;
&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;prom/prometheus:v2.26.0&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./prometheus.yml:/etc/prometheus/prometheus.yml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;prometheus:/prometheus&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;9090:9090"&lt;/span&gt;
  &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/grafana:7.5.2&lt;/span&gt;
    &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;grafana:/var/lib/grafana&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yaml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./dashboards.yaml:/etc/grafana/provisioning/dashboards/dashboards.yaml&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;./dashboard.json:/var/lib/grafana/dashboards/dashboard.json&lt;/span&gt;
    &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;3000:3000"&lt;/span&gt;

&lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;prometheus&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;grafana&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;


&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Update the Docker composition with &lt;code&gt;docker-compose up -d&lt;/code&gt;. Your dashboard should be automatically loaded into Grafana.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztf19ogdtoq0t7qg3nub.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fztf19ogdtoq0t7qg3nub.png" alt="grafana-provisionned"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Dashboard Modification Workflow
&lt;/h1&gt;

&lt;p&gt;If you modify the dashboard, remember to copy the new JSON Model to the &lt;code&gt;dashboard.json&lt;/code&gt; file and restart Grafana with &lt;code&gt;docker-compose restart grafana&lt;/code&gt;. Now, your modifications are persisted as code in the &lt;code&gt;dashboard.json&lt;/code&gt; and you can commit the dashboard definition within your project repository.&lt;/p&gt;

&lt;p&gt;If someone wants to try your project, they just need to run the application and the monitoring stack with &lt;code&gt;docker-compose up -d&lt;/code&gt;. They can then access your dashboard, see the evolution of metrics and create or modify graphs.&lt;/p&gt;

&lt;p&gt;In this post, we were able to start a monitoring stack to retrieve metrics from our test application and build a dashboard to visualize the evolution of product views and purchases. We created several files that can be committed in Git and used later by other developers.&lt;/p&gt;

&lt;h1&gt;
  
  
  Conclusion
&lt;/h1&gt;

&lt;p&gt;You are now ready to launch a monitoring stack aside your application, whichever language you’re using. You can also integrate the application as a new service in the Docker composition to ease test and demo.&lt;br&gt;
This Docker composition should help you get your application monitoring code ready for production, so you can now deploy it on a Kubernetes cluster, and avoid the usual pitfalls of a new Kubernetes deployment.&lt;/p&gt;

&lt;p&gt;In the next post, we will deploy the application and its monitoring in a Kubernetes cluster and see how to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;integrate the application monitoring with the Prometheus Operator&lt;/li&gt;
&lt;li&gt;create alerts based on application metrics&lt;/li&gt;
&lt;li&gt;package everything in a Helm Chart&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>prometheus</category>
      <category>tutorial</category>
      <category>docker</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>Implement Prometheus Metrics in a Flask Application</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Thu, 01 Apr 2021 09:41:54 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/implement-prometheus-metrics-in-a-flask-application-p18</link>
      <guid>https://dev.to/camptocamp-ops/implement-prometheus-metrics-in-a-flask-application-p18</guid>
      <description>&lt;p&gt;In the &lt;a href="https://dev.to/camptocamp-ops/intro-to-prometheus-for-developers-675"&gt;previous post&lt;/a&gt;, we introduced the principles of Prometheus metrics series, labels, and saw which kind of metrics were useful to observe applications.&lt;/p&gt;

&lt;p&gt;In this post, we will implement some metrics in a Flask application with 2 endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;/view/&amp;lt;product&amp;gt;&lt;/code&gt;: Display the product information. This page simply displays the product name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/buy/&amp;lt;product&amp;gt;&lt;/code&gt;: Purchase the product. Actually, this endpoint just displays a message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We will start with the following implementation for our Flask application in a &lt;code&gt;app.py&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/view/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"View %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/buy/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buy_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Buy %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First &lt;a href="https://flask.palletsprojects.com/en/1.1.x/installation/#installation"&gt;install Flask&lt;/a&gt; with &lt;code&gt;pip install Flask&lt;/code&gt; or &lt;code&gt;pip3 install Flask&lt;/code&gt; and then run application with &lt;code&gt;flask run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The purpose is to generate the following metrics to monitor product views and purchases:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP view_total Product view&lt;/span&gt;
&lt;span class="c"&gt;# TYPE view_total counter&lt;/span&gt;
&lt;span class="n"&gt;view_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"product1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;
&lt;span class="n"&gt;view_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"product2"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="c"&gt;# HELP buy_total Product buy&lt;/span&gt;
&lt;span class="c"&gt;# TYPE buy_total counter&lt;/span&gt;
&lt;span class="n"&gt;buy_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"product1"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h1&gt;
  
  
  How to Generate Metrics
&lt;/h1&gt;

&lt;p&gt;In order to generate metrics, we need HTTP endpoints that output text-based metrics. The default path is &lt;code&gt;/metrics&lt;/code&gt;, but it can be modified. This can be implemented with a standard "route":&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/metrics'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;         &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'view_total{product="%s"} %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;                        &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;buy_metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;         &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'buy_total{product="%s"} %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;                        &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buy_metric&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this example, the &lt;code&gt;view_metric&lt;/code&gt; and &lt;code&gt;buy_metric&lt;/code&gt; variables contain a mapping between the product name and the count of views or purchases.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;line 1:&lt;/em&gt; We create a new HTTP endpoint with the path &lt;code&gt;/metrics&lt;/code&gt;; this endpoint
will be used by Prometheus.&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;line 3:&lt;/em&gt; We initialize the result as an empty string&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;lines 4 to 6:&lt;/em&gt; For each product, we generate a line with:

&lt;ul&gt;
&lt;li&gt;metric name: &lt;code&gt;view&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;label: &lt;code&gt;product=&amp;lt;product name&amp;gt;&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;value: the count of views; this value is fetched from &lt;code&gt;view_metric&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;lines 7 to 9:&lt;/em&gt; Same for product purchases&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Maintain the Metrics Variable up-to-date
&lt;/h2&gt;

&lt;p&gt;In this approach, &lt;code&gt;view_metric&lt;/code&gt; and &lt;code&gt;buy_metric&lt;/code&gt; need to be updated elsewhere in the code when the product is viewed or bought. So the first thing to implement is a variable or object that holds metric values within application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="n"&gt;view_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/view/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="c1"&gt;# Update metric
&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;     &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;product&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;         &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;     &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;         &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="mi"&gt;10&lt;/span&gt;   &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"View %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;line 1:&lt;/em&gt; a global variable that holds the total number of views for each product&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;lines 7 and 9:&lt;/em&gt; the global variable is updated&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code for  the &lt;code&gt;metrics()&lt;/code&gt; function is really simple, it does not compute anything, but simply retrieves values from an existing variable that is kept up-to-date. So the complexity is in the &lt;code&gt;view_product()&lt;/code&gt; function. Each time a product is "viewed", a piece of code is run to maintain the &lt;code&gt;view_metric&lt;/code&gt; counter up-to-date.&lt;/p&gt;

&lt;h2&gt;
  
  
  On-demand Metrics
&lt;/h2&gt;

&lt;p&gt;Sometimes, the cost of maintaining these kinds of variables is higher than computing the values on-the-fly when the &lt;code&gt;metrics()&lt;/code&gt; function is called. With the default configuration, Prometheus queries the &lt;code&gt;/metrics&lt;/code&gt; endpoint once every 30 seconds. So if the variable needs to be updated several times during this interval, it might be a good idea to compute metrics on demand:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/metrics'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;     &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;     &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'SELECT product, count(*) FROM product_view'&lt;/span&gt;\
&lt;span class="mi"&gt;5&lt;/span&gt;                 &lt;span class="s"&gt;'GROUP BY product'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;     &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;         &lt;span class="n"&gt;metrics&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="s"&gt;'view_total{product="%s"} %s&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;'&lt;/span&gt;
&lt;span class="mi"&gt;8&lt;/span&gt;                     &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;metrics&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With this approach, we don't need to modify other parts of the code. Instead, we query the database to retrieve this information.&lt;br&gt;
Note that, in this example, the metrics concerning the purchases  are removed to improve readability.&lt;/p&gt;
&lt;h2&gt;
  
  
  Using the Prometheus Client Library
&lt;/h2&gt;

&lt;p&gt;There is probably a &lt;a href="https://prometheus.io/docs/instrumenting/clientlibs/"&gt;library for your language&lt;/a&gt; that will take care of producing Prometheus text format.&lt;/p&gt;

&lt;p&gt;This will provide &lt;code&gt;Counter&lt;/code&gt; and &lt;code&gt;Gauge&lt;/code&gt; objects to implements your metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt;  &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt;  &lt;span class="n"&gt;view_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Product view'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;  &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/view/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt;  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;      &lt;span class="c1"&gt;# Update metric
&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;      &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"View %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;line 1:&lt;/em&gt; Loading the Prometheus client library &lt;/li&gt;
&lt;li&gt;
&lt;em&gt;line 3:&lt;/em&gt; Creation of &lt;code&gt;Counter&lt;/code&gt; metrics with name and description&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;line 8:&lt;/em&gt; Here the code is far more simple as we only need to call the &lt;code&gt;inc()&lt;/code&gt; method of &lt;code&gt;Counter&lt;/code&gt; object&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Note that a &lt;code&gt;_total&lt;/code&gt; suffix will be added to the metric name because it's a &lt;code&gt;Counter&lt;/code&gt; and another metric with a &lt;code&gt;_created&lt;/code&gt; suffix will contain the timestamp of the creation of the counter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Adding Labels
&lt;/h3&gt;

&lt;p&gt;So far, we haven’t handled labels. Let's see how we can add them now:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt; 
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="n"&gt;view_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Product view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/view/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;6&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;span class="mi"&gt;7&lt;/span&gt;     &lt;span class="c1"&gt;# Update metric
&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;     &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="mi"&gt;9&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"View %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;line 3:&lt;/em&gt; an additional parameter defines the allowed labels for the &lt;code&gt;view&lt;/code&gt; metric&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;line 8:&lt;/em&gt; a call to &lt;code&gt;labels()&lt;/code&gt; allows to set label values and thus select the &lt;em&gt;time series&lt;/em&gt; that will be incremented&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, in the &lt;code&gt;metrics()&lt;/code&gt; function, we just need to retrieve all the metrics in the Prometheus text format using the &lt;code&gt;generate_latest()&lt;/code&gt; function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;generate_latest&lt;/span&gt;
&lt;span class="mi"&gt;2&lt;/span&gt;
&lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/metrics'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="mi"&gt;4&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
&lt;span class="mi"&gt;5&lt;/span&gt;     &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;generate_latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a full example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generate_latest&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;view_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Product view'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;buy_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'buy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Product buy'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;'product'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/view/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;view_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;view_metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"View %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/buy/&amp;lt;id&amp;gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;buy_product&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;buy_metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;labels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;product&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;inc&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;"Buy %s"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'/metrics'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;metrics&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;generate_latest&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Using Python Decorator
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://github.com/prometheus/client_python"&gt;python library&lt;/a&gt; also has some nice decorators.&lt;/p&gt;

&lt;p&gt;For example, you can track the time spent in a function by using the &lt;code&gt;@&amp;lt;metric&amp;gt;.time()&lt;/code&gt; decorator:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Summary&lt;/span&gt;

&lt;span class="n"&gt;duration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'duration_compute_seconds'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Time spent in the compute() function'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a &lt;code&gt;Counter&lt;/code&gt;, you can keep track of exceptions thrown in particular functions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;prometheus_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;

&lt;span class="n"&gt;exception&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'compute_exception'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Exception thrown in compute() function'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="o"&gt;@&lt;/span&gt;&lt;span class="n"&gt;exception&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count_exceptions&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Random error"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  On-demand Metrics with the Prometheus Library
&lt;/h3&gt;

&lt;p&gt;The previously seen on-demand pattern can be implemented with the Prometheus Library by setting a callback function when defining the metric:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;stock_metric&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Counter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'stock'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;'Stock count'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;stock_metric&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_function&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;compute_stock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;compute_stock&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;'SELECT count(*) FROM product_stock'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can of course mix metrics managed with &lt;code&gt;inc()&lt;/code&gt; and &lt;code&gt;set()&lt;/code&gt; with other metrics using a callback function.&lt;/p&gt;

&lt;p&gt;In the next blog post, we will see:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;how to visualize the application metrics using a Docker composition that includes Prometheus and Grafana &lt;/li&gt;
&lt;li&gt;how to leverage the new metrics in order to monitor the application in a Kubernetes cluster.&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>flask</category>
      <category>python</category>
      <category>prometheus</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Intro to Prometheus for Developers</title>
      <dc:creator>Julien Acroute</dc:creator>
      <pubDate>Fri, 26 Mar 2021 14:28:37 +0000</pubDate>
      <link>https://dev.to/camptocamp-ops/intro-to-prometheus-for-developers-675</link>
      <guid>https://dev.to/camptocamp-ops/intro-to-prometheus-for-developers-675</guid>
      <description>&lt;p&gt;There are many tutorials to install &lt;a href="https://prometheus.io/" rel="noopener noreferrer"&gt;Prometheus&lt;/a&gt; in a Kubernetes cluster. Some of them explain how to install an exporter for &lt;a href="https://prometheus.io/docs/instrumenting/exporters" rel="noopener noreferrer"&gt;mainstream apps&lt;/a&gt;. See for example the following video:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag__link"&gt;
  &lt;a href="/techworld_with_nana" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__pic"&gt;
      &lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F319175%2Fc5aebaff-6768-4f5e-9c0b-b1eae091ded8.png" alt="techworld_with_nana"&gt;
    &lt;/div&gt;
  &lt;/a&gt;
  &lt;a href="/techworld_with_nana/step-by-step-tutorial-to-monitor-third-party-apps-using-prometheus-mongodb-example-3f71" class="ltag__link__link"&gt;
    &lt;div class="ltag__link__content"&gt;
      &lt;h2&gt;Step by Step tutorial to monitor third-party apps using Prometheus (MongoDB example 🍃)&lt;/h2&gt;
      &lt;h3&gt;TechWorld with Nana ・ Sep 25 '20&lt;/h3&gt;
      &lt;div class="ltag__link__taglist"&gt;
        &lt;span class="ltag__link__tag"&gt;#prometheus&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#devops&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#kubernetes&lt;/span&gt;
        &lt;span class="ltag__link__tag"&gt;#tutorial&lt;/span&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/a&gt;
&lt;/div&gt;


&lt;p&gt;But what about monitoring your own application? You may already have metrics for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cluster nodes with Node Exporter&lt;/li&gt;
&lt;li&gt;Workload in the cluster: Pod metrics, aggregation by namespace&lt;/li&gt;
&lt;li&gt;Third party apps: Load balancer, Database, Proxy&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The core component, the application itself, should also be monitored. Some metrics can be retrieved from the middleware by using a dedicated exporter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;HTTP requests: total requests, duration, sessions&lt;/li&gt;
&lt;li&gt;Memory usage: Garbage collector, cache&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We still only have technical metrics about compute resources and their usage. This kind of metrics can be retrieved from any web project, as there always are HTTP requests, DB connection pool, memory, and CPU usage.&lt;/p&gt;

&lt;p&gt;The most important indicators in your application are the ones that differ from other apps. For example, a web store app might have the following elements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cart: total active cart count, total items in active carts&lt;/li&gt;
&lt;li&gt;item: number of items available, number of items with no stock&lt;/li&gt;
&lt;li&gt;customer: number of accounts, number of accounts with a connection in the last 3 months&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can build metrics within your app to be able to see the evolution of the cart, item, or customer accounts. Each application is unique, so only the people using or contributing to the application know which metric can be created to improve the level of observability of the application.&lt;/p&gt;

&lt;p&gt;This series of blog posts will focus on adding high level metrics to existing applications. We will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Modify an application to generate metrics in the Prometheus format&lt;/li&gt;
&lt;li&gt;Connect Prometheus to the application&lt;/li&gt;
&lt;li&gt;Create Grafana dashboard and configure alerts&lt;/li&gt;
&lt;li&gt;Package monitoring stuff with the whole application using a Helm chart&lt;/li&gt;
&lt;/ul&gt;

&lt;h1&gt;
  
  
  External Exporter or Integrated in Application?
&lt;/h1&gt;

&lt;p&gt;It's always better to include code that will generate metrics within the application. But sometimes you either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;cannot modify the application code&lt;/li&gt;
&lt;li&gt;or the application already exposes some metrics, though not in the Prometheus format.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this case, you may have to create a separate application that will compute metrics based on information retrieved from the main application, and act as an adapter.&lt;/p&gt;

&lt;h1&gt;
  
  
  What needs to be generated?
&lt;/h1&gt;

&lt;p&gt;You need to generate one or more metrics. For each metric, the exporter needs to output:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a description&lt;/li&gt;
&lt;li&gt;the name of the metrics&lt;/li&gt;
&lt;li&gt;an optional list of labels&lt;/li&gt;
&lt;li&gt;a decimal value&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Metric Labels
&lt;/h2&gt;

&lt;p&gt;Labels are used to create multiple metrics with the same name which refer to different elements. Labels add &lt;em&gt;dimensions&lt;/em&gt; to metrics. With labels, you can have multiple values for the same timestamp.&lt;/p&gt;

&lt;p&gt;For example, a metric regarding disk usage can look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP disk_usage_percent Usage of disk in percent (0-100)&lt;/span&gt;
&lt;span class="c"&gt;# TYPE disk_usage_percent gauge&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt; &lt;span class="mf"&gt;63.7&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If there is more than one partition, you can use labels to identify partitions, but keep the same metrics name:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP disk_usage_percent Usage of disk in percent (0-100)&lt;/span&gt;
&lt;span class="c"&gt;# TYPE disk_usage_percent gauge&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mf"&gt;63.7&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mf"&gt;37.2&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/tmp"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mf"&gt;12.5&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For HTTP request metrics, you can use labels for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the HTTP method: &lt;code&gt;method="POST"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the path: &lt;code&gt;path="/assets/script.js"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the browser type: &lt;code&gt;browser="Firefox"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a store, you can build a sales-related metric and use labels for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the item category: &lt;code&gt;category="Tools"&lt;/code&gt;, &lt;code&gt;category="Garden and Outdoor"&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the brand: &lt;code&gt;brand="Makita"&lt;/code&gt;, &lt;code&gt;brand="Weber"&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can also create a metric for the stock and use the same labels. This way, when displaying metrics, you will be able to correlate sales with stock of a specific category using labels.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Labels can be viewed as additional dimensions for a metric.&lt;/em&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Every &lt;em&gt;time series&lt;/em&gt; is uniquely identified by its metric name and optional&lt;br&gt;
key-value pairs called labels.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prometheus Text Format
&lt;/h2&gt;

&lt;p&gt;Let's talk about the &lt;a href="https://prometheus.io/docs/instrumenting/exposition_formats/#text-based-format" rel="noopener noreferrer"&gt;Prometheus format&lt;/a&gt;. It's a text based format:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP http_requests_total The total number of HTTP requests.&lt;/span&gt;
&lt;span class="c"&gt;# TYPE http_requests_total counter&lt;/span&gt;
&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"get"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;1234027&lt;/span&gt;
&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;1027&lt;/span&gt;
&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"post"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"400"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lines starting with &lt;code&gt;#&lt;/code&gt; define metric metadata:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;# HELP &amp;lt;metric name&amp;gt; &amp;lt;description&amp;gt;&lt;/code&gt;: Human readable description&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;# TYPE &amp;lt;metric name&amp;gt; [counter|gauge|histogram|summary]&lt;/code&gt;: &lt;a href="https://prometheus.io/docs/concepts/metric_types/" rel="noopener noreferrer"&gt;Metric type&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most metrics use &lt;code&gt;counter&lt;/code&gt; or &lt;code&gt;gauge&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;counter&lt;/code&gt; metrics are always incremental, cumulative, e.g.: error count, total requests&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;gauge&lt;/code&gt; metrics can increase and decrease, e.g.: connected customers, disk usage&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The curly brackets contain a comma-separated collection of labels:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;method="get"&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;code="200"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Finally, separated by a space, is the value.&lt;/p&gt;

&lt;p&gt;So for our webstore, we can generate the following metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight prometheus"&gt;&lt;code&gt;&lt;span class="c"&gt;# HELP disk_usage_percent Usage of disk in percent (0-100)&lt;/span&gt;
&lt;span class="c"&gt;# TYPE disk_usage_percent gauge&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mf"&gt;63.4&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/var"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mf"&gt;37.6&lt;/span&gt;
&lt;span class="n"&gt;disk_usage_percent&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;partition&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/tmp"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mf"&gt;12.3&lt;/span&gt;

&lt;span class="c"&gt;# HELP http_requests_total The total number of HTTP requests.&lt;/span&gt;
&lt;span class="c"&gt;# TYPE http_requests_total counter&lt;/span&gt;
&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"GET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;1234027&lt;/span&gt;
&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"200"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;1027&lt;/span&gt;
&lt;span class="n"&gt;http_requests_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;method&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"POST"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;code&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"400"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;

&lt;span class="c"&gt;# HELP sales_total The total number of sales.&lt;/span&gt;
&lt;span class="c"&gt;# TYPE sales_total counter&lt;/span&gt;
&lt;span class="n"&gt;sales_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Makita"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;163376&lt;/span&gt;
&lt;span class="n"&gt;sales_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Bosh"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;298425&lt;/span&gt;
&lt;span class="n"&gt;sales_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Garden and Outdoor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Weber"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;18346&lt;/span&gt;
&lt;span class="n"&gt;sales_total&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Garden and Outdoor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Hyundai"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;163376&lt;/span&gt;

&lt;span class="c"&gt;# HELP stock The number of stock items.&lt;/span&gt;
&lt;span class="c"&gt;# TYPE stock gauge&lt;/span&gt;
&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Makita"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;234&lt;/span&gt;
&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Tools"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Bosh"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;456&lt;/span&gt;
&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Garden and Outdoor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Weber"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;
&lt;span class="n"&gt;stock&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Garden and Outdoor"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;brand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"Hyundai"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in this example, we mix metrics from different layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;System metrics: disk usage&lt;/li&gt;
&lt;li&gt;Middleware metrics: HTTP requests&lt;/li&gt;
&lt;li&gt;Application metrics: stocks and sales&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For system metrics, it is recommended to use a dedicated exporter like&lt;br&gt;
&lt;a href="https://github.com/prometheus/node_exporter" rel="noopener noreferrer"&gt;Node Exporter&lt;/a&gt;. For middleware, you should be able to find an exporter for your needs: &lt;a href="https://github.com/korfuri/django-prometheus" rel="noopener noreferrer"&gt;Django&lt;/a&gt;, &lt;a href="https://rabbitmq.com/prometheus.html" rel="noopener noreferrer"&gt;RabbitMQ&lt;/a&gt;, &lt;a href="https://github.com/oliver006/redis_exporter" rel="noopener noreferrer"&gt;Redis&lt;/a&gt;, &lt;a href="https://github.com/prometheus/jmx_exporter" rel="noopener noreferrer"&gt;Java JMX&lt;/a&gt;, &lt;a href="https://github.com/wrouesnel/postgres_exporter" rel="noopener noreferrer"&gt;PostgreSQL&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In the next blog post, we will start adding metrics to an existing application.&lt;/p&gt;

</description>
      <category>prometheus</category>
      <category>kubernetes</category>
      <category>observability</category>
      <category>monitoring</category>
    </item>
  </channel>
</rss>
