<?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: Shakir</title>
    <description>The latest articles on DEV Community by Shakir (@networkandcode).</description>
    <link>https://dev.to/networkandcode</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%2F593072%2Faa4f7d63-3610-499b-ba00-1b538d8dca20.jpg</url>
      <title>DEV Community: Shakir</title>
      <link>https://dev.to/networkandcode</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/networkandcode"/>
    <language>en</language>
    <item>
      <title>🚀 Sample RAG app with Strands, Reflex and S3</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Mon, 29 Sep 2025 16:16:13 +0000</pubDate>
      <link>https://dev.to/aws-builders/sample-rag-app-with-strands-reflex-and-s3-4n6m</link>
      <guid>https://dev.to/aws-builders/sample-rag-app-with-strands-reflex-and-s3-4n6m</guid>
      <description>&lt;p&gt;Hi 👋, these are some setup instructions for the app that lets store some notes for subjects 📚 in S3 as vectordb and query it with Strands. You may checkout the code &lt;a href="https://github.com/networkandcode/studynotes" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First goto S3 &amp;gt;&amp;gt; Vector buckets, and create a vector bucket there and give it some name such as &lt;em&gt;studynotes&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;Create an index there with dimension &lt;em&gt;1024&lt;/em&gt;, this is going to be the size of the embedding vector.&lt;/p&gt;

&lt;p&gt;Let's now clone the app and switch the directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/networkandcode/studynotes.git
&lt;span class="nb"&gt;cd &lt;/span&gt;studynotes
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we now have the following for our &lt;code&gt;.env&lt;/code&gt; file, add it here.&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;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"us-west-2"&lt;/span&gt;
&lt;span class="nv"&gt;AWS_S3_VECTOR_BUCKET_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;studynotes
&lt;span class="nv"&gt;AWS_S3_VECTOR_INDEX_NAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;books
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;Note that its using nova micro in the app for the LLM calls, so make sure it's allowed in Bedrock.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;⚡ Run the app with uv/reflex.&lt;br&gt;
&lt;/p&gt;

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

Creating virtual environment at: .venv

App running at: http://localhost:3000/
Backend running at: http://0.0.0.0:8000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now access the app at &lt;a href="http://localhost:3000/" rel="noopener noreferrer"&gt;http://localhost:3000/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A few screenshots below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Home page
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F183u2jwmg0mqilwser22.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F183u2jwmg0mqilwser22.png" alt="Home page" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Subjects
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fq5usmgtmn3tektkaffdk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fq5usmgtmn3tektkaffdk.png" alt="Subjects page" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Upload notes
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fen5jp0mhhc68g4lz6thf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fen5jp0mhhc68g4lz6thf.png" alt="Notes page" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Chat
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fs71em8qqellrnex21v5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fs71em8qqellrnex21v5a.png" alt="Chat page" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Ok, so that's it for the post. This was done mainly to see how we can use S3 vector with python for RAG. The code related to S3 vector operations is in this &lt;a href="https://github.com/networkandcode/studynotes/blob/4dc9682d6db0b63c8aac7fb1fa3edce76a1553eb/s3_vectors.py" rel="noopener noreferrer"&gt;file&lt;/a&gt;. The app may not be perfect yet, needs some improvement.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>rag</category>
      <category>vectordatabase</category>
      <category>llm</category>
    </item>
    <item>
      <title>New Relic Template for Strands</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Sun, 14 Sep 2025 10:05:17 +0000</pubDate>
      <link>https://dev.to/aws-builders/new-relic-template-for-strands-33p</link>
      <guid>https://dev.to/aws-builders/new-relic-template-for-strands-33p</guid>
      <description>&lt;p&gt;Hi 👋, we’ll see about observability with New Relic / OTEL, for Strands Agents that shows some quick insights such as tokens used, cost for the tokens(based on a static cost set as variable), request duration, errors etc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Before you begin
&lt;/h2&gt;

&lt;p&gt;You can check this &lt;a href="https://www.youtube.com/watch?v=aIEpu5o4fPU" rel="noopener noreferrer"&gt;video&lt;/a&gt; for explanantion of the poc app used here, basically we would be using kubernetes mcp to interact with a k3s cluster and opentelemetry will be enabled to generate observability, the observability part for strands was discussed in this &lt;a href="https://dev.to/aws-builders/otel-observability-with-langfuse-for-strands-agents-3eon"&gt;post&lt;/a&gt; and this &lt;a href="https://www.youtube.com/watch?v=iidTHzkXB8k" rel="noopener noreferrer"&gt;video&lt;/a&gt;. Optionally, if you'd like to know how to collect data on New Relic through otel collector, please check this &lt;a href="https://dev.to/aws-builders/otel-demo-with-and-eks-and-newrelic-3j52"&gt;post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Ok, so what's new 🤔, we have to just set the OTLP variables and modify the code a bit so that telemetry is sent to NewRelic endpoint. And a newrelic template was built to visualize the telemetry data. &lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The code for the lab and the dashboard configuration is present &lt;a href="https://github.com/networkandcode/networkandcode.github.io/tree/b886650457ef0d0ae5c88aceb93eb510a3640201" rel="noopener noreferrer"&gt;here&lt;/a&gt;. You can clone and checkout as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/networkandcode/networkandcode.github.io/tree/1014fbcffeda3b61d331421d2cc67d11ca98c597

&lt;span class="nb"&gt;cd &lt;/span&gt;networkandcode.github.io/strands-examples/strands-newrelic-demo/

git switch 1014fbc &lt;span class="nt"&gt;--detach&lt;/span&gt;

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;   .env.example     README.md         k8s_mcp_app.py  pyproject.toml    strands-agent-dashboard.json
..  .python-version  k8s_mcp_agent.py  main.py         set_telemetry.py  uv.lock
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ENV
&lt;/h2&gt;

&lt;p&gt;You have to first get the API key of type &lt;em&gt;Ingest License&lt;/em&gt; and set it  as &lt;code&gt;OTEL_EXPORTER_OTLP_HEADERS=api-key=&amp;lt;the-copied-value&amp;gt;&lt;/code&gt;. So the env file would now be as follows.&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;cat&lt;/span&gt; .env
&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-west-2

&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_ENDPOINT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"https://otlp.nr-data.net"&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;api-key&lt;span class="o"&gt;=&lt;/span&gt;
&lt;span class="nv"&gt;OTEL_EXPORTER_OTLP_PROTOCOL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;http/protobuf
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The set_telemetry file is modified.&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;cat &lt;/span&gt;set_telemetry.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.telemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrandsTelemetry&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_telemetry&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;strands_telemetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StrandsTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;strands_telemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup_otlp_exporter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Run
&lt;/h2&gt;

&lt;p&gt;We can now run the app, send some prompts.&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;uv run streamlit run k8s_mcp_app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.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%2Fh15z2kwqfbbc4u5e3s8m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fh15z2kwqfbbc4u5e3s8m.png" alt="Streamlit screenshot" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Newrelic
&lt;/h2&gt;

&lt;p&gt;Telemetry data should now flow to newrelic, we can visualize data 📊 and here is a screenshot from the dashboard.&lt;br&gt;
&lt;a href="https://media2.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%2F8358gcxok1kbwvukha26.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8358gcxok1kbwvukha26.png" alt="Newrelic dashboard screenshot" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's a single dashboard with 3 pages 🗂️ to show App level, Request(Trace) level and Operation(Span) level data. The dashboard is available as json here:&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;ls &lt;/span&gt;strands-agent-dashboard.json 
strands-agent-dashboard.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this can be imported to newrelic as new dashboard. You may have a look and inform if they are any errors in the dashboard. Ok so that's it for the post, thank you 🤝 for reading.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>newrelic</category>
      <category>mcp</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>OTEL Observability with Langfuse for Strands Agents</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Sat, 30 Aug 2025 06:44:51 +0000</pubDate>
      <link>https://dev.to/aws-builders/otel-observability-with-langfuse-for-strands-agents-3eon</link>
      <guid>https://dev.to/aws-builders/otel-observability-with-langfuse-for-strands-agents-3eon</guid>
      <description>&lt;p&gt;Hi 👋, in this post we shall see how to use Open Telemetry and Langfuse cloud to observe LLM calls made via Strands Agents.&lt;/p&gt;

&lt;h2&gt;
  
  
  UV
&lt;/h2&gt;

&lt;p&gt;Let's initialize our project with &lt;a href="https://docs.astral.sh/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv init strands-langfuse-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copy the code shown in this &lt;a href="https://www.youtube.com/watch?v=aIEpu5o4fPU" rel="noopener noreferrer"&gt;video&lt;/a&gt; to the project, so that the content looks like below. The final code for this lab is anyway &lt;a href="https://github.com/networkandcode/networkandcode.github.io/tree/e8c3871d779440cf0b324db564087d5109b3ac09/strands-examples/strands-langfuse-demo" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&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;cd &lt;/span&gt;strands-langfuse-demo/

&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-a&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt;  ..  .env  .python-version  README.md  k8s_mcp_agent.py  k8s_mcp_app.py  main.py  pyproject.toml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's activate the virtual environment.&lt;br&gt;
&lt;/p&gt;

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

&lt;span class="nb"&gt;source&lt;/span&gt; .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now install these dependencies: &lt;a href="https://pypi.org/project/streamlit/" rel="noopener noreferrer"&gt;streamlit&lt;/a&gt;, &lt;a href="https://pypi.org/project/strands-agents/" rel="noopener noreferrer"&gt;strands-agents&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;uv add &lt;span class="nv"&gt;streamlit&lt;/span&gt;&lt;span class="o"&gt;==&lt;/span&gt;1.49.1
uv add strands-agents[otel]&lt;span class="o"&gt;==&lt;/span&gt;1.6.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We are adding the extra otel here, as we need to enable telemetry.&lt;/p&gt;

&lt;h2&gt;
  
  
  K3S
&lt;/h2&gt;

&lt;p&gt;I am using &lt;a href="https://k3s.io/" rel="noopener noreferrer"&gt;K3S&lt;/a&gt; for launching a single node kubernetes cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-sfL&lt;/span&gt; https://get.k3s.io | sh - 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's copy the kubeconfig as we will be referring to this in our agent configuration.&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;sudo cp&lt;/span&gt; /etc/rancher/k3s/k3s.yaml &lt;span class="nb"&gt;.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Langfuse
&lt;/h2&gt;

&lt;p&gt;Go to &lt;a href="https://cloud.langfuse.com/" rel="noopener noreferrer"&gt;Langfuse cloud&lt;/a&gt; and create a new organization(for ex. my-org), inside that you can create a new project(for ex. my-llm-org). And generate api keys for the project.&lt;br&gt;
&lt;a href="https://media2.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%2Ffic4nljne7f4v2t3ozsd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffic4nljne7f4v2t3ozsd.png" alt="Generate API keys for Langfuse project" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We need to add these variables in our env file. So it looks like below.&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;cat&lt;/span&gt; .env
&lt;span class="nv"&gt;AWS_ACCESS_KEY_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;AWS_SECRET_ACCESS_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;
&lt;span class="nv"&gt;AWS_DEFAULT_REGION&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;us-west-2
&lt;span class="nv"&gt;LANGFUSE_PUBLIC_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"pk-lf-.."&lt;/span&gt;
&lt;span class="nv"&gt;LANGFUSE_SECRET_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"sk-lf-.."&lt;/span&gt;
OTEL_EXPORTER_OTLP_ENDPOINT &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"https://cloud.langfuse.com/api/public/otel"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's use this code to load the env vars and set the telemetry with otlp headers.&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;cat &lt;/span&gt;set_telemetry.py 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.telemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;StrandsTelemetry&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_telemetry&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Build Basic Auth header.
&lt;/span&gt;    &lt;span class="n"&gt;LANGFUSE_AUTH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;base64&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;b64encode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LANGFUSE_PUBLIC_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;LANGFUSE_SECRET_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;encode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# Configure OpenTelemetry endpoint &amp;amp; headers
&lt;/span&gt;    &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OTEL_EXPORTER_OTLP_HEADERS&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization=Basic &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;LANGFUSE_AUTH&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="n"&gt;strands_telemetry&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;StrandsTelemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;strands_telemetry&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setup_otlp_exporter&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we are loading env above, we can remove it from the agent code. These lines can be removed:&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;cat &lt;/span&gt;k8s_mcp_agent.py | &lt;span class="nb"&gt;grep &lt;/span&gt;dotenv
from dotenv import load_dotenv
        load_dotenv&lt;span class="o"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And replace it with the new file we added.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat k8s_mcp_agent.py | grep telemetry
from set_telemetry import set_telemetry
        set_telemetry()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Streamlit
&lt;/h2&gt;

&lt;p&gt;Let's now run our app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;streamlit run k8s_mcp_app.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I tried a prompt to get the list of namespaces.&lt;br&gt;
&lt;a href="https://media2.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%2Fpkiyjm6olopb46l7nwse.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpkiyjm6olopb46l7nwse.png" alt="Check the list of namespaces from streamlit" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can see the trace for this in langfuse.&lt;br&gt;
&lt;a href="https://media2.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%2F1mwtv3qdtfrxe4adfari.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F1mwtv3qdtfrxe4adfari.png" alt="Trace on langfuse" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can add some custom attributes to traces, let's add one by the name session.id with the trace_attributes argument.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import uuid

self.agent = Agent(
                callback_handler=None,
                model="us.amazon.nova-micro-v1:0",
                tools=tools,
                trace_attributes={
                    "session.id": str(uuid.uuid4()),
                },
            )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Which we can now see in the trace in the attributes section.&lt;br&gt;
&lt;a href="https://media2.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%2Fybpj9dny9uazj59bcd0e.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fybpj9dny9uazj59bcd0e.png" alt="Session id in trace" width="800" height="449"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The final agent code will be:&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;cat &lt;/span&gt;k8s_mcp_agent.py 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&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="n"&gt;re&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;stdio_client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StdioServerParameters&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;strands.tools.mcp&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;MCPClient&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;streamlit&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;set_telemetry&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;set_telemetry&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;uuid&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;remove_html_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text_with_html&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;text_with_out_html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;&amp;lt;[^&amp;gt;]+&amp;gt;&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text_with_html&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;text_with_out_html&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;stream_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;
    &lt;span class="n"&gt;placeholder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;empty&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;chunk&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
            &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;remove_html_tags&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;placeholder&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;KubernetesMCPAgent&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;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nf"&gt;set_telemetry&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;server_params&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;npx&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;args&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-y&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;kubernetes-mcp-server@latest&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;],&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;KUBECONFIG&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;k3s.yaml&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdio_mcp_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MCPClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;lambda&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;stdio_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="nc"&gt;StdioServerParameters&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;server_params&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;))&lt;/span&gt;

        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdio_mcp_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# Get the tools from the MCP server
&lt;/span&gt;            &lt;span class="n"&gt;tools&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdio_mcp_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list_tools_sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

            &lt;span class="c1"&gt;# Create an agent with these tools
&lt;/span&gt;            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;callback_handler&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;us.amazon.nova-micro-v1:0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                &lt;span class="n"&gt;trace_attributes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
                    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;session.id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nf"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;uuid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uuid4&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
                &lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="p"&gt;)&lt;/span&gt;


    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_prompt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdio_mcp_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;stream&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stream_async&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;stream_result&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="n"&gt;kubernetes_mcp_agent&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;KubernetesMCPAgent&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it for the post, hope we got some insights on LLM observability. Thank you for reading!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>llm</category>
      <category>mcp</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>A simple screening agent with CrewAI</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Sat, 28 Jun 2025 17:26:14 +0000</pubDate>
      <link>https://dev.to/aws-builders/a-simple-screening-agent-with-crewai-539l</link>
      <guid>https://dev.to/aws-builders/a-simple-screening-agent-with-crewai-539l</guid>
      <description>&lt;p&gt;Let's try a simple exercise with &lt;a href="https://www.crewai.com/" rel="noopener noreferrer"&gt;CrewAI&lt;/a&gt; with a single agent and task, that parses information it has from &lt;a href="https://docs.aws.amazon.com/" rel="noopener noreferrer"&gt;AWS docs&lt;/a&gt;, and prepares a list of questions that could be used for basic screening during interviews. Of course we can do this with chatgpt, gemini etc. with their UI, but having some sort of a code can also help, for repeatable tasks, and for further refining the logic and automation. Also, we shall look into some basic concepts of CrewAI such as agents, tasks, crew as we do this. Let's get started! 🚀&lt;/p&gt;

&lt;p&gt;Initialize a project with &lt;a href="https://pypi.org/project/uv/" rel="noopener noreferrer"&gt;uv&lt;/a&gt; and switch directory.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ uv init screening_agent
$ cd screening_agent/ 

$ ls -a
.               ..              .git            .gitignore      .python-version main.py         pyproject.toml  README.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Setup a virtual environment and activate it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ uv venv
$ source .venv/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add crewai as dependency and install it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat pyproject.toml | grep dependencies
dependencies = ["crewai==0.134.0"]

$ uv lock

$ uv sync  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  LLM
&lt;/h2&gt;

&lt;p&gt;I am using &lt;code&gt;gemini/gemma-3n-e2b-it&lt;/code&gt; for this exercise, the API key for which was obtained from &lt;a href="https://aistudio.google.com/apikey" rel="noopener noreferrer"&gt;Google AI studio&lt;/a&gt;. We need to setup &lt;code&gt;.env&lt;/code&gt; file to hold the key.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;GOOGLE_API_KEY=&amp;lt;Paste-your-key&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can add the llm setup in a separate python file &lt;code&gt;llm.py&lt;/code&gt;.&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="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;LLM&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;load_dotenv&lt;/span&gt;

&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="n"&gt;llm&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LLM&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini/gemma-3n-e2b-it&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;GOOGLE_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;temperature&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we have read the api key from &lt;code&gt;.env&lt;/code&gt; file and passed it to the LLM.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent
&lt;/h2&gt;

&lt;p&gt;We can then define our agent in &lt;code&gt;agents.py&lt;/code&gt;. We are using only one agent in this exercise to keep it simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from crewai import Agent
from llm import llm

screening_agent = Agent(
    role="Screening Agent for AWS interviews",
    goal="Get content from AWS official documentation about the topics: {topics}",
    backstory="You will explore relevant links from AWS official documentation https://docs.aws.amazon.com/ about the topics: {topics}"
              "Try as much as possible to gather links that have information about all the topics on the same webpage"
              "You will then prepare a total of 10 questions and their answers"
              "The number of questions to be evenly split among the topics as much as possible"
              "The length of each question should be 1 line"
              "The length of the answer should also be 1 line"
              "Display numbering for the questions from 1 to 10"
              "You also need to capture the link from which that question and answer was prepared",
    verbose=True,
    llm=llm,
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;All in plain English, we are setting proper context with the role, goal and backstory arguments. We are setting verbose to see somewhat detailed output while we run the code, and then map the llm we defined in the previous step. This means each agent can be mapped to a separate llm if required. Also, note that there is one parameter enclosed in curly braces &lt;code&gt;{topics}&lt;/code&gt;, we will be passing this later as input when we initialize the crew.&lt;/p&gt;

&lt;h2&gt;
  
  
  Task
&lt;/h2&gt;

&lt;p&gt;Let's define our task in &lt;code&gt;tasks.py&lt;/code&gt;, that will call the agent.&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="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;screening_agent&lt;/span&gt;

&lt;span class="n"&gt;screening_task&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Get key information about the topics: {topics}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Make the content understandable for non technical audience as well&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;As this task could be used by HR recruiters as well, for screening purposes&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;expected_output&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;There should be 10 sections in the output&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Each section should have Question:, Answer: and Reference Link:&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;agent&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;screening_agent&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again in plain English, give appropriate context with the description and expected_output arguments. Note that &lt;code&gt;{topics}&lt;/code&gt; is used here as well just like the agent.&lt;/p&gt;

&lt;h2&gt;
  
  
  Crew
&lt;/h2&gt;

&lt;p&gt;We finally have the starting point of our code in &lt;code&gt;main.py&lt;/code&gt; where our crew will be defined. We need to map it with both the agents and tools. Note that we only have a single tool in this case, if there are more tools, those would be executed sequentially, by default.&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="n"&gt;crewai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Crew&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;agents&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;screening_agent&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;screening_task&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;crew&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Crew&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;agents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;screening_agent&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;screening_task&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;topics_list&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Redshift, Glue, S3&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;topics&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;topics_list&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;crew&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;kickoff&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;inputs&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;topics&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;topics&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;output.md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;raw&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;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that in the crew kickoff method, we are passing topics as input which we saw was referred to in both agent and tool configuration. In the code above we are trying to pass the topics as &lt;code&gt;Redshift, Glue, S3&lt;/code&gt; which would be passed to the task and then to the agent, to retreive the questions and answers, we need for screening, the output is finally saved in a markdown file mentioned. Let's try runnig it with &lt;code&gt;uv run main.py&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.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%2F0rd44e7qjfzhrpyk1sif.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F0rd44e7qjfzhrpyk1sif.png" alt="Screenshot of cli output" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here is a preview of the markdown generated.&lt;br&gt;
&lt;a href="https://media2.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%2Ftqtmorbol094otk0ttuh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftqtmorbol094otk0ttuh.png" alt="Markdown preview" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So that's the end of this post, hope it was beginner friendly, and we just navigated some fundamentals of CrewAI, we could explore further on this such as passing the topics via user input, build multi tool based agents, add tools, mcp, rag and see options for integrating it with UI based tools for day to day use. Thank you for reading!!!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>llm</category>
      <category>python</category>
      <category>ai</category>
    </item>
    <item>
      <title>GitHub MCP with Amazon Q CLI</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Tue, 06 May 2025 06:31:02 +0000</pubDate>
      <link>https://dev.to/aws-builders/mcp-with-amazon-q-cli-ac9</link>
      <guid>https://dev.to/aws-builders/mcp-with-amazon-q-cli-ac9</guid>
      <description>&lt;p&gt;Hi 👋, let's explore some MCP stuff with the amazon q developer cli. We shall try onboarding a new nextjs app with boilerplate code on GitHub in this exercise. We then just clone the code to our machine and run it locally. Let's get started 🚀&lt;/p&gt;

&lt;p&gt;Install the q &lt;a href="https://docs.aws.amazon.com/amazonq/latest/qdeveloper-ug/command-line.html" rel="noopener noreferrer"&gt;cli&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install amazon-q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once installed open Amazon Q from the launchpad and follow the instructions there to integrate Q with shell, and then login with a builder ID.&lt;/p&gt;

&lt;p&gt;Let's create a directory where we can keep our mcp code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir app-onboarding-with-q
cd app-onboarding-with-q 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  GitHub MCP server
&lt;/h2&gt;

&lt;p&gt;We can now add the &lt;a href="https://github.com/github/github-mcp-server" rel="noopener noreferrer"&gt;github&lt;/a&gt; mcp config.&lt;/p&gt;

&lt;p&gt;Create a personal access token on github developer settings with access to &lt;code&gt;All repositories&lt;/code&gt; and read and write permissions for &lt;code&gt;Administration&lt;/code&gt; and &lt;code&gt;Content&lt;/code&gt;.&lt;br&gt;
&lt;a href="https://media2.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%2Fmp6j0y0ntlzj1s16j12v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fmp6j0y0ntlzj1s16j12v.png" alt="GitHub token permissions" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the mcp configuration in the directory on the file &lt;code&gt;.amazonq/mcp.json&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "mcpServers": {
      "github": {
        "command": "docker",
        "args": [
          "run",
          "-i",
          "--rm",
          "-e",
          "GITHUB_PERSONAL_ACCESS_TOKEN",
          "ghcr.io/github/github-mcp-server"
        ],
        "env": {
          "GITHUB_PERSONAL_ACCESS_TOKEN": "&amp;lt;your-github-pat&amp;gt;"
        }
      }
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Start the docker engine on your machine as the github mcp server defined above needs docker.&lt;/p&gt;

&lt;p&gt;Launch &lt;code&gt;q&lt;/code&gt; from the cli.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ q
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see that the mcp server is launched.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;✓ github loaded in 0.33 s
✓ 1 of 1 mcp servers initialized
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should have started a docker container for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ docker ps | grep github
76ff6beb314c   ghcr.io/github/github-mcp-server              "./github-mcp-server…"   26 seconds ago   Up 26 seconds                                                                                                   stupefied_heyrovsky
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the q-prompt if you enter &lt;code&gt;/tools&lt;/code&gt; you should see the available tools including the MCP tools. Here is a glimpse.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;github (MCP):
- github___search_issues                      * not trusted
- github___get_pull_request_reviews           * not trusted
- github___get_code_scanning_alert            * not trusted
- github___create_pull_request_review         * not trusted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are certain built in tools as well, other than the mcp tools we installed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Built-in:
- execute_bash    * trust read-only commands
- fs_write        * not trusted
- use_aws         * trust read-only commands
- fs_read         * trusted
- report_issue    * trusted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create a nextjs boilerplate app.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; create a new repo in github with name nextjs-new-app and put nextjs bolierplate code in it, with the github mcp server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For brevity, Iam not going to copy the complete response, but we will see some key details from it. First, it will try to create a repo and ask us for confirmation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🛠️  Using tool: create_repository from mcp server github
Allow this action? Use 't' to trust (always allow) this tool for the session. [y/n/t]:
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can press &lt;code&gt;y&lt;/code&gt; to confirm. The repo is now created 🔥.&lt;br&gt;
&lt;a href="https://media2.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%2Fpmjbn18o1c6bs33otz2l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpmjbn18o1c6bs33otz2l.png" alt="New repo" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we could also trust the tool by pressing &lt;code&gt;t&lt;/code&gt;, however it's better to confirm actions for now(human in the loop), so that we are sure what the action would do.&lt;/p&gt;

&lt;p&gt;Let's continue...&lt;/p&gt;

&lt;p&gt;Amazon Q would then try to push multiple files to our repo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;🛠️  Using tool: push_files from mcp server github
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Press &lt;code&gt;y&lt;/code&gt; again after the which the files are all there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5yy5xo2upur6nz7lpwbm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5yy5xo2upur6nz7lpwbm.png" alt="Repo with files" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Run the app locally
&lt;/h2&gt;

&lt;p&gt;Let's once try to run the app locally and see if it's all working, for which we have to clone it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; clone the repo you just created and run the app
🛠️  Using tool: execute_bash
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It will then try to do a few actions with the &lt;code&gt;execute_bash&lt;/code&gt; tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ● I will run the following shell command: 
cd /Users/networkandcode/app-onboarding-with-q &amp;amp;&amp;amp; git clone https://github.com/networkandcode/nextjs-new-app.git

 ● I will run the following shell command: 
cd /Users/networkandcode/app-onboarding-with-q/nextjs-new-app &amp;amp;&amp;amp; npm install

 ● I will run the following shell command: 
cd /Users/networkandcode/app-onboarding-with-q/nextjs-new-app &amp;amp;&amp;amp; npm run dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app should have now started.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; y

&amp;gt; nextjs-new-app@0.1.0 dev
&amp;gt; next dev

 ⚠ Port 3000 is in use, trying 3001 instead.
  ▲ Next.js 14.2.28
  - Local:        http://localhost:3001

 ✓ Starting...
 ✓ Ready in 2.7s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can try accessing &lt;code&gt;http://localhost:3001&lt;/code&gt; as shown above.&lt;br&gt;
&lt;a href="https://media2.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%2Ffkl13twnjzgvqbbvslmb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ffkl13twnjzgvqbbvslmb.png" alt="App running locally" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can press ctrl c when done, so that we are back to the q prompt.&lt;br&gt;
We can type &lt;code&gt;/quit&lt;/code&gt; or &lt;code&gt;/exit&lt;/code&gt; on the q-prompt anytime to exit.&lt;/p&gt;

&lt;p&gt;Let's modify our code&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; modify the code in the nextjs repo networkandcode/nextjs-new-app to show an analog clock on the home page with utc +5.30 time, note that it is written in typescript and has the src folder
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It first tries to read the existing contents &lt;code&gt;🛠️  Using tool: get_file_contents from mcp server github&lt;/code&gt;. Note that I resumed this exercise a bit later so had to give some extra context.&lt;/p&gt;

&lt;p&gt;And then it would try to update files &lt;code&gt;🛠️  Using tool: create_or_update_file from mcp server github&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If we try running it again we should see the clock. Note that I have given a few more prompts to modify code.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2Fsft9vbghqapf28duvfa0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fsft9vbghqapf28duvfa0.png" alt="Homepage showing clock" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As next steps we could try deploying this app on platforms such as AWS Amplify(I think there is no MCP server as of this writing for Amplify deployments). Also, usually we write code first on the local system(we could use the &lt;a href="https://github.com/modelcontextprotocol/servers/tree/main/src/filesystem" rel="noopener noreferrer"&gt;filesystem&lt;/a&gt; mcp for that) and then push it to the git repo, however in this blog we tried the other way round.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>mcp</category>
      <category>github</category>
      <category>nextjs</category>
    </item>
    <item>
      <title>OTEL Auto Instrumentation Demo for SpringBoot</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Fri, 11 Oct 2024 17:36:59 +0000</pubDate>
      <link>https://dev.to/aws-builders/otel-auto-instrumentation-demo-for-springboot-gmk</link>
      <guid>https://dev.to/aws-builders/otel-auto-instrumentation-demo-for-springboot-gmk</guid>
      <description>&lt;p&gt;Hello 👋, this post is about auto instrumenting a spring boot app with the &lt;a href="https://opentelemetry.io/docs/zero-code/java/agent/getting-started/" rel="noopener noreferrer"&gt;otel agent&lt;/a&gt; running on EKS, forward the telemetry signals(metrics, logs, traces) to backends such as prometheus, loki, tempo via the open telemetry collector, and visualize those in Grafana. The setup is as in the diagram below:&lt;br&gt;
&lt;a href="https://media2.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%2F9njwlqldhpacf50hm8dl.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F9njwlqldhpacf50hm8dl.png" alt="Setup" width="800" height="939"&gt;&lt;/a&gt;&lt;br&gt;
Note that the datasources are added on Grafana, which means Grafana sends them API requests to fetch the data.&lt;/p&gt;

&lt;p&gt;Alright, let's get started!&lt;/p&gt;
&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Ensure you have installed tools such as the aws cli, eksctl, git, docker, kubectl, helm and that you have authenticated to aws from cli.&lt;/p&gt;
&lt;h2&gt;
  
  
  Setup the demo app
&lt;/h2&gt;

&lt;p&gt;Let's clone a simple spring boot app from github, and switch to complete directory, where the application code is present.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/spring-guides/gs-rest-service.git

cd gs-rest-service/complete
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can add a logger to the application in the controller class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;java&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;example&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;restservice&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="nc"&gt;GreetingController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;java&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="kn"&gt;package&lt;/span&gt; &lt;span class="nn"&gt;com.example.restservice&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;java.util.concurrent.atomic.AtomicLong&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.GetMapping&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RequestParam&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.springframework.web.bind.annotation.RestController&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.Logger&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;org.slf4j.LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;@RestController&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GreetingController&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;

        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;template&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"Hello, %s!"&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;AtomicLong&lt;/span&gt; &lt;span class="n"&gt;counter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;AtomicLong&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

        &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Logger&lt;/span&gt; &lt;span class="n"&gt;logger&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LoggerFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getLogger&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;GreetingController&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="nd"&gt;@GetMapping&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/greeting"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Greeting&lt;/span&gt; &lt;span class="nf"&gt;greeting&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@RequestParam&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;defaultValue&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"World"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Received GET greeting request for {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;Greeting&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;incrementAndGet&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;template&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
        &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt; 
&lt;span class="no"&gt;EOF&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Build the Docker image
&lt;/h2&gt;

&lt;p&gt;Add a dockerfile to containerize this application, also include the otel agent in it with the curl command.  Note that I'm using maven for the build, however the app has both maven and gradle specs, so you can build it with gradle too just by changing the line in dockerfile from &lt;code&gt;./mvnw clean package&lt;/code&gt; to &lt;code&gt;./gradlew clean build&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; Dockerfile &amp;lt;&amp;lt;EOF
# Build
FROM eclipse-temurin:17-jdk-jammy AS build
WORKDIR /usr/app
COPY . .
RUN ./mvnw clean package

# Run
FROM eclipse-temurin:17-jre-jammy
WORKDIR /usr/app
COPY --from=build /usr/app/target/*.jar ./app.jar
RUN curl -O -L https://github.com/open-telemetry/opentelemetry-java-instrumentation/releases/latest/download/opentelemetry-javaagent.jar
RUN useradd appuser &amp;amp;&amp;amp; chown -R appuser /usr/app
USER appuser
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3   CMD curl -f http://localhost:8080/ || exit 1 
ENTRYPOINT ["java", "-javaagent:/usr/app/opentelemetry-javaagent.jar", "-jar", "/usr/app/app.jar"]
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have added the healthcheck command and a nonroot user in the dockerfile above, so that it passes security checks. We can lint/scan it with trivy as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker run --rm -v ./Dockerfile:/tmp/Dockerfile aquasec/trivy config /tmp/Dockerfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add the dockerignore file so that we are not copying unwanted files to the image.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; .dockerignore &amp;lt;&amp;lt;EOF
Dockerfile
manifest.yml
build
target
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can build the image now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)

export REGION=us-east-1

docker build -t $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/otel-auto-java-demo .
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Push the image to ECR
&lt;/h2&gt;

&lt;p&gt;The image we built can be pushed to ECR, for which we have to first create a repo there.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ecr create-repository --repository-name otel-auto-java-demo --region $REGION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then, get the ecr password and login with it from docker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should get &lt;code&gt;Login Succeeded&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The image can now be pushed to ECR with the docker cli.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker push $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/otel-auto-java-demo 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create EKS cluster
&lt;/h2&gt;

&lt;p&gt;I'm going to create an EKS cluster with the eksctl cli.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl create cluster --name otel-auto-java-demo --zones=us-east-1a,us-east-1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When this is done, your kubeconfig should have got updated and you should have scoped to the new cluster. However you can keep this command handy to switch to the correct context &lt;code&gt;aws eks update-kubeconfig --region $REGION --name otel-auto-java-demo&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup helmfile
&lt;/h2&gt;

&lt;p&gt;We can deploy our tool stack with &lt;a href="https://github.com/helmfile/helmfile/releases" rel="noopener noreferrer"&gt;helmfile&lt;/a&gt;. First check the architecture, for ex. with a command like &lt;code&gt;uname -m&lt;/code&gt; and then install helmfile with the correct package. In my case the architecture was &lt;code&gt;x86_64&lt;/code&gt; and hence, I am installing the &lt;code&gt;linux_amd64&lt;/code&gt; package.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;uname -m
wget https://github.com/helmfile/helmfile/releases/download/v1.0.0-rc.5/helmfile_1.0.0-rc.5_linux_amd64.tar.gz

tar -xvf helmfile_1.0.0-rc.5_linux_amd64.tar.gz 

rm LICENSE README.md README-zh_CN.md 

sudo mv helmfile /usr/bin/helmfile
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that you would also need &lt;a href="https://helm.sh/docs/intro/install/" rel="noopener noreferrer"&gt;helm&lt;/a&gt; as helmfile is a wrapper around helm.&lt;/p&gt;

&lt;p&gt;Our helmfile should look as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; helmfile.yaml &amp;lt;&amp;lt;EOF
repositories:
- name: open-telemetry
  url: https://open-telemetry.github.io/opentelemetry-helm-charts
- name: prometheus-community
  url: https://prometheus-community.github.io/helm-charts
- name: grafana
  url: https://grafana.github.io/helm-charts

releases:
- name: otel-collector
  chart: open-telemetry/opentelemetry-collector
  namespace: otel-auto-java-demo
  values:
  - otel-collector-values.yaml
- name: prometheus
  chart: prometheus-community/prometheus
  namespace: otel-auto-java-demo
  values:
  - prometheus-values.yaml
- name: loki
  chart: grafana/loki
  namespace: otel-auto-java-demo
  values:
  - loki-values.yaml
- name: tempo
  chart: grafana/tempo-distributed
  namespace: otel-auto-java-demo
  values:
  - tempo-values.yaml
- name: grafana
  chart: grafana/grafana
  namespace: otel-auto-java-demo
  values:
  - grafana-values.yaml
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup Helm values
&lt;/h2&gt;

&lt;p&gt;This section shows the different values files, that we are using in the helmfile.&lt;/p&gt;

&lt;h3&gt;
  
  
  OTEL collector
&lt;/h3&gt;

&lt;p&gt;Let's begin with the otel collector values. Some of the sections such as receivers are omitted, as anyway they will come from the default &lt;a href="https://artifacthub.io/packages/helm/opentelemetry-helm/opentelemetry-collector?modal=values" rel="noopener noreferrer"&gt;values&lt;/a&gt; file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; otel-collector-values.yaml &amp;lt;&amp;lt;EOF
image:
  repository: "otel/opentelemetry-collector-contrib"
mode: deployment
resources:
  limits:
    cpu: 250m
    memory: 512Mi
config:
  processors:
    batch:
      send_batch_max_size: 500
      send_batch_size: 50
      timeout: 5s
  exporters:
    otlphttp/prometheus:
      endpoint: "http://prometheus-server/api/v1/otlp"
    loki:
      endpoint: "http://loki-write:3100/loki/api/v1/push"
    otlp:
      endpoint: "http://tempo-distributor:4317"
      tls:
        insecure: true
  service:
    pipelines:
      metrics:
        receivers: [otlp]
        exporters: [otlphttp/prometheus]
      logs:
        exporters: [loki]
      traces:
        receivers: [otlp]
        exporters: [otlp]
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Prometheus
&lt;/h3&gt;

&lt;p&gt;We are disabling components that we do not need for this exercise, from the prometheus chart, and we are enabling otlp write receiver, as we are sending metric from the otel collector to prometheus via otlp/http.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; prometheus-values.yaml &amp;lt;&amp;lt;EOF
alertmanager:
  enabled: false
prometheus-pushgateway:
  enabled: false
prometheus-node-exporter:
  enabled: false
kube-state-metrics:
  enabled: false
server:
  extraFlags:
  - "enable-feature=exemplar-storage"
  - "enable-feature=otlp-write-receiver"
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Loki
&lt;/h3&gt;

&lt;p&gt;We are installing loki in the &lt;a href="https://grafana.com/docs/loki/latest/setup/install/helm/install-scalable/" rel="noopener noreferrer"&gt;simple scalable&lt;/a&gt; mode. And have disabled some of the components such as chunks cache, results cache, gateway to keep it simple.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; loki-values.yaml &amp;lt;&amp;lt;EOF
# https://grafana.com/docs/loki/latest/operations/caching/
chunksCache:
  enabled: false
resultsCache:
  enabled: false
gateway:
  enabled: false

limits_config:
  allow_structured_metadata: true

loki:
  auth_enabled: false
  schemaConfig:
    configs:
      - from: 2024-04-01
        store: tsdb
        object_store: s3
        schema: v13
        index:
          prefix: loki_index_
          period: 24h
  ingester:
    chunk_encoding: snappy
  tracing:
    enabled: true
  querier:
    # Default is 4, if you have enough memory and CPU you can increase, reduce if OOMing
    max_concurrent: 4

deploymentMode: SimpleScalable

backend:
  replicas: 3
read:
  replicas: 3
write:
  replicas: 3

# Enable minio for storage
minio:
  enabled: true

# Zero out replica counts of other deployment modes
singleBinary:
  replicas: 0

ingester:
  replicas: 0
querier:
  replicas: 0
queryFrontend:
  replicas: 0
queryScheduler:
  replicas: 0
distributor:
  replicas: 0
compactor:
  replicas: 0
indexGateway:
  replicas: 0
bloomCompactor:
  replicas: 0
bloomGateway:
  replicas: 0
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Tempo
&lt;/h3&gt;

&lt;p&gt;This is our tempo values. Note that we are using the tempo distributed chart, i.e. the microservices mode.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; tempo-values.yaml &amp;lt;&amp;lt;EOF 
gateway:
  enabled: true
traces:
  otlp:
    grpc:
      enabled: true
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Grafana
&lt;/h3&gt;

&lt;p&gt;And finally grafana, with datasources provisioned.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; grafana-values.yaml &amp;lt;&amp;lt;EOF
service:
  type: LoadBalancer
datasources:
  datasources.yaml:
    apiVersion: 1
    datasources:
    - name: Prometheus
      type: prometheus
      url: http://prometheus-server
    - name: Loki
      type: loki
      url: http://loki-read:3100
    - name: Tempo
      type: tempo
      url: http://tempo-gateway
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Setup prerequisites for provisioning volumes
&lt;/h2&gt;

&lt;p&gt;We need to setup a few things on AWS so that we are able to provision volumes that are needed for some of our tools. First we have to install the CSI driver on the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl create addon --name aws-ebs-csi-driver --cluster otel-auto-java-demo --region $REGION
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We have to then the modify the nodegroup's IAM role with appropriate permissions, for which we have to first find the role. Note that the role suffix could be different in your case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws iam list-roles | jq -r '.Roles[].RoleName' | grep  otel-auto-java | grep node
eksctl-otel-auto-java-demo-nodegr-NodeInstanceRole-tUmFVlpwtUfc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can then attach the appropriate policy to the role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ aws iam attach-role-policy   --role-name eksctl-otel-auto-java-demo-nodegr-NodeInstanceRole-tUmFVlpwtUfc   --policy-arn arn:aws:iam::aws:policy/service-role/AmazonEBSCSIDriverPolicy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And then we can set the gp2 storage class as default. This is because the volumes we install would look for the default storage class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl patch storageclass gp2 -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io/gp2 patched
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The word &lt;code&gt;default&lt;/code&gt; should now appear in the storage class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get storageclass
NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  20h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploy the tool stack
&lt;/h2&gt;

&lt;p&gt;Alright, done some heavy lifting, we can install our tool stack now.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helmfile sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the installation is complete you can check the release and pods status with commands such as &lt;code&gt;helm ls -A&lt;/code&gt;, &lt;code&gt;kubectl get po -A&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the demo app
&lt;/h2&gt;

&lt;p&gt;We should now deploy the demo app with the following manifest, which has both deployment and service configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cat &amp;gt; demoapp.yaml &amp;lt;&amp;lt;EOF
apiVersion: apps/v1
kind: Deployment
metadata:
  name: otel-auto-java-demo
  labels:
    app: otel-auto-java-demo
  namespace: otel-auto-java-demo
spec:
  selector:
    matchLabels:
      app: otel-auto-java-demo
  template:
    metadata:
      labels:
        app: otel-auto-java-demo
    spec:
      containers:
        - name: otel-auto-java-demo
          image: $ACCOUNT_ID.dkr.ecr.$REGION.amazonaws.com/otel-auto-java-demo:latest
          # https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/
          env:
          - name: OTEL_EXPORTER_OTLP_ENDPOINT
            value: "http://otel-collector-opentelemetry-collector:4317"
          - name: OTEL_EXPORTER_OTLP_PROTOCOL
            value: grpc
          imagePullPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
  name: otel-auto-java-demo
  namespace: otel-auto-java-demo
spec:
  type: ClusterIP
  selector:
    app: otel-auto-java-demo
  ports:
    - port: 80
      targetPort: 8080
EOF
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that the above file has env vars for account id and region, hence we can use envsubst before applying the manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;envsubst &amp;lt; demoapp.yaml | kubectl apply -f -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Make some api calls to the app
&lt;/h2&gt;

&lt;p&gt;We can expose the service, so that we can send some calls on the localhost.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl port-forward svc/otel-java-demo -n otel-java-demo 8080:80
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Make a few api calls with curl, from a different terminal.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ curl localhost:8080/greeting; echo
{"id":8,"content":"Hello, World!"}

$ curl localhost:8080/greeting?name=Earth; echo
{"id":13,"content":"Hello, Earth!"}

$ curl localhost:8080/greeting?name=Universe; echo
{"id":14,"content":"Hello, Universe!"}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When done, you may stop the port fowarding with Ctrl C.&lt;/p&gt;

&lt;h2&gt;
  
  
  Monitor the app
&lt;/h2&gt;

&lt;p&gt;We can now monitor our app from Grafana with the LoadBalancer URL of Grafana.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get svc grafana -n otel-auto-java-demo
NAME      TYPE           CLUSTER-IP     EXTERNAL-IP                                                               PORT(S)        AGE
grafana   LoadBalancer   10.100.69.33   acdbcf3ef71674a0b9a8be554d62db7c-2016095508.us-east-1.elb.amazonaws.com   80:32283/TCP   73m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Just access the URL above with &lt;code&gt;http://&lt;/code&gt; on the browser, the URL would be different in your case. The username is admin, and the password can be obtained from the secret as shown below. The password would be different in your case.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get secret grafana -n otel-auto-java-demo -o jsonpath={.data.admin-password} | base64 -d ; echo
3cOFbY8Q5H5Qz4CHHJvJZ2a2UDvSeQnmRrFIQlsv
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go to explore tab on let's try one query each for metrics, logs and traces.&lt;/p&gt;

&lt;p&gt;Metrics preview:&lt;br&gt;
&lt;a href="https://media2.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%2F8ib0xn0ezd45c75ca87z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F8ib0xn0ezd45c75ca87z.png" alt="Metrics preview" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Logs preview:&lt;br&gt;
&lt;a href="https://media2.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%2F2gxj6l04dyx30c4f5ek1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2gxj6l04dyx30c4f5ek1.png" alt="Logs preview" width="800" height="369"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Traces preview:&lt;br&gt;
&lt;a href="https://media2.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%2Fk5821qbx2nxxda2nk2uc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk5821qbx2nxxda2nk2uc.png" alt="Traces preview" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that we only added two &lt;a href="https://opentelemetry.io/docs/specs/otel/configuration/sdk-environment-variables/" rel="noopener noreferrer"&gt;env vars&lt;/a&gt; in our demo app, we haven't added any extra vars such as resource attributes, service name etc. Just to show that  it would pick it from the artifact id if not provided. As shown in the image previews above, for metrics(prometheus) the it would become the &lt;code&gt;job&lt;/code&gt; label, and for logs(loki) it would become the &lt;code&gt;job&lt;/code&gt; and &lt;code&gt;service_name&lt;/code&gt; label and for traces(tempo) it would be the &lt;code&gt;resource.service.name&lt;/code&gt; tag.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat pom.xml
--TRUNCATED--
&amp;lt;groupId&amp;gt;com.example&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;rest-service-complete&amp;lt;/artifactId&amp;gt;
--TRUNCATED--
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, we were able to get the telemetry data successfully from our SpringBoot Java App all the way down to Grafana. With this as base, we could now build custom dashboards as we like, do some correlation by modifying datasource config, etc. &lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Delete the helm releases: &lt;code&gt;helmfile destroy&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete the PVCs: &lt;code&gt;kubectl delete pvc --all -n otel-auto-java-demo&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete the demo app (optional): &lt;code&gt;kubectl delete -f demoapp.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete the cluster: &lt;code&gt;eksctl delete cluster --name otel-auto-java-demo --region $REGION&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Delete the ECR repo: &lt;code&gt;aws ecr delete-repository --repository-name otel-auto-java-demo --region $REGION --force&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading :)&lt;/p&gt;

</description>
      <category>monitoring</category>
      <category>springboot</category>
      <category>aws</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>AWS ALB(Ingress) access logs on Grafana</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Tue, 01 Oct 2024 10:05:48 +0000</pubDate>
      <link>https://dev.to/aws-builders/aws-albingress-access-logs-on-grafana-25em</link>
      <guid>https://dev.to/aws-builders/aws-albingress-access-logs-on-grafana-25em</guid>
      <description>&lt;p&gt;Hi 👋, we shall explore the following use case in this post: Let's say you have an EKS cluster, and you have deployed an application there and exposed it via the AWS ALB controller as ingress(instead of other options like nginx ingress, istio virtual service etc.). We'll see how to check the access logs for this scenario, by forwarding the access logs from the application load balancer to a bucket on S3. And then use an event trigger when new logs are added to S3, to invoke a Lambda function that would write those logs to a Cloud watch log group and stream. We could then use Cloud watch as a datasource on Grafana to visualize/monitor the logs.&lt;/p&gt;

&lt;p&gt;So the setup is going to look like this:&lt;br&gt;
&lt;a href="https://media2.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%2Fcad2ma436joy2vk5ph07.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fcad2ma436joy2vk5ph07.png" alt="Blog diagram" width="800" height="811"&gt;&lt;/a&gt;&lt;br&gt;
Note that though the arrow on the diagram is one sided, it's only referring to who initiates the traffic(request) first, in most cases there is going to be some response back.&lt;/p&gt;
&lt;h2&gt;
  
  
  EKS cluster
&lt;/h2&gt;

&lt;p&gt;We need to first create a cluster and associate oidc provider with it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl create cluster --name ingress-logs-demo --zones=us-east-1a,us-east-1b

eksctl utils associate-iam-oidc-provider --region=us-east-1 --cluster=ingress-logs-demo --approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ingress
&lt;/h2&gt;

&lt;p&gt;First create a service account with the built in load balancer controller role.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl create iamserviceaccount \
  --cluster=ingress-logs-demo \
  --namespace=kube-system \
  --name=aws-load-balancer-controller \
  --role-name AmazonEKSLoadBalancerControllerRole \
  --attach-policy-arn=arn:aws:iam::$AWS_ACCOUNT_ID:policy/AWSLoadBalancerControllerIAMPolicy \
  --approve
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Replace $AWS_ACCOUNT_ID with your account id.&lt;/p&gt;

&lt;p&gt;We can now install the &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html" rel="noopener noreferrer"&gt;AWS Loadbalancer Controller&lt;/a&gt;, and map it with the service account we just created.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add eks https://aws.github.io/eks-charts

helm install aws-load-balancer-controller eks/aws-load-balancer-controller \ 
-n kube-system \ 
--set clusterName=$CLUSTER \ 
--set serviceAccount.create=false \ 
--set serviceAccount.name=aws-load-balancer-controller
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo app
&lt;/h2&gt;

&lt;p&gt;We can install a &lt;a href="https://docs.aws.amazon.com/eks/latest/userguide/lbc-helm.html" rel="noopener noreferrer"&gt;demo app - 2048 game&lt;/a&gt; that comes with ingress as well.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl apply -f https://raw.githubusercontent.com/kubernetes-sigs/aws-load-balancer-controller/v2.7.2/docs/examples/2048/2048_full.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should have created an ingress resource for us.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl get ing -n game-2048
NAME           CLASS   HOSTS   ADDRESS                                                                  PORTS   AGE
ingress-2048   alb     *       k8s-game2048-ingress2-cfcea01af4-806164108.us-east-1.elb.amazonaws.com   80      3d3h
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could access the URL with the URL above(it would change in your case).&lt;br&gt;
&lt;a href="https://media2.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%2F6nhmwrks4fl3sbhenomw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6nhmwrks4fl3sbhenomw.png" alt="2048 game on browser" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Access logs
&lt;/h2&gt;

&lt;p&gt;Let's say we want to monitor our app, like how many requests are coming in, how many are errors, what's the response time etc. In that case we can catch the access logs that are going to our app. For this we need to go the application loadbalancer that was created when we installed the aws load balancer controller via helm. And update it to forward the acces logs to S3. Which means we first need to create the S3 bucket.&lt;/p&gt;
&lt;h2&gt;
  
  
  S3
&lt;/h2&gt;

&lt;p&gt;I have created an S3 bucket with the name &lt;code&gt;ingress-logs-demo&lt;/code&gt; in the us-east-2 region with the following policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "AWS": "arn:aws:iam::127311923021:root"
            },
            "Action": "s3:PutObject",
            "Resource": "arn:aws:s3:::ingress-logs-demo/AWSLogs/&amp;lt;AWS_ACCOUNT_ID&amp;gt;/*"
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that &lt;code&gt;127311923021&lt;/code&gt; refers to the AWS account for loadbalancers in the us-east-2 region. Check this &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/enable-access-logging.html" rel="noopener noreferrer"&gt;link&lt;/a&gt; for more info. Also, replace &lt;code&gt;&amp;lt;AWS_ACCOUNT_ID&amp;gt;&lt;/code&gt; with your account id. The policy above means any loadblancer created in the us-east-2 region should be able to put objects in this bucket in the defined path.&lt;/p&gt;

&lt;p&gt;We can now modify the application loadbalancer to forward logs to S3.&lt;br&gt;
&lt;a href="https://media2.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%2Fpy03zedkctde9so8i0k5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpy03zedkctde9so8i0k5.png" alt="Send access logs to S3" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we start using the app ui now via browser and do a few hits, we should be able to see the access logs for those on S3 in compressed format.&lt;br&gt;
&lt;a href="https://media2.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%2Fncd1fe2fdcn8ntl68a4h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fncd1fe2fdcn8ntl68a4h.png" alt="Access logs on S3" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Cloudwatch Logs
&lt;/h2&gt;

&lt;p&gt;We can create a log watch group(&lt;code&gt;/aws/lambda/ingress-logs-demo&lt;/code&gt;) and stream(&lt;code&gt;access-logs&lt;/code&gt;) where we can forward the access logs from S3. Note that I am going to use the same log group for both the access logs(alb to s3 to cloud watch) and for any logs coming from the lambda function itself. That is why I have named the logwatch group with that format, as any logs from a function with name (ingress-logs-demo) would be stored in (/aws/lambda/ingress-logs-demo)&lt;/p&gt;
&lt;h2&gt;
  
  
  Lambda
&lt;/h2&gt;

&lt;p&gt;We should need a way to retreive these logs and forward those to Cloudwatch, for which I would be using a Lambda function in Python with the same name &lt;code&gt;ingress-logs-demo&lt;/code&gt;(for simplicity) with the following code.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import boto3
import gzip
import io
from datetime import datetime

s3_client = boto3.client('s3')
logs_client = boto3.client('logs')

log_group_name = '/aws/lambda/ingress-logs-demo'
log_stream_name = 'access-logs'

def lambda_handler(event, context):
    # Get S3 bucket and log file details from the event
    bucket = event['Records'][0]['s3']['bucket']['name']
    key = event['Records'][0]['s3']['object']['key']

    # Download the log file from S3
    response = s3_client.get_object(Bucket=bucket, Key=key)

    # Read the gzipped file
    with gzip.GzipFile(fileobj=io.BytesIO(response['Body'].read())) as file:
        logs = file.read().decode('utf-8').splitlines()

    # Send each log line as an event to CloudWatch
    log_events = []
    for line in logs:
        timestamp = int(datetime.now().timestamp() * 1000)


        log_events.append({
            'timestamp': timestamp,
            'message': line
        })

    if log_events:
        logs_client.put_log_events(
            logGroupName=log_group_name,
            logStreamName=log_stream_name,
            logEvents=log_events
        )

    return {
        'statusCode': 200,
        'body': f'Successfully processed {len(log_events)} log events from {key}'
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the we are passing the logs as is but we could use some logic in our code, to parse the content and restructure it as desired. Please check this &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-access-logs.html#log-processing-tools" rel="noopener noreferrer"&gt;link&lt;/a&gt; for the log format.&lt;/p&gt;

&lt;p&gt;Also we need a trigger to invoke this function when any objects are added to S3.&lt;br&gt;
&lt;a href="https://media2.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%2F59pxllxi506zow59sp7s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F59pxllxi506zow59sp7s.png" alt="Lambda trigger for S3" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Role
&lt;/h2&gt;

&lt;p&gt;We need an IAM role that lets the lambda function read from S3 and write to Cloudwatch Logs. We can create a new one with name &lt;code&gt;ingress-logs-demo&lt;/code&gt;, trusted entity type as &lt;code&gt;AWS service&lt;/code&gt;, Service or use case  as &lt;code&gt;Lambda&lt;/code&gt; and in the permission policies, select &lt;code&gt;AWSLambdaBasicExecutionRole&lt;/code&gt; and &lt;code&gt;AmazonS3ReadOnlyAccess&lt;/code&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  All set
&lt;/h2&gt;

&lt;p&gt;All set now, we can now hit the 2048 game url on browser a couple of times, and  try playing the game. This should run our workflow and store logs in cloud watch.&lt;br&gt;
&lt;a href="https://media2.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%2Fes4uwgbkt5vipnn4d7vg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fes4uwgbkt5vipnn4d7vg.png" alt="Access logs on cloud watch" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Grafana
&lt;/h2&gt;

&lt;p&gt;We can now go to Grafana, add cloudwatch as a &lt;a href="https://dev.toAWS%20Community%20Builders%20&amp;lt;br&amp;gt;%0ALogging%20demo%20with%20OTEL%20Collector,%20CloudWatch%20and%20Grafana"&gt;datasource&lt;/a&gt; and play with the data and do some visualization, display the logs etc.&lt;br&gt;
Let's try a couple of exercises with panels.&lt;/p&gt;

&lt;p&gt;Just filter for logs that has "200" typically meaning we wan't to see  logs that have response code 200.&lt;br&gt;
&lt;a href="https://media2.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%2Fxi07itlgnygtwphlek9f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fxi07itlgnygtwphlek9f.png" alt="Logs on Grafana" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's check 404 this time. And create a stat panel with it.&lt;br&gt;
&lt;a href="https://media2.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%2Fdyfl0siacvvjj3erz6j6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdyfl0siacvvjj3erz6j6.png" alt="Stat panel for 404" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I have used this query:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;fields @timestamp, @message
| filter @logStream = "access-logs"
| filter @message like /"404"/
| sort @timestamp desc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;and the calculation was set to count for the field &lt;code&gt;@message&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Alright, so that's end the post, we were finally able to get our access logs on cloudwatch and see those on Grafana. This should help us monitoring any urls thats accessed through the ingress.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;Checklist:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Delete the function&lt;/li&gt;
&lt;li&gt;Delete the IAM role&lt;/li&gt;
&lt;li&gt;Empty the S3 bucket and delete it&lt;/li&gt;
&lt;li&gt;Delete the cloudwatch log group&lt;/li&gt;
&lt;li&gt;Delete the load balancer controller and remove the helm repo
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm uninstall aws-load-balancer-controller -n kube-system
helm repo remove eks
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;Delete the eks cluster: &lt;code&gt;eksctl delete cluster --name ingress-logs-demo&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Thank you for reading :)&lt;/p&gt;

</description>
      <category>aws</category>
      <category>kubernetes</category>
      <category>lambda</category>
      <category>monitoring</category>
    </item>
    <item>
      <title>OTEL Demo with EKS and New Relic</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Thu, 18 Jul 2024 04:09:25 +0000</pubDate>
      <link>https://dev.to/aws-builders/otel-demo-with-and-eks-and-newrelic-3j52</link>
      <guid>https://dev.to/aws-builders/otel-demo-with-and-eks-and-newrelic-3j52</guid>
      <description>&lt;p&gt;Hi👋, In this post we shall deploy the OTEL demo app on EKS, and send the metrics, logs and traces to New Relic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;It's assumed you know how to install and use tools such as &lt;em&gt;eksctl, kubectl, helm, helmfile&lt;/em&gt; and know some observability basics.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cluster
&lt;/h2&gt;

&lt;p&gt;Create a cluster on EKS with this command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl create cluster --name newrelic-otel-demo --zones=us-east-1a,us-east-1b
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once the cluster is created, the kubeconfig's current context will be automatically changed to it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl config current-context
nc@newrelic-otel-demo.us-east-1.eksctl.io
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  License
&lt;/h2&gt;

&lt;p&gt;We need a license key🔑 first from New Relic, log in to the New Relic website and headerover to &lt;code&gt;Administration &amp;gt; API keys&lt;/code&gt; and copy the license key and set it as a variable. Note that the free account is sufficient for this demo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export NEW_RELIC_LICENSE_KEY='&amp;lt;NEW_RELIC_LICENSE_KEY&amp;gt;'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can create a namespace for our demo and keep a secret in it for the license.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl create ns otel-demo-newrelic
namespace/newrelic-otel-demo created

$ kubectl create secret generic newrelic-license-key --from-literal=license-key="$NEW_RELIC_LICENSE_KEY" -n otel-demo-newrelic
secret/newrelic-license-key created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Helm
&lt;/h2&gt;

&lt;p&gt;We would be using the following helm values. We have disabled extra components that come along with the helm chart which we do not need for this demo.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat otel-demo-newrelic-values.yaml
prometheus:
  enabled: false
opensearch:
  enabled: false
grafana:
  enabled: false
jaeger:
  enabled: false

opentelemetry-collector:
  config:
    exporters:
      otlphttp/newrelic:
        endpoint: https://otlp.nr-data.net:4317
        headers:
          api-key: ${NEW_RELIC_LICENSE_KEY}
        tls:
          insecure: false
    service:
      pipelines:
        logs:
          exporters: [otlphttp/newrelic, debug]
        metrics:
          exporters: [otlphttp/newrelic, debug]
          receivers: [httpcheck/frontendproxy, redis, otlp]
        traces:
          exporters: [otlphttp/newrelic, debug]
  extraEnvs:
    - name: NEW_RELIC_LICENSE_KEY
      valueFrom:
        secretKeyRef:
          key: license-key
          name: newrelic-license-key
    - name: OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCE
      value: delta
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the helmfile would refer to the public helm chart and the helm values file we just defined above.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat otel-demo-newrelic-helmfile.yaml
repositories:
- name: open-telemetry
  url: https://open-telemetry.github.io/opentelemetry-helm-charts

releases:
- name: otel-demo-newrelic
  chart: open-telemetry/opentelemetry-demo
  namespace: otel-demo-newrelic
  values:
  - otel-demo-newrelic-values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's time to deploy the helm release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helmfile sync -f otel-demo-newrelic-helmfile.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can check the helm release.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ helm ls -n otel-demo-newrelic
NAME                NAMESPACE           REVISION    UPDATED                                 STATUS      CHART                       APP VERSION
otel-demo-newrelic  otel-demo-newrelic  1           2024-07-17 22:02:13.440621 +0530 IST    deployed    opentelemetry-demo-0.32.1   1.11.0     
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Pods
&lt;/h2&gt;

&lt;p&gt;In some time, all the pods should be running. I had seen image pull back off for sometime, however it fixed itself in a while.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get po -n otel-demo-newrelic
NAME                                                        READY   STATUS    RESTARTS   AGE
otel-demo-newrelic-accountingservice-76fc85f7f9-vxps4       1/1     Running   0          25m
otel-demo-newrelic-adservice-7f8964f98-z2nqd                1/1     Running   0          25m
otel-demo-newrelic-cartservice-bbdfdc59c-w4cqg              1/1     Running   0          25m
otel-demo-newrelic-checkoutservice-f6fd788db-khgq6          1/1     Running   0          25m
otel-demo-newrelic-currencyservice-f9f77f79d-8n946          1/1     Running   0          25m
otel-demo-newrelic-emailservice-66c84d6b64-frs8b            1/1     Running   0          25m
otel-demo-newrelic-flagd-7444ccd98d-cknxl                   1/1     Running   0          25m
otel-demo-newrelic-frauddetectionservice-7c85f7c6bf-mpbw9   1/1     Running   0          25m
otel-demo-newrelic-frontend-c995cfcdd-dsgvc                 1/1     Running   0          25m
otel-demo-newrelic-frontendproxy-76bb8f75cf-kzmn6           1/1     Running   0          25m
otel-demo-newrelic-imageprovider-66db4b77cf-7rg97           1/1     Running   0          25m
otel-demo-newrelic-kafka-74fbd4c749-dspqm                   1/1     Running   0          25m
otel-demo-newrelic-loadgenerator-864475d886-wtnxs           1/1     Running   0          25m
otel-demo-newrelic-otelcol-6b89d8dd84-l5mx4                 1/1     Running   0          25m
otel-demo-newrelic-paymentservice-59677d8c8f-bds4f          1/1     Running   0          25m
otel-demo-newrelic-productcatalogservice-845d79f865-6hs7c   1/1     Running   0          25m
otel-demo-newrelic-quoteservice-76b5f85685-5zz86            1/1     Running   0          25m
otel-demo-newrelic-recommendationservice-8d6bb74c-6zfm4     1/1     Running   0          25m
otel-demo-newrelic-shippingservice-6d44b76489-n4tlw         1/1     Running   0          25m
otel-demo-newrelic-valkey-7f5ffc95ff-wz9zg                  1/1     Running   0          25m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  New Relic
&lt;/h2&gt;

&lt;p&gt;We can now go to New Relic and see if the data is flowing. Check &lt;code&gt;All Entities &amp;gt; Services-OpenTelemetry&lt;/code&gt;, we should start seeing the data for all our services.&lt;br&gt;
&lt;a href="https://media2.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%2F32cb057vguyztxp16ksp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F32cb057vguyztxp16ksp.png" alt="OTEL services in New Relic" width="800" height="595"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We could either click on one of the service names on the pic above, or choose a service from the &lt;code&gt;APM &amp;amp; Services&lt;/code&gt; section to see quite a lot of information about a particular service including built-in dashboards📈.&lt;br&gt;
&lt;a href="https://media2.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%2F6vx8ybuguc2kslrrcow9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F6vx8ybuguc2kslrrcow9.png" alt="Service overview" width="800" height="553"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We could also check the traces page for a particular service, as we are sending traces too, based on our otel collector configuration.&lt;br&gt;
&lt;a href="https://media2.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%2Fdlc7t4hwg7uvvw3o6f0r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fdlc7t4hwg7uvvw3o6f0r.png" alt="Traces page" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Already a lot of information, forget about creating custom panels or alerts 😃, understading and managing what we have already given by New Relic is in itself a great administration task😉.&lt;/p&gt;

&lt;p&gt;Yet we will try to run a simple query using the query builder.&lt;br&gt;
&lt;a href="https://media2.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%2Fkiplq8gsx8o0rr8b4wla.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fkiplq8gsx8o0rr8b4wla.png" alt="Query builder" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We could make custom dashboards with such queries. Alright so that's the end of this post. We just saw how we can use the open telemetry collector to send observability data to newrelic and just explored some of the UI functionalities on New Relic. See below for the housekeeping check.&lt;/p&gt;
&lt;h2&gt;
  
  
  Cleanup
&lt;/h2&gt;

&lt;p&gt;Note that free account comes with a data ingestion limit of 100GB per month. I think it ingested around 17GB approximately for about 10 hours(I left my cluster ON and more than 85% of data was coming from traces). So it's a good idea to stop sending traffic once your demo is done, so you can leverage the free limit later when required.&lt;/p&gt;

&lt;p&gt;Delete the namespace to delete all the objects we created in the kubernetes cluster for this demo. This would stop sending data to newrelic.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl delete ns otel-demo-newrelic
namespace "otel-demo-newrelic" deleted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also you may unset the license key variable.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;unset NEW_RELIC_LICENSE_KEY
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Finally delete the cluster.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;eksctl delete cluster --name newrelic-otel-demo --region=us-east-1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thank you for reading !!!.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>newrelic</category>
      <category>kubernetes</category>
      <category>sre</category>
    </item>
    <item>
      <title>Grafana OTEL App Observability Demo</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Fri, 12 Jul 2024 17:20:00 +0000</pubDate>
      <link>https://dev.to/networkandcode/grafana-otel-app-observability-demo-552j</link>
      <guid>https://dev.to/networkandcode/grafana-otel-app-observability-demo-552j</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;We are going to explore the built in app observability solution on Grafana cloud, which generates some built in dashboards for us. We would be using OTEL traces for this particular demo. You may check the video version here:   &lt;iframe src="https://www.youtube.com/embed/jw2FXDwEFWg"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;We shall install a sample demo app that simulates a microservices environment. It would generate otel compatible traces which are sent to a local alloy instance. From the local alloy, the traces are sent to Grafana's OTLP gateway(alloy) on the cloud.&lt;/p&gt;

&lt;p&gt;The setup would typically look as below.&lt;br&gt;
&lt;a href="https://media2.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%2Faofl5n8twcfhbwdt9ocy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Faofl5n8twcfhbwdt9ocy.png" alt="Solution diagram" width="800" height="892"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that certain metrics will be generated from the traces. So traces will be sent to tempo and the generated metrics from the traces will be sent to Mimir. Both Mimir and Tempo on the cloud are available as datasources in Grafana cloud.&lt;/p&gt;
&lt;h2&gt;
  
  
  OTLP endpoint
&lt;/h2&gt;

&lt;p&gt;Go to grafana.com, sign in and access your account, the url of which would be like &lt;code&gt;https://grafana.com/orgs/&amp;lt;org-name&amp;gt;&lt;/code&gt;. Click on the Stack at the top of the page. In my case, I am using a free account and the organization and stack name are both same. Note that I can only have one organization in the free account.&lt;/p&gt;

&lt;p&gt;In the manage stack page, there should be an OTLP section, click on it and copy the gateway URL and instance id from there. Note that the instance id would be our username as well. On this page, you may generate a token with the predefined scope &lt;code&gt;set:alloy-data-write&lt;/code&gt;. This has the sub scopes &lt;code&gt;metrics:write, logs:write, traces:write, profiles:write, fleet-management:read&lt;/code&gt;. I have generated a new API token with the predefined options and given it a name &lt;code&gt;app-observability-demo&lt;/code&gt;. Ensure you copy the token.&lt;/p&gt;
&lt;h2&gt;
  
  
  Alloy config
&lt;/h2&gt;

&lt;p&gt;My alloy config would look as below. We need to put the username and password from what we collected in the previous step.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat config.alloy 

otelcol.receiver.otlp "otlp_receiver" {
  grpc {
    endpoint = "0.0.0.0:4317"
  }

  output {
    traces = [otelcol.exporter.otlphttp.grafana_cloud.input,]
  }
}

otelcol.exporter.otlphttp "grafana_cloud" {
    client {
        endpoint = "https://otlp-gateway-prod-us-east-0.grafana.net/otlp"
        auth     = otelcol.auth.basic.grafana_cloud.handler
    }
}

otelcol.auth.basic "grafana_cloud" {
  username = "&amp;lt;username&amp;gt;"
  password = "&amp;lt;api-token&amp;gt;"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create a namespace and then a configmap with this config.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create ns app-o11y-demo

kubectl create configmap \
    alloy-config \
    --namespace app-o11y-demo \
    "--from-file=config.alloy=config.alloy"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Alloy chart
&lt;/h2&gt;

&lt;p&gt;We can define the helm values for alloy by referring to the configmap as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat alloy-values.yaml 
alloy:
  configMap:
    create: false
    name: alloy-config
    key: config.alloy
  extraPorts:
  - name: "grpc"
    port: 4317
    targetPort: 4317
    protocol: "TCP"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And call this values file in the helmfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat helmfile.yaml
repositories:
- name: grafana
  url: https://grafana.github.io/helm-charts

releases:
- name: alloy
  chart: grafana/alloy
  namespace: app-o11y-demo
  values:
  - alloy-values.yaml
  version: 0.5.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now install the helm chart with helmfile.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ helmfile sync
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The alloy pod should be running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get po -n app-o11y-demo
NAME                                  READY   STATUS    RESTARTS   AGE
alloy-cd4zp                           2/2     Running   0          6m10s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And the service, endpoints can be validated.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get svc -n app-o11y-demo
NAME    TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)              AGE
alloy   ClusterIP   10.100.121.96   &amp;lt;none&amp;gt;        12345/TCP,4317/TCP   6m55s

$ kubectl get ep -n app-o11y-demo
NAME    ENDPOINTS                          AGE
alloy   10.1.4.159:12345,10.1.4.159:4317   7m11s

$ kubectl get po -n app-o11y-demo -o wide
NAME                                  READY   STATUS    RESTARTS   AGE     IP           NODE             NOMINATED NODE   READINESS GATES
alloy-cd4zp                           2/2     Running   0          7m27s   10.1.4.159   docker-desktop   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo app
&lt;/h2&gt;

&lt;p&gt;We can now deploy a demo app that would generate traces and send it alloy via grpc. The manifest for which is as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat xk6-client-tracing.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: xk6-client-tracing
  namespace: app-o11y-demo
spec:
  minReadySeconds: 10
  replicas: 1
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: xk6-client-tracing
      name: xk6-client-tracing
  template:
    metadata:
      labels:
        app: xk6-client-tracing
        name: xk6-client-tracing
    spec:
      containers:
      - env:
        - name: ENDPOINT
          value: alloy.app-o11y-demo.svc.cluster.local:4317
        image: ghcr.io/grafana/xk6-client-tracing:v0.0.2
        imagePullPolicy: IfNotPresent
        name: xk6-client-tracing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the ENDPOINT variable above is pointing to the alloy service that we deployed earlier. We can now deploy it with kubectl.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl create -f xk6-client-tracing.yaml
deployment.apps/xk6-client-tracing created
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pod status can be checked.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get po -n app-o11y-demo | grep k6
xk6-client-tracing-6c49cdfbdb-cm25h   1/1     Running   0          5m44s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If everything works as expected, the demo app should have sent traces to alloy and then to tempo backend in Grafana cloud. Likewise it should sent the trace metrics to alloy and then to Mimir on cloud.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana cloud
&lt;/h2&gt;

&lt;p&gt;We can now headover to Grafana cloud. The url of which would look like &lt;code&gt;https://&amp;lt;stack-name&amp;gt;.grafana.net/&lt;/code&gt;. You may go to &lt;code&gt;Home &amp;gt; Application&lt;/code&gt; and click on &lt;code&gt;Enable Metrics&lt;/code&gt; and activate it. After that add a new a connection and select &lt;code&gt;OTLP&lt;/code&gt;. Just scroll down and enter the service name as &lt;code&gt;shop-backend&lt;/code&gt; as that's the root service of our demo app. Test the connection now, it should be successful.&lt;br&gt;
&lt;a href="https://media2.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%2Fk41fek8ea9q202sgfcr2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fk41fek8ea9q202sgfcr2.png" alt="OTLP connection successful" width="800" height="279"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once we click on &lt;code&gt;Go to Application observability&lt;/code&gt; we should be able to see the pre built dashboards based on the traces and the trace metrics, predominantly based on the RED(Rate, Error, Duration method.&lt;br&gt;
&lt;a href="https://media2.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%2Fpjpwpxp4j2fbas98x6gb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fpjpwpxp4j2fbas98x6gb.png" alt="Shop backend dashboard" width="800" height="412"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that in our case we are only sending traces from our application and the trace metrics were generated at Grafana cloud. We could also have sent direct metrics for ex. if it's a go application we could have sent go metrics, likewise we could also have send logs from our application, all of this to the same gateway(alloy on cloud). With the just the traces we have a handful of data to debug web app issues.&lt;/p&gt;

&lt;p&gt;Also, though shop-backend is our root service, we also have other simulated microservices in this application.&lt;br&gt;
&lt;a href="https://media2.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%2Ftdd7qunhgjeffsbim1pk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Ftdd7qunhgjeffsbim1pk.png" alt="Services page" width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are a lot of other options in these built in dashboards to explore. Though we have these built in dashboards, we could also built any custom dashboards. For ex. let's explore what we get in the explore pane keeping tempo as the datasource and build a complete service map for all the services that are part of our demo app. Note that the service map from this is shown in the cover image of the blog.&lt;br&gt;
&lt;a href="https://media2.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%2F2os6uphastchwsov5j5d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F2os6uphastchwsov5j5d.png" alt="Explore trace" width="800" height="290"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let's try one more for the trace metrics with mimir as the datasource.&lt;br&gt;
&lt;a href="https://media2.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%2Fur4puigobj2q8f2pckyq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fur4puigobj2q8f2pckyq.png" alt="Explore metrics" width="800" height="354"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Alright so we have come to the end of this post, we saw how one of Grafana cloud's solutions around app observability could help us get built in dashboards quite quickly once the data starts flowing to the cloud backend. However note that instrumentation is still necessary on the app side, we were able to get a demo app that already had the OTEL tracing instrumentation for us. As a cleanup check you may run the following commands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm uninstall alloy -n app-o11y-demo
kubectl delete -f xk6-client-tracing.yaml

# if you also want to delete the ns
kubectl delete ns app-o11y-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Thanks for reading!!!&lt;/p&gt;

</description>
      <category>grafana</category>
      <category>otel</category>
      <category>observability</category>
      <category>kubernetes</category>
    </item>
    <item>
      <title>Synthetic Monitoring with Grafana Cloud and Lambda</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Sat, 18 May 2024 08:48:03 +0000</pubDate>
      <link>https://dev.to/aws-builders/synthetic-monitoring-with-grafana-cloud-1n5b</link>
      <guid>https://dev.to/aws-builders/synthetic-monitoring-with-grafana-cloud-1n5b</guid>
      <description>&lt;p&gt;🕐Time for some synthetic monitoring fun with Grafana...📊 We'd deploy a simple function on AWS Lambda that generates some data. We then expose this function with a public URL which would then be probed with a synthetic test from Grafana cloud servers.&lt;/p&gt;

&lt;h2&gt;
  
  
  AWS Lambda
&lt;/h2&gt;

&lt;p&gt;Create a new AWS lambda function with runtime as nodejs and a name such as &lt;code&gt;synthetic-monitoring-demo&lt;/code&gt;. Put the following code in &lt;code&gt;index.js&lt;/code&gt;, which returns data similar to what's returned by this Grafana provided &lt;a href="https://test-api.k6.io/public/crocodiles/" rel="noopener noreferrer"&gt;endpoint&lt;/a&gt;. We just create a lambda function here so that we could test as required and delete it when done.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const randomInt = (min, max) =&amp;gt; Math.floor(Math.random() * (max - min + 1)) + min
const randomChoice = (arr) =&amp;gt; arr[randomInt(0, arr.length - 1)]

const generateCrocodileData = () =&amp;gt; {
    const names = [
        "Smiley", "Snappy", "Bitey", "Chompy", "Jaws", "Scaly", "Cruncher", 
        "Munch", "Gator", "Nibbles", "Swampy", "Toothless", "Grin", "Sly", 
        "Snapper", "Croc", "Dagger", "Ripjaw", "Spike", "Thrasher"
    ]
    const species = [
        "Saltwater Crocodile", "Nile Crocodile", "Mugger Crocodile", 
        "American Crocodile", "Freshwater Crocodile", "Morelet's Crocodile", 
        "Cuban Crocodile", "Siamese Crocodile", "Philippine Crocodile", 
        "Orinoco Crocodile", "Dwarf Crocodile", "Slender-snouted Crocodile"
    ]

    return {
        name: randomChoice(names),
        species: randomChoice(species),
        age: randomInt(1, 70),
        sex: randomChoice(["M", "F"]),
    }
}

export const handler = async () =&amp;gt; {
    const numRecords = 8
    const crocodiles = Array.from({ length: numRecords }, generateCrocodileData)
    return {
        statusCode: 200,
        body: JSON.stringify(crocodiles),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The crocodile species names were taken via generative AI, so not quite sure if it's correct😉. &lt;/p&gt;

&lt;p&gt;Deploy the function, and then create a function URL in the configuration section with auth type as none, so that it could be accessed publicly🌍.&lt;br&gt;
&lt;a href="https://media2.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%2F4t2fl4mqsr9tvwgc1z1y.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F4t2fl4mqsr9tvwgc1z1y.png" alt="Create function url" width="800" height="336"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Try accessing the function URL.&lt;br&gt;
&lt;a href="https://media2.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%2Fc1dchscxgvnzw0kaarhq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fc1dchscxgvnzw0kaarhq.png" alt="Access the function url" width="800" height="723"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  K6
&lt;/h2&gt;

&lt;p&gt;The synthetic monitoring in Grafana is based on k6. So we shall try some k6 basics on the local machine first.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://grafana.com/docs/k6/latest/set-up/install-k6/" rel="noopener noreferrer"&gt;Install&lt;/a&gt; k6.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;brew install k6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The command &lt;code&gt;k6 new test.js&lt;/code&gt; should create a file with name &lt;code&gt;test.js&lt;/code&gt; with some references in it. However, for our case we can add a file &lt;code&gt;test.js&lt;/code&gt; from scratch. Let's first import the functions/modules we need.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import { check } from 'k6'
import http from 'k6/http'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We just need to create a function that's exported by default in this code inside which we can try calling the api with http modules's get() function. Let's use the function URL we generated as endpoint for this purpose and try printing the status, response body as an array and length of the returned response array.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;export default function main() {
  const res = http.get('https://ieaivrjwpcdbuxh4tws5autdoa0sflqi.lambda-url.us-east-1.on.aws/')
  console.log('The response status is', res.status)
  console.log('The response array is', res.json())
  console.log('The length of the response array is', res.json().length)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We could now this run this code with k6 and see the output as well as some test results.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ k6 run test.js

          /\      |‾‾| /‾‾/   /‾‾/   
     /\  /  \     |  |/  /   /  /    
    /  \/    \    |     (   /   ‾‾\  
   /          \   |  |\  \ |  (‾)  | 
  / __________ \  |__| \__\ \_____/ .io

     execution: local
        script: test.js
        output: -

     scenarios: (100.00%) 1 scenario, 1 max VUs, 10m30s max duration (incl. graceful stop):
              * default: 1 iterations for each of 1 VUs (maxDuration: 10m0s, gracefulStop: 30s)

INFO[0001] The response status is 200                    source=console
INFO[0001] The response array is [{"name":"Chompy","species":"Mugger Crocodile","age":41,"sex":"M"},{"name":"Snappy","species":"Orinoco Crocodile","age":24,"sex":"M"},{"name":"Spike","species":"Mugger Crocodile","age":64,"sex":"M"},{"species":"Siamese Crocodile","age":40,"sex":"M","name":"Gator"},{"species":"Freshwater Crocodile","age":44,"sex":"F","name":"Snapper"},{"sex":"F","name":"Sly","species":"Slender-snouted Crocodile","age":11},{"age":24,"sex":"M","name":"Ripjaw","species":"Mugger Crocodile"},{"name":"Thrasher","species":"Orinoco Crocodile","age":67,"sex":"M"}]  source=console
INFO[0001] The length of the response array is 8         source=console

     data_received..................: 6.5 kB 7.3 kB/s
     data_sent......................: 542 B  606 B/s
     http_req_blocked...............: avg=533.74ms min=533.74ms med=533.74ms max=533.74ms p(90)=533.74ms p(95)=533.74ms
     http_req_connecting............: avg=246.67ms min=246.67ms med=246.67ms max=246.67ms p(90)=246.67ms p(95)=246.67ms
     http_req_duration..............: avg=356.86ms min=356.86ms med=356.86ms max=356.86ms p(90)=356.86ms p(95)=356.86ms
       { expected_response:true }...: avg=356.86ms min=356.86ms med=356.86ms max=356.86ms p(90)=356.86ms p(95)=356.86ms
     http_req_failed................: 0.00%  ✓ 0        ✗ 1
     http_req_receiving.............: avg=357µs    min=357µs    med=357µs    max=357µs    p(90)=357µs    p(95)=357µs   
     http_req_sending...............: avg=297µs    min=297µs    med=297µs    max=297µs    p(90)=297µs    p(95)=297µs   
     http_req_tls_handshaking.......: avg=285.27ms min=285.27ms med=285.27ms max=285.27ms p(90)=285.27ms p(95)=285.27ms
     http_req_waiting...............: avg=356.21ms min=356.21ms med=356.21ms max=356.21ms p(90)=356.21ms p(95)=356.21ms
     http_reqs......................: 1      1.117563/s
     iteration_duration.............: avg=894.17ms min=894.17ms med=894.17ms max=894.17ms p(90)=894.17ms p(95)=894.17ms
     iterations.....................: 1      1.117563/s


running (00m00.9s), 0/1 VUs, 1 complete and 0 interrupted iterations
default ✓ [======================================] 1 VUs  00m00.9s/10m0s  1/1 iters, 1 per VU
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So based on the output above, the api is returning 8 crocodiles with it's name, gender and other details. The test results give us some metrics such as data recd., sent and other http metrics. Note that we haven't added any checks so far, though we have imported the check function.&lt;/p&gt;

&lt;p&gt;For this usecase let's do a few checks such as:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Check the response status to be 200&lt;/li&gt;
&lt;li&gt;Is it sending back 8 crocodile details&lt;/li&gt;
&lt;li&gt;Is it giving back the details of 4 male and 4 crocodiles, meaning is the response distrubuted equally based on gender&lt;/li&gt;
&lt;li&gt;There should be atleast one crocodile under 15 years of age&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The code for these checks should look like below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;  check(res, {
    'is status 200': (r) =&amp;gt; r.status === 200,
    'is response length 8': (r) =&amp;gt; r.json().length === 8,
    'is response distributed equally based on gender': (r) =&amp;gt; {
      let maleCount = 0
      let femaleCount = 0
      r.json().forEach(crocodile =&amp;gt; {
        if (crocodile.sex === 'M') {
          maleCount++
        } else if (crocodile.sex === 'F') {
          femaleCount++
        }
      })
      return (maleCount === femaleCount)
    },
    'is any crocodile under 15 years of age': (r) =&amp;gt; {
      const isAnyUnder15 = r.json().some(crocodile =&amp;gt; crocodile.age &amp;lt; 15)
      return isAnyUnder15
    }
  })
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Please keep the checks block inside the main function after which we can run the test again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;% k6 run test.js
---TRUNCATED---
     ✓ is status 200
     ✓ is response length 8
     ✗ is response distributed equally based on gender
      ↳  0% — ✓ 0 / ✗ 1
     ✗ is any crocodile under 15 years of age
      ↳  0% — ✓ 0 / ✗ 1
---TRUNCATED---
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So, couple of our checks are failing.&lt;/p&gt;

&lt;p&gt;All good👍, so we got some grip on how to run k6 and add some checks. We can try doing the same stuff on grafana.&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana
&lt;/h2&gt;

&lt;p&gt;Go to &lt;code&gt;Grafana Cloud &amp;gt; Home &amp;gt; Testing &amp;amp; synthetics &amp;gt; Add a new check &amp;gt; Scripted check&lt;/code&gt;. I have defined the following options in the form.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Job name: test-job
Instance: test-instance
Probe locations: Seoul(I have chosen only one)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the script from, just put the same script we used before, and test/save it.&lt;br&gt;
&lt;a href="https://media2.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%2Fp3h1m09vnnqm36eqpejd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fp3h1m09vnnqm36eqpejd.png" alt="Set synethtic monitoring in Grafana" width="800" height="664"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Note that by default the logs and metrics related to our test would go to the loki and mimir endpoints of Grafana cloud.&lt;/p&gt;

&lt;p&gt;The out of box dashboard for our check should appear in sometime. The assertions panel which gives a stat and timeseries graph(expanded view) for the four checks.&lt;br&gt;
&lt;a href="https://media2.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%2Fjnio6uuw1p7crexvnx2m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fjnio6uuw1p7crexvnx2m.png" alt="Dashboard preview" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a bonus let's go to &lt;code&gt;Home &amp;gt; Dashbooards &amp;gt; New Dashboard&lt;/code&gt; and try creating a custom dashboard panel with mimir(prometheus type datasource).&lt;br&gt;
&lt;a href="https://media2.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%2Foy0whqdzmymds4f72du1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Foy0whqdzmymds4f72du1.png" alt="Current probe checks status" width="800" height="487"&gt;&lt;/a&gt;&lt;br&gt;
Here is the panel json for this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "datasource": {
    "uid": "grafanacloud-prom",
    "type": "prometheus"
  },
  "type": "stat",
  "title": "Current probe checks status",
  "gridPos": {
    "x": 0,
    "y": 8,
    "w": 12,
    "h": 8
  },
  "id": 1,
  "targets": [
    {
      "datasource": {
        "type": "prometheus",
        "uid": "grafanacloud-prom"
      },
      "refId": "A",
      "expr": "sum(probe_checks_total{job=\"test-job\"}) by(result)",
      "range": false,
      "instant": true,
      "editorMode": "code",
      "legendFormat": "__auto",
      "useBackend": false,
      "disableTextWrap": false,
      "fullMetaSearch": false,
      "includeNullMetadata": true,
      "format": "time_series",
      "exemplar": false
    }
  ],
  "options": {
    "reduceOptions": {
      "values": true,
      "calcs": [
        "lastNotNull"
      ],
      "fields": ""
    },
    "orientation": "auto",
    "textMode": "auto",
    "wideLayout": true,
    "colorMode": "value",
    "graphMode": "area",
    "justifyMode": "auto",
    "showPercentChange": false
  },
  "transformations": [
    {
      "id": "calculateField",
      "options": {
        "mode": "binary",
        "reduce": {
          "reducer": "sum"
        },
        "binary": {
          "left": "fail",
          "right": "pass"
        },
        "alias": "total"
      }
    },
    {
      "id": "calculateField",
      "options": {
        "mode": "binary",
        "binary": {
          "left": "pass",
          "operator": "/",
          "right": "total"
        },
        "alias": "success ratio"
      }
    }
  ],
  "fieldConfig": {
    "defaults": {
      "mappings": [],
      "thresholds": {
        "mode": "absolute",
        "steps": [
          {
            "value": null,
            "color": "green"
          },
          {
            "value": 80,
            "color": "red"
          }
        ]
      },
      "color": {
        "mode": "thresholds"
      }
    },
    "overrides": [
      {
        "matcher": {
          "id": "byName",
          "options": "fail"
        },
        "properties": [
          {
            "id": "thresholds",
            "value": {
              "mode": "absolute",
              "steps": [
                {
                  "color": "green",
                  "value": null
                },
                {
                  "color": "red",
                  "value": 1
                }
              ]
            }
          }
        ]
      },
      {
        "matcher": {
          "id": "byName",
          "options": "success ratio"
        },
        "properties": [
          {
            "id": "unit",
            "value": "percentunit"
          },
          {
            "id": "thresholds",
            "value": {
              "mode": "absolute",
              "steps": [
                {
                  "color": "red",
                  "value": null
                },
                {
                  "color": "yellow",
                  "value": 0.8
                },
                {
                  "value": 0.9,
                  "color": "green"
                }
              ]
            }
          }
        ]
      },
      {
        "matcher": {
          "id": "byName",
          "options": "total"
        },
        "properties": [
          {
            "id": "color",
            "value": {
              "mode": "shades",
              "fixedColor": "purple"
            }
          }
        ]
      }
    ]
  },
  "pluginVersion": "11.1.0-70724"
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And one with loki for logs.&lt;br&gt;
&lt;a href="https://media2.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%2Fr0dkh2blb2st7j69l2te.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fr0dkh2blb2st7j69l2te.png" alt="Logs panel" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Cool, we came to the end of this post👏, here we tried synthetic testing first with k6 and used the same script with Grafana cloud and visualized the data with couple of panels. As a housekeeping reminder, you may delete the check that's created in Grafana, so that it stops polling the endpoint, and then may delete the function in AWS Lambda. Thanks for reading!!!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>monitoring</category>
      <category>http</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Logging demo with OTEL Collector, CloudWatch and Grafana</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Sat, 11 May 2024 14:18:27 +0000</pubDate>
      <link>https://dev.to/aws-builders/logging-demo-with-otel-collector-cloudwatch-and-grafana-53l4</link>
      <guid>https://dev.to/aws-builders/logging-demo-with-otel-collector-cloudwatch-and-grafana-53l4</guid>
      <description>&lt;p&gt;Hello 👋, in this post, we would be using the OTEL Demo App with OpenTelemetry Collector and export the logs from the microservices in the app to AWS CloudWatch.&lt;/p&gt;

&lt;p&gt;Let's get started!!! We can create a separate namespace in the kubernetes cluster with &lt;code&gt;kubectl create ns otelcol-logs-demo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;And then create a secret that holds the AWS credentials.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create secret generic aws-credentials \
    --from-literal=AWS_ACCESS_KEY_ID=&amp;lt;access-key-id&amp;gt; \
    --from-literal=AWS_SECRET_ACCESS_KEY=&amp;lt;aws-secret-access-key&amp;gt; \
    -n otelcol-logs-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We would be deploying open telemetry demo app, otel collector as well as Grafana with a single helm &lt;a href="https://artifacthub.io/packages/helm/opentelemetry-helm/opentelemetry-demo" rel="noopener noreferrer"&gt;chart&lt;/a&gt;. The chart comes with other components too, we could disable those that we do not need for this lab. Our helm values would like below.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat values.yaml 
opentelemetry-collector:
  config:
    exporters:
      awscloudwatchlogs:
        log_group_name: otel-logs-demo
        log_stream_name: otel-demo-app
        region: ap-south-2
    service:
      pipelines:
        logs:
          exporters:
            - awscloudwatchlogs
        metrics: {}
        traces: {}
  extraEnvsFrom:
    - secretRef:
        name: aws-credentials
prometheus:
  enabled: false
jaeger:
  enabled: false
opensearch:
  enabled: false
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the values above, we have the cloudwatchlogs exporter section where we specify the region, log group and stream names. We are only setting up logs in the pipeline as we are only dealing with that for this demo. We are then setting the AWS credentials via env vars so that the collector could authenticate and send logs to cloud watch. Then, finally we are disabling certain applications which we do not need for this exercise.&lt;/p&gt;

&lt;p&gt;Let's install the chart.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts

helm install my-otel-demo open-telemetry/opentelemetry-demo -n otelcol-logs-demo -f values.yaml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Head over to CloudWatch logs in the mentioned region and you should be able to see the log group. And, inside the log group we should have the stream.&lt;/p&gt;

&lt;p&gt;We should be able to view the logs, for ex. from cartservice as shown in the screenshot below.&lt;br&gt;
&lt;a href="https://media2.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%2Flybmwuqzylq4c76grlsv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flybmwuqzylq4c76grlsv.png" alt="Logs in console" width="800" height="423"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can now go to Grafana cloud and add the cloud watch datasource with access credentials.&lt;br&gt;
&lt;a href="https://media2.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%2Fant4ggluzt0xsp53fgck.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fant4ggluzt0xsp53fgck.png" alt="CloudWatch datasource in Grafana" width="800" height="467"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And should now be able to see the logs in Grafana for our log group, on the explore tab.&lt;br&gt;
&lt;a href="https://media2.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%2Fu51cdwvf291792ojdk9q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fu51cdwvf291792ojdk9q.png" alt="Logs in Grafana" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a bonus let's try adding a panel that shows the no. of messages per severity.&lt;/p&gt;

&lt;p&gt;Add a panel with query as shown in the screenshot below.&lt;br&gt;
&lt;a href="https://media2.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%2Fd3qa20xo889bzpah1pcj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd3qa20xo889bzpah1pcj.png" alt="Query in panel for logs" width="800" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Go to the transform data tab and apply a transformation to parse the severity_text from the json message.&lt;br&gt;
&lt;a href="https://media2.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%2F467gg6pjgpnf0j6gfbgv.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F467gg6pjgpnf0j6gfbgv.png" alt="JSON transformation" width="800" height="496"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then do a group by transformation to calculate the no. of messages per severity.&lt;br&gt;
&lt;a href="https://media2.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%2Fraaubd4k51hs43nlv75q.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fraaubd4k51hs43nlv75q.png" alt="Group by transformation" width="800" height="562"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Can choose a visualization type such as pie chart like below.&lt;br&gt;
&lt;a href="https://media2.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%2F84wsbwn6zh65a8oaxxuw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F84wsbwn6zh65a8oaxxuw.png" alt="Pie chart showing log count by severity" width="800" height="673"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The options I have set in the panel are as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Title: Logs by severity
Value options &amp;gt; Show &amp;gt; All values
Legend &amp;gt; Legend values: Value, Percent
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok that's it for this post, we saw how to get logs from the otel demo app via open collector to AWS CloudWatch Logs and explored it on Grafana with a sample visualization. We just need to delete the namespace with &lt;code&gt;kubectl delete ns otelcol-logs-demo&lt;/code&gt; so that logs are not sent to AWS cloudwatch. Thanks for reading !!!&lt;/p&gt;

</description>
      <category>aws</category>
      <category>monitoring</category>
      <category>kubernetes</category>
      <category>cloudnative</category>
    </item>
    <item>
      <title>OTEL Tracing demo with ADOT, AWS X-Ray and Grafana</title>
      <dc:creator>Shakir</dc:creator>
      <pubDate>Fri, 26 Apr 2024 10:01:17 +0000</pubDate>
      <link>https://dev.to/aws-builders/tracing-demo-with-aws-x-ray-and-grafana-1pb5</link>
      <guid>https://dev.to/aws-builders/tracing-demo-with-aws-x-ray-and-grafana-1pb5</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Hello 👋, In this post we'll see about sending traces from a demo app to AWS X-Ray via the ADOT(AWS Distro for OpenTelemetry) collector. We would then visualize this on Grafana. Note that we'd deploy the workloads on a kubernetes cluster.&lt;/p&gt;

&lt;p&gt;Here is a picture of what we are trying to accomplish:&lt;br&gt;
&lt;a href="https://media2.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%2Fd79dr2bjzdphe7d2w3de.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fd79dr2bjzdphe7d2w3de.png" alt="Block diagram for the lab" width="800" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Alright, let's get started!!!&lt;/p&gt;
&lt;h2&gt;
  
  
  Namespace
&lt;/h2&gt;

&lt;p&gt;We shall deploy the workloads on a separate namespace. Let's create one.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create ns adot-traces-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Credentials
&lt;/h2&gt;

&lt;p&gt;Store the AWS credentials as a kubernetes secret.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create secret generic aws-credentials \
    --from-literal=AWS_ACCESS_KEY_ID=&amp;lt;access-key-id&amp;gt; \
    --from-literal=AWS_SECRET_ACCESS_KEY=&amp;lt;aws-secret-access-key&amp;gt; \
    -n adot-traces-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ADOT Config
&lt;/h2&gt;

&lt;p&gt;Set the ADOT config in a file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat adot-config.yaml
exporters:
  awsxray:
    region: ap-south-2
receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
service:
  pipelines:
    traces:
      exporters:
        - awsxray
      receivers:
        - otlp
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And create a config map with this file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create configmap adot-config --from-file=adot-config.yaml -n adot-traces-demo 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ADOT Deployment
&lt;/h2&gt;

&lt;p&gt;Setup deployment spec in a file, that injects the secret we created earlier as environment variables and the configmap as a volume.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat adot-deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: adot-collector
  name: adot-collector
spec:
  replicas: 1
  selector:
    matchLabels:
      app: adot-collector
  template:
    metadata:
      labels:
        app: adot-collector
    spec:
      containers:
        - args:
            - '--config=/etc/adot-config.yaml'
          envFrom:
            - secretRef:
                name: aws-credentials
          image: public.ecr.aws/aws-observability/aws-otel-collector:latest
          name: adot-collector
          volumeMounts:
            - mountPath: /etc/adot-config.yaml
              name: config-volume
              subPath: adot-config.yaml
      volumes:
        - configMap:
            name: adot-config
          name: config-volume
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Create the deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create -f adot-deploy.yaml -n adot-traces-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pod in the deployment should be running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get po -n adot-traces-demo
NAME                              READY   STATUS    RESTARTS   AGE
adot-collector-7cbf849b89-b4bkl   1/1     Running   0          3m26s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  ADOT Service
&lt;/h2&gt;

&lt;p&gt;We can expose the ADOT deployment with a service spec that exposes the grpc port 4317 as follows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat adot-svc.yaml 
apiVersion: v1
kind: Service
metadata:
  name: adot-collector-service
spec:
  selector:
    app: adot-collector
  ports:
    - protocol: TCP
      port: 4317
      targetPort: 4317
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can now create the service.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create -f adot-svc.yaml -n adot-traces-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The endpoint IP should match with the pod IP.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get ep -n adot-traces-demo
NAME                     ENDPOINTS         AGE
adot-collector-service   10.1.3.187:4317   22s

$ kubectl get po -n adot-traces-demo -o wide
NAME                              READY   STATUS    RESTARTS   AGE     IP           NODE             NOMINATED NODE   READINESS GATES
adot-collector-7cbf849b89-b4bkl   1/1     Running   0          7m11s   10.1.3.187   docker-desktop   &amp;lt;none&amp;gt;           &amp;lt;none&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Demo app
&lt;/h2&gt;

&lt;p&gt;We can now deploy the sample demo app which can send traces to ADOT collector, with the following manifest.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ cat k6-tracing-deploy.yaml 
apiVersion: apps/v1
kind: Deployment
metadata:
  name: xk6-tracing
spec:
  replicas: 1
  selector:
    matchLabels:
      app: xk6-tracing
  template:
    metadata:
      labels:
        app: xk6-tracing
    spec:
      containers:
        - env:
            - name: ENDPOINT
              value: adot-collector-service:4317
          image: ghcr.io/grafana/xk6-client-tracing:v0.0.2
          name: xk6-tracing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's create the deployment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;kubectl create -f k6-tracing-deploy.yaml -n adot-traces-demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both the ADOT collector and k6-tracing pods should now be running.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ kubectl get po -n adot-traces-demo   
NAME                              READY   STATUS    RESTARTS   AGE
adot-collector-7cbf849b89-b4bkl   1/1     Running   0          14m
xk6-tracing-69b48fcfd9-bjzbd      1/1     Running   0          24s
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  X-Ray
&lt;/h2&gt;

&lt;p&gt;We can now headover to AWS X-Ray,  in &lt;code&gt;ap-south-2&lt;/code&gt; region that we mentioned in the adot-config.&lt;br&gt;
&lt;a href="https://media2.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%2Flzblt5bwa2ikustn0h6h.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flzblt5bwa2ikustn0h6h.png" alt="Traces in AWS X-Ray" width="800" height="395"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The nodes(services) shown in the screenshot belong to our demo application. We could filter for traces that passes through a particular service name for ex. article service, like below.&lt;br&gt;
&lt;a href="https://media2.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%2F793iwmcy6cl1qweessew.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F793iwmcy6cl1qweessew.png" alt="Traces " width="800" height="282"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we click on a single trace we should be able to see a complete service map for that trace, that shows all the services that trace traverses.&lt;br&gt;
&lt;a href="https://media2.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%2F37vtbbx0fkowsnpxtjty.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F37vtbbx0fkowsnpxtjty.png" alt="Service map for a trace" width="800" height="411"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If we go a further down on this we should be able to see the details for segments/spans with in this trace.&lt;br&gt;
&lt;a href="https://media2.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%2Fib655esdlfxoxeyr4cac.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fib655esdlfxoxeyr4cac.png" alt="Spans in trace" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Grafana
&lt;/h2&gt;

&lt;p&gt;So we far we were able to see the traces in AWS X-Ray, we can do a similar exercise on Grafana. I am using a Grafana Cloud Free subscription for this lab.&lt;/p&gt;

&lt;p&gt;Go to Connections, Add a new connection and search for X-Ray and install it.&lt;br&gt;
&lt;a href="https://media2.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%2Flvks2wrj6m13x11lpsxx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Flvks2wrj6m13x11lpsxx.png" alt="Install X-Ray" width="800" height="136"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can then go to datasources, add a new X-Ray datasource with the access key id, secret access key, and default region(I have chosen ap-south-2 which matches with adot config).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.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%2F5z1wgvj4cuvlb28tuzkj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F5z1wgvj4cuvlb28tuzkj.png" alt="Add X-Ray datasource" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;All good, we can try adding a new panel, go to dashboards &amp;gt; new dashboard and a new visualization with table as panel type and a sample query for ex. &lt;code&gt;service(id(name: "article-service"  ))&lt;/code&gt;&lt;br&gt;
&lt;a href="https://media2.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%2Fo31cgl9cpatnaw8jnhin.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fo31cgl9cpatnaw8jnhin.png" alt="Traces in tabular format" width="800" height="567"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We can click on one of the traces we should take us to the explore view where we can see the node graph(service map)&lt;br&gt;
&lt;a href="https://media2.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%2F65b4n0id03356ftff66f.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2F65b4n0id03356ftff66f.png" alt="Node graph for trace in grafana" width="800" height="402"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;We should also see the trace explorer that shows the individual spans.&lt;br&gt;
&lt;a href="https://media2.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%2Fwikqutl9srogd6t62qm4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.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%2Fwikqutl9srogd6t62qm4.png" alt="Trace explorer in Grafana" width="800" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay so we reached this far, that was some fun exploring traces on AWS and Grafana with Open Telemetry. Thank you for reading !!! &lt;/p&gt;

&lt;h2&gt;
  
  
  CleanUp
&lt;/h2&gt;

&lt;p&gt;Just delete the namespace with &lt;code&gt;kubectl delete ns adot-traces-demo&lt;/code&gt; and it should remove the workloads we deployed from kubernetes and stop sending any new data to the cloud.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>grafana</category>
      <category>kubernetes</category>
      <category>otel</category>
    </item>
  </channel>
</rss>
