<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0">
  <channel>
    <title><![CDATA[Kubernetes - Elastic Observability Labs]]></title>
    <description><![CDATA[Trusted security news & research from the team at Elastic.]]></description>
    <copyright><![CDATA[© 2026. Elasticsearch B.V. All Rights Reserved]]></copyright>
    <image>
      <title><![CDATA[Kubernetes - Elastic Observability Labs]]></title>
      <url>https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltad972c1c27dbefc6/6a88d9782904ea5e8511d473/observability-labs-thumbnail.png</url>
      <link>https://www.elastic.co/observability-labs/blog/category/kubernetes</link>
    </image>
    <link>https://www.elastic.co/observability-labs/blog/category/kubernetes</link>
    <atom:link href="https://www.elastic.co/observability-labs/rss/category/kubernetes.xml" rel="self" type="application/rss+xml"/>
    <language><![CDATA[en]]></language>
    <lastBuildDate>Tue, 15 Sep 2026 21:38:07 GMT</lastBuildDate>
  <item>
    <title><![CDATA[AI agent observability for Microsoft Foundry: two env vars, no collector]]></title>
    <description><![CDATA[Set up LLM tracing once and every model call, tool execution and handoff from your Foundry agent arrives in Kibana as one queryable trace, with token counts on each span and code for Agent Framework, LangGraph and Node.js.]]></description>
    <content:encoded><![CDATA[<p>Instrument your Microsoft Foundry agent with the OTel SDK, set two environment variables, and every LLM call, tool execution and agent handoff lands in Kibana as a queryable trace with token counts on every span. No OTel Collector sits in between, and nothing gets rewritten into a proprietary schema on the way in. Normal APM assumes a successful response is a correct one. AI agent observability can't, because an agent run can return HTTP 200 and still answer wrong, having burned 40,000 tokens to get there, so what you need is the whole decision tree. Code below for <a href="https://github.com/microsoft/agent-framework">Agent Framework</a>, <a href="https://github.com/langchain-ai/langgraph">LangGraph</a>, and custom Node.js containers.</p>
<h2 id="whydoesaiagentobservabilityneedadifferentmodel">Why does AI agent observability need a different model?</h2>
<p>Standard application monitoring answers: "Did this request succeed? How long did the database query take?" These questions work because traditional software is deterministic: same input, same output, errors have error codes.</p>
<p>AI agents break that model. A single user request to a Foundry hosted agent might trigger ten LLM calls, five tool executions, two file searches, and an MCP server call, each with its own latency and potential failure mode. The agent can return HTTP 200 and still produce a wrong answer. It can silently loop on the same tool call until it hits a token limit. It can hand off a task to a specialist agent and lose context in transit, and none of that shows up in your error rate or P99 latency.</p>
<p>The questions you actually need to answer in production are different:</p>
<ul>
<li>Why did this run consume 40,000 tokens when the average is 3,000?</li>
<li>Which tool call is responsible for the long tail?</li>
<li>When the orchestrator handed off to the search agent, did the trace context survive?
For those, you need hierarchical trace data that maps the agent's decision tree, not just its I/O boundary.</li>
</ul>
<p>Foundry Agent Service has strong built-in observability. For Prompt agents, <a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/trace-agent-setup">server-side tracing</a> is zero-config: connect an Application Insights resource to your project and Foundry automatically captures inputs, outputs, tool calls, token usage, and latency with no code changes required. For Hosted agents, you add client-side instrumentation to your container code, which is what this post focuses on.</p>
<p>What Foundry's built-in tracing doesn't give you is the ability to run arbitrary queries over the raw OTel data or correlate agent traces with infrastructure telemetry from the rest of your stack. That's where routing those same traces to Elastic adds value. The <a href="https://www.youtube.com/watch?v=WprbDyANqy0">Instrument → Debug → Evaluate → Optimize loop</a> that Foundry supports in its portal is even more powerful when you can drive it from ES|QL queries against the full trace corpus.</p>
<p>A note on Application Insights: it receives OTel traces but converts them into its own schema on ingestion. Elastic stores the data as-is (resource attributes, semantic convention fields, everything preserved) so you can query exactly what was emitted, without a translation layer between you and the data.</p>
<h2 id="llmtracingwiththeopentelemetrygenaiconventions">LLM tracing with the OpenTelemetry GenAI conventions</h2>
<p>Foundry uses <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/README.md">OpenTelemetry's GenAI semantic conventions</a> to structure its traces. Three core span types cover most agent workloads.</p>
<p>Stability note: All <code>gen_ai.*</code> span names and attributes currently carry a <a href="https://github.com/open-telemetry/semantic-conventions-genai/blob/main/docs/gen-ai/gen-ai-agent-spans.md">Development stability badge</a> in the OpenTelemetry registry; they are pre-1.0 and have already changed once (for example, <code>gen_ai.system</code> was renamed to <code>gen_ai.provider.name</code>). Pin your SDK versions and expect attribute strings to shift before these conventions reach stable status.</p>
<p><strong>invoke_agent</strong> wraps the entire agent execution. Every run gets one of these as the root span.</p>
<p><strong>chat</strong> is a single LLM API call. It carries gen_ai.provider.name (the provider: openai, anthropic, aws.bedrock), gen_ai.request.model, and gen_ai.usage.input_tokens and gen_ai.usage.output_tokens on every single call, which is what makes per-call token cost attribution possible.</p>
<p><strong>execute_tool</strong> is a tool or function invocation triggered by the model, nested under the chat span that requested it.</p>
<p>For multi-agent systems, Microsoft (in collaboration with Cisco Outshift) extended these conventions with additional span types now integrated into Foundry, Agent Framework, LangChain, LangGraph, and the OpenAI Agents SDK:</p>
<ul>
<li><code>execute_task</code> captures task planning and how work is decomposed and distributed across agents.</li>
<li>agent_to_agent_interaction (child of invoke_agent) traces direct communication between agents.</li>
<li>agent_planning logs an agent's internal planning steps  </li>
<li>agent.state.management covers context and memory operations</li>
</ul>
<p>The resulting trace for a multi-step Foundry agent looks like this:</p>
<pre><code>[invoke_agent: research-agent]             ← root: the whole task
  [agent_planning]                         ← agent decides its approach
  [chat: azure]                            ← first LLM call
  [execute_tool: file_search]              ← Foundry built-in tool call
  [chat: azure]                            ← LLM call to reason over results
  [agent_to_agent_interaction: summarizer] ← handoff to specialist agent
    [invoke_agent: summarizer]             ← nested agent execution
      [chat: azure]
  [chat: azure]                            ← final synthesis call
</code></pre>
<p>In Elastic, the agent trace renders as a waterfall. You see how long each step took, which one errored, and where the tokens went, including across the agent handoff boundary. That's the data model we're getting into Elastic.</p>
<h2 id="howtoinstrumentafoundryhostedagenttoemitoteltraces">How to instrument a Foundry Hosted agent to emit OTel traces</h2>
<p>Foundry <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/hosted-agents">Hosted agents</a> run your code in a container managed by Foundry. Because you own the container, you control the instrumentation. Which path you take depends on the framework you're using.</p>
<p>| Framework | Language | Key package | Instrumentation method |
|---|---|---|---|
| Agent Framework | Python | <code>azure-ai-projects</code> | <code>AIProjectInstrumentor().instrument()</code> |
| LangGraph | Python | <code>langchain-azure-ai</code> | <code>AzureAIOpenTelemetryTracer</code> callback |
| Custom container | TypeScript / Node.js | <code>@opentelemetry/sdk-node</code> | Manual <code>startActiveSpan</code> |</p>
<h3 id="traceagentframeworkagentswiththeazureaiprojectssdkpython">Trace Agent Framework agents with the Azure AI Projects SDK (Python)</h3>
<p><a href="https://github.com/microsoft/agent-framework">Agent Framework</a> is Microsoft's framework for building Hosted agents on Foundry. It calls the Foundry Responses API for model inference and tool orchestration. To route traces to Elastic, configure the OTel SDK at container startup and add the OTLP exporter pointing at your Elastic endpoint.</p>
<p>Install the packages:</p>
<pre><code>pip install azure-ai-projects azure-identity opentelemetry-sdk azure-core-tracing-opentelemetry opentelemetry-exporter-otlp-proto-http
</code></pre>
<p>Configure at startup in your agent container:</p>
<pre><code>import os
from azure.identity import DefaultAzureCredential
from azure.ai.projects import AIProjectClient
from azure.ai.projects.telemetry import AIProjectInstrumentor
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

# OTLPSpanExporter reads OTEL_EXPORTER_OTLP_ENDPOINT and
# OTEL_EXPORTER_OTLP_HEADERS automatically, no need to pass them explicitly
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

# Tracing is off by default — opt in explicitly (experimental preview as of 2026)
# Set AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true in your container environment
AIProjectInstrumentor().instrument()

client = AIProjectClient(
    endpoint=os.getenv("AZURE_AI_FOUNDRY_PROJECT_ENDPOINT"),
    credential=DefaultAzureCredential(),
)
</code></pre>
<p>Every model call, tool invocation, and agent handoff made through the Responses API now produces a structured OTel span with token usage, model identity, and tool metadata. Note that GenAI tracing in the Azure AI Projects SDK is an experimental preview — you must set <code>AZURE_EXPERIMENTAL_ENABLE_GENAI_TRACING=true</code> in your container environment and call <code>AIProjectInstrumentor().instrument()</code> before any agent runs, or no spans are produced.</p>
<h3 id="tracelanggraphagentswithazureaiopentelemetrytracerpython">Trace LangGraph agents with AzureAIOpenTelemetryTracer (Python)</h3>
<p><a href="https://github.com/langchain-ai/langgraph">LangGraph</a> is a supported framework for Foundry Hosted agents. Microsoft's <code>langchain-azure-ai</code> package provides an OTel-compliant tracer for LangGraph that emits spans for graph steps, tool invocations, and model calls. Configure the OTLP exporter the same way, then attach the tracer as a callback on each invocation:</p>
<pre><code>pip install langchain-azure-ai langgraph langchain langchain-openai opentelemetry-sdk opentelemetry-exporter-otlp-proto-http azure-identity
</code></pre>
<pre><code>from langchain_azure_ai.callbacks.tracers import AzureAIOpenTelemetryTracer
from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter

# OTLPSpanExporter reads OTEL_EXPORTER_OTLP_ENDPOINT and
# OTEL_EXPORTER_OTLP_HEADERS automatically, no need to pass them explicitly
provider = TracerProvider()
provider.add_span_processor(BatchSpanProcessor(OTLPSpanExporter()))
trace.set_tracer_provider(provider)

# AzureAIOpenTelemetryTracer is Microsoft's supported tracer for LangChain and LangGraph.
# Pass it as a callback when invoking your graph — it uses the active TracerProvider above.
azure_tracer = AzureAIOpenTelemetryTracer(name="my-langgraph-agent")

# app is your compiled LangGraph workflow (e.g. workflow.compile())
config = {"callbacks": [azure_tracer]}
result = app.invoke({"messages": [...]}, config=config)
</code></pre>
<h3 id="tracecustomnodejsagentswiththeopentelemetrysdktypescript">Trace custom Node.js agents with the OpenTelemetry SDK (TypeScript)</h3>
<p>For custom agent architectures in Node.js, wrap your orchestration logic directly with the OTel SDK. The startActiveSpan API handles parent-child nesting automatically. Any span created inside the callback is a child of the current span, giving you the decision tree hierarchy without explicit parent references.</p>
<pre><code>import { NodeSDK } from "@opentelemetry/sdk-node";
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-proto";
import { trace, SpanKind, SpanStatusCode } from "@opentelemetry/api";

// OTLPTraceExporter reads OTEL_EXPORTER_OTLP_ENDPOINT and
// OTEL_EXPORTER_OTLP_HEADERS automatically, no need to pass them explicitly
const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter(),
  serviceName: "my-foundry-agent",
});
sdk.start();

const tracer = trace.getTracer("foundry-agent", "1.0.0");

// Wrap your agent's entry point: everything inside becomes a child span
async function runAgent(userMessage: string) {
  return tracer.startActiveSpan(
    "invoke_agent my-agent",
    {
      kind: SpanKind.INTERNAL,
      attributes: {
        "gen_ai.operation.name": "invoke_agent",
        "gen_ai.agent.name": "my-agent",
        "gen_ai.provider.name": "azure",
      },
    },
    async (span) =&gt; {
      try {
        const result = await agentLoop(userMessage);
        span.setStatus({ code: SpanStatusCode.OK });
        return result;
      } catch (err) {
        span.recordException(err as Error);
        span.setStatus({ code: SpanStatusCode.ERROR });
        throw err;
      } finally {
        span.end();
      }
    }
  );
}

// Record token usage on every LLM call
async function callAzureOpenAI(messages: Message[]) {
  return tracer.startActiveSpan(
    "chat azure",
    {
      kind: SpanKind.CLIENT,
      attributes: {
        "gen_ai.operation.name": "chat",
        "gen_ai.provider.name": "azure",
        "gen_ai.request.model": "gpt-4o",
      },
    },
    async (span) =&gt; {
      const response = await client.chat.completions.create({ model: "gpt-4o", messages });
      span.setAttributes({
        "gen_ai.usage.input_tokens": response.usage.prompt_tokens,
        "gen_ai.usage.output_tokens": response.usage.completion_tokens,
        "gen_ai.response.model": response.model,
      });
      span.end();
      return response;
    }
  );
}
</code></pre>
<p>If your Node.js agent calls the OpenAI SDK directly, OpenTelemetry JS also ships an <a href="https://github.com/open-telemetry/opentelemetry-js-contrib/tree/main/packages/instrumentation-openai">instrumentation-openai</a> auto-instrumentation package as an alternative to the manual spans above (it works with Azure OpenAI clients too). Check its semantic-convention version against what's shown here before relying on it; third-party instrumentations for LangChain and others also exist but vary in how current they are.</p>
<h2 id="howtogetyourelasticmanagedotlpendpointandapikey">How to get your Elastic managed OTLP endpoint and API key</h2>
<p>The managed OTLP endpoint (mOTLP) is generally available on both <strong>Elastic Cloud Serverless</strong> and <strong>Elastic Cloud Hosted</strong>. It is not available for self-managed, ECE, or ECK deployments. For those, use the <a href="https://www.elastic.co/docs/reference/edot-collector/modes#edot-collector-as-gateway">EDOT Collector as a gateway</a> instead.</p>
<p><strong>Serverless:</strong> Log in to <a href="https://cloud.elastic.co">Elastic Cloud</a> → find your project → <strong>Manage</strong> → <strong>Application endpoints, cluster and component IDs</strong> → <strong>Ingest</strong>. Copy the endpoint value. Alternatively, go to <strong>Add data → Applications → OpenTelemetry</strong> inside your project, which also generates a pre-configured API key.</p>
<p><strong>Elastic Cloud Hosted:</strong> Log in → <strong>Hosted deployments</strong> → <strong>Manage</strong> → <strong>Application endpoints</strong> → <strong>Managed OTLP</strong>. Copy the public endpoint value.</p>
<p>The API key must have event:write privilege on the apm application. The Elastic Cloud quickstart wizard generates this for you. Set two environment variables in your Foundry Hosted agent container:</p>
<pre><code>export OTEL_EXPORTER_OTLP_ENDPOINT="https://&lt;your-motlp-endpoint&gt;"
export OTEL_EXPORTER_OTLP_HEADERS="Authorization=ApiKey &lt;your-api-key&gt;"
</code></pre>
<p>Note the header format: ApiKey \&lt;key&gt;, not Bearer. And the env var uses = as the separator between header name and value, not :.</p>
<p>Traces sent to the endpoint land in the traces-generic.otel-default data stream by default. In Kibana, find them under <strong>Observability → APM → Traces</strong> or query them directly with ES|QL against traces-generic.otel-*. No OTel Collector required. No schema translation.</p>
<h2 id="autoinstrumentfoundryagentsonakswiththeopentelemetryoperator">Auto-instrument Foundry agents on AKS with the OpenTelemetry Operator</h2>
<p>Foundry Hosted agents support bring-your-own VNet and can run container workloads on AKS. If you're in that configuration, you can skip the SDK-level OTLP configuration entirely. Add this annotation to your pod spec and the <a href="https://opentelemetry.io/docs/platforms/kubernetes/operator/">OpenTelemetry Operator</a> injects the SDK automatically:</p>
<pre><code>annotations:
  instrumentation.opentelemetry.io/inject-python: "true"
</code></pre>
<p>You still set OTEL_EXPORTER_OTLP_ENDPOINT and OTEL_EXPORTER_OTLP_HEADERS to point at Elastic. The SDK wiring is handled for you.</p>
<p>For agents running in Azure Container Apps, the platform's built-in managed OTel collector routes to Application Insights by default. To also get traces into Elastic, use the SDK-level configuration from the examples above alongside Foundry's built-in observability, or run a sidecar OTel Collector with an OTLP exporter configured for your Elastic endpoint.</p>
<h2 id="howtokeepamultiagenthandoffinonetrace">How to keep a multi-agent handoff in one trace</h2>
<p>If your architecture involves an orchestrator delegating to specialist agents, you want all of that to appear as one connected trace, not N disconnected fragments.</p>
<p>OTel handles this via the <a href="https://www.w3.org/TR/trace-context/">W3C TraceContext standard</a> (traceparent and tracestate headers). For HTTP-based inter-agent calls, the SDK propagates these automatically. For queue-based handoffs (Service Bus, Event Hubs), you carry the context in the message itself:</p>
<pre><code>from opentelemetry import propagate, context

# Sending agent: inject the active trace context into the message
carrier = {}
propagate.inject(carrier)

await queue.send_message({
    "payload": task_data,
    "trace_context": carrier,  # {"traceparent": "00-abc123...", "tracestate": "..."}
})

# Receiving agent: restore the trace context before doing any work
incoming_ctx = propagate.extract(message["trace_context"])
with context.use_context(incoming_ctx):
    await process_task(message["payload"])
</code></pre>
<p>With this in place, the entire work item appears as one trace in Elastic, from orchestrator through every worker agent, across process boundaries. The waterfall shows exactly where time was spent at each tier.</p>
<h2 id="whatfoundryagenttraceslooklikeinelasticapm">What Foundry agent traces look like in Elastic APM</h2>
<p>Traces arrive as a hierarchical waterfall in the APM UI. For each agent invocation, you get the full span tree: every LLM call, tool execution, and sub-agent call nested under the root invoke_agent span. Every chat span carries gen_ai.usage.input_tokens and gen_ai.usage.output_tokens, so you can see at a glance which model invocations are expensive and which are routine. Errors surface as specific failed spans with full stack traces, not a red line on a latency graph.</p>
<p>You can also query the trace data directly with ES|QL. To find every agent run in the past hour that consumed more than 20,000 input tokens:</p>
<pre><code>FROM traces-generic.otel-default
| WHERE attributes.gen_ai.operation.name == "invoke_agent"
  AND @timestamp &gt; NOW() - 1 hour
| STATS total_input_tokens = SUM(attributes.gen_ai.usage.input_tokens)
    BY trace.id, attributes.gen_ai.agent.name
| WHERE total_input_tokens &gt; 20000
| SORT total_input_tokens DESC
</code></pre>
<p>Combining trace structure with token accounting in a single query is what OTel native storage makes possible.</p>
<h2 id="howtosampleagenttraceswithoutlosingerrors">How to sample agent traces without losing errors</h2>
<p>For tail-based sampling, add a local <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/tree/main/processor/tailsamplingprocessor">OTel Collector</a> as an intermediate hop before the Elastic endpoint. A policy that keeps all error traces, all slow traces, and samples down routine successes is a reasonable starting point:</p>
<pre><code># Run this Collector between your Foundry Hosted agent and the Elastic endpoint
processors:
  tail_sampling:
    decision_wait: 10s
    policies:
      - name: errors
        type: status_code
        status_code: { status_codes: [ERROR] }
      - name: slow-traces
        type: latency
        latency: { threshold_ms: 5000 }
      - name: sample-routine
        type: probabilistic
        probabilistic: { sampling_percentage: 10 }

exporters:
  otlp/elastic:
    endpoint: "${OTEL_EXPORTER_OTLP_ENDPOINT}"
    headers:
      Authorization: "ApiKey ${ELASTIC_API_KEY}"
    sending_queue:
      enabled: true
      sizer: bytes
      queue_size: 50_000_000
      block_on_overflow: true
</code></pre>
<p>If you're sending directly from the SDK without a Collector, start at 100% sampling. Agent trace volume is usually smaller than expected. Evaluate storage costs after a week and tune from there. For most Foundry Hosted agent workloads, the direct SDK path is the right starting point.</p>
<h2 id="howtouseagenttracesforevaluationandoptimization">How to use agent traces for evaluation and optimization</h2>
<p>The Build session demo framed tracing as a four-step production loop: instrument, debug, evaluate, optimize. Debugging is the obvious first payoff: when an agent run fails or produces a wrong answer, the trace shows exactly which span introduced the problem. But the evaluate and optimize steps are where the loop compounds.</p>
<p>With traces flowing into Elastic, you can identify which agent runs produced incorrect or low-quality outputs, then pull those traces directly into an evaluation workflow in Foundry. The trace gives you the full context (the prompt, the tool calls, the model's reasoning path) that an eval needs to score quality and catch regressions. From there, optimization targets are concrete: this tool call is slow, this model is expensive for its output quality, this planning step runs unnecessarily on every request.</p>
<p>Foundry's <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/concepts/agent-optimizer-overview">agent optimizer</a> can use trace data to improve agent instructions automatically. Elastic gives you the query layer to find the traces worth optimizing in the first place.</p>
<h2 id="whatyouneedtostarttracingfoundryagents">What you need to start tracing Foundry agents</h2>
<p>You need four things: an <a href="https://cloud.elastic.co/registration">Elastic Cloud Serverless project</a> (the managed OTLP endpoint is included), your endpoint URL and API key from Project Management → Edit alias, instrumentation that matches your stack (<code>AIProjectInstrumentor</code> for Agent Framework, <code>AzureAIOpenTelemetryTracer</code> from <code>langchain-azure-ai</code> for LangGraph, or the OTel SDK directly for custom container code), and one agent run to verify the trace appears in Kibana's APM view.</p>
<p>The first trace that shows you exactly which tool call caused that 45-second timeout makes the setup worth it.</p>
<hr />
<p><em>More resources: <a href="https://learn.microsoft.com/en-us/azure/foundry/agents/overview">Microsoft Foundry Agent Service overview</a> | <a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/trace-agent-setup">Set up tracing in Foundry</a> | <a href="https://learn.microsoft.com/en-us/azure/foundry/observability/how-to/trace-agent-framework">Tracing integrations by framework</a> | <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic managed OTLP endpoint docs</a> | <a href="https://opentelemetry.io/docs/specs/semconv/gen-ai/">OpenTelemetry GenAI semantic conventions</a> | <a href="https://www.youtube.com/watch?v=WprbDyANqy0">Build 2026 DEM341: Any agent, any cloud</a></em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ai-agent-observability-microsoft-foundry</link>
    <guid isPermaLink="false">ai-agent-observability-microsoft-foundry</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Greg Crist]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt85d71c4aee65b074/6a8e9dc313070e654f204872/elastic-de_149846_720x420_11-B.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Sleep through the 3am page: automated incident response with Elastic on Red Hat OpenShift]]></title>
    <description><![CDATA[Elastic Observability handles three routine incidents on its own: it scales, restarts or rolls back the workload, then confirms the service recovered, all with the reasoning model inside your own cluster.]]></description>
    <content:encoded><![CDATA[<p>Every operations team knows the 3am page. A service slows down, an alert fires, and someone wakes up to start digging through dashboards, logs, and traces to find the one signal that explains the outage. By the time they find it, customers have already felt it. This post is about a different approach: autonomous SRE, where Elastic Observability handles the routine incident from detection through fix, running entirely inside your own Red Hat OpenShift cluster, so the page that wakes someone up is the exception, not the routine. No deep configuration here, just the high-level picture of how it works and what it changes for the people who run the systems.</p>
<h2 id="whymanualincidentresponsecantkeepupatscale">Why manual incident response can't keep up at scale</h2>
<p>Modern platforms run at a scale the human brain was never meant to triage. A single service can handle tens of thousands of transactions a second, and each one leaves a trail of metrics, logs, and traces. When something breaks, the answer is somewhere in that flood of data, but finding it by hand is slow, and slow is expensive. The AI era is making it worse, not better: data volumes are compounding, and the more tools a team adds, the more scattered the answer becomes.</p>
<p>Three things go wrong in the manual model:</p>
<ul>
<li><strong>Exploding cost and volume.</strong> The sheer amount of telemetry is compounding observability spend, and teams often drop data to control the bill, which means the one signal that explains the outage may not even be there when they look.  </li>
<li><strong>Lost and fragmented context.</strong> The real story usually lives across container logs, infrastructure events, and application traces at once. When those sit in different tools, no single platform correlates them at the moment an alert fires, and stitching them together under pressure is slow and easy to get wrong.  </li>
<li><strong>Slow investigations and rushed calls.</strong> Every minute spent searching for the cause is a minute the outage continues, and a 3am restart made on a hunch can make the incident worse instead of better.</li>
</ul>
<p>The result is long outages, stressed teams, and a Mean Time to Resolution (MTTR) that stays stubbornly high no matter how many dashboards you build. Dashboards show you the problem. They do not fix it.</p>
<h2 id="whatautomatedincidentresponsedoesendtoend">What automated incident response does end to end</h2>
<p>Autonomous SRE means the system handles the full incident loop on its own: it detects the problem, investigates the likely cause, resolves it with a corrective action, and verifies the action worked. The same loop a skilled on-call engineer runs in their head, running continuously and at machine speed.</p>
<p>It follows a simple cycle: <strong>detect, investigate, resolve, verify.</strong></p>
<ol>
<li><strong>Detect.</strong> Elastic Observability continuously collects the metrics, logs, and traces from across the environment and maintains a live system model: an always-current map of your services, hosts, and the dependencies between them. It surfaces the events that actually matter instead of flooding the team with raw alerts.  </li>
<li><strong>Investigate.</strong> When something crosses a threshold, the platform pulls together the related evidence and asks an AI model, one that reads the evidence and explains it in plain language, what is happening, how confident it is, and which other services the problem will affect.  </li>
<li><strong>Resolve.</strong> Based on that diagnosis, a remediation step is carried out, for example, scaling a service, restarting it, or rolling back a recent change, either automatically or with a person's approval.  </li>
<li><strong>Verify.</strong> The system then checks whether the fix worked and the service returned to healthy, and records the whole sequence so a human can review exactly what happened and why.</li>
</ol>
<p>The important word is <em>loop</em>. The system does not stop at an alert or a recommendation. It closes the gap between knowing and doing, which is precisely the gap where outages live.</p>
<h3 id="howelasticobservabilitycorrelatestelemetryforairootcauseanalysis">How Elastic Observability correlates telemetry for AI root cause analysis</h3>
<p>Autonomous action is only as good as the context behind it, and context is where Elastic Observability is strong. It brings the metrics, logs, and traces from across your environment into one place, then builds a live model of how those pieces connect, so the system reasons over the full story rather than a single noisy signal.</p>
<p>That unified view matters for three reasons:</p>
<ul>
<li><strong>Better diagnosis.</strong> Grounding the AI model in real, correlated telemetry rather than a single metric means the diagnosis reflects what is actually happening, not a guess.  </li>
<li><strong>Fewer false moves.</strong> When the evidence is complete, the system is far less likely to act on a symptom and miss the cause.  </li>
<li><strong>A record you can trust.</strong> Every observation, decision, and action is captured, so the incident comes with a built-in audit trail instead of a gap in the story.</li>
</ul>
<p>This is the same foundation Elastic already provides for search and security, applied to keeping services healthy.</p>
<h2 id="whyautomatedincidentresponserunsonredhatopenshift">Why automated incident response runs on Red Hat OpenShift</h2>
<p>The reason this approach works for regulated, sovereign, and on-premises environments is that the entire loop runs inside your own Red Hat OpenShift cluster. Nothing about an incident, not the telemetry, not the diagnosis, not the action, has to leave your walls.  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt57c8143de2338f9a/6a8e9fcdfa5f3ca2d90e3a40/image_1.png" alt="The whole loop inside one cluster. Elastic Observability, the reasoning model on Red Hat OpenShift AI, and the remediation agent all run on Red Hat OpenShift, so telemetry and decisions never leave the cluster." /></p>
<p>Four things make Red Hat OpenShift the right home for it:</p>
<ul>
<li><strong>The full stack is in-cluster.</strong> Elastic Observability is deployed and managed natively on Red Hat OpenShift through the Elastic Cloud on Kubernetes (ECK) operator, so the data foundation lives next to the workloads it watches. That keeps analysis fast and keeps your data under your control. It runs on managed OpenShift (such as Red Hat OpenShift Service on AWS or Azure Red Hat OpenShift) or self-managed Red Hat OpenShift.  </li>
<li><strong>The reasoning model runs locally too.</strong> Red Hat OpenShift AI serves the language model, for example, IBM Granite, inside the same cluster. The AI that diagnoses your incidents never sends your telemetry to an outside service, which is what makes the approach viable for air-gapped and sovereign deployments.  </li>
<li><strong>Remediation speaks to Red Hat OpenShift natively.</strong> When the system acts, it is performing ordinary Red Hat Kubernetes operations: scaling a deployment, restarting a workload, rolling back to the last good version. These are the same actions your platform team already trusts, now triggered automatically and verified.  </li>
<li><strong>It is a packaged, validated starting point.</strong> The whole pattern ships as a quickstart in the Red Hat catalog, built jointly so a team can stand it up on an existing Red Hat OpenShift environment and see the loop work without assembling the pieces from scratch.</li>
</ul>
<p>The payoff is sovereignty without a tradeoff: you get machine-speed, AI-driven incident response and you keep every byte of telemetry and every decision inside infrastructure you already run.</p>
<h2 id="fromalerttoverifiedfix">From alert to verified fix</h2>
<p>Here is the routine incident, told the way the team experiences it once the loop is in place.  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1913df630fd924ca/6a8e9fd1f59d7c15d3c93817/image_2.png" alt="From alert to root cause. Raw signals are correlated and reasoned over, then consolidated into a single situation with a named root cause, the blast radius, and a confidence score." /></p>
<p>A service starts to slow down. Response times climb past their healthy range and an alert fires, the same trigger that would normally start a human's night. Instead, the platform immediately gathers the relevant evidence: which service, what changed recently, the related errors and events. It hands that package to the model running on Red Hat OpenShift AI, which returns a plain-language diagnosis, a confidence level, and the <em>blast radius</em>, which is the set of other services this incident will affect if it is left alone.</p>
<p>The recommended action, if it is one you have allowed to run automatically, is then carried out as a native Red Hat Kubernetes operation; the service is scaled to absorb the load, and the platform watches the response times settle back to normal. The entire sequence, from the first alert to the confirmed recovery, is written up as a case: what happened, why, what was done, and the proof it worked. In the morning, the team reviews a finished incident report instead of reconstructing a fire drill.</p>
<p>The engineer's job shifts from <em>finding and fixing</em> to <em>reviewing and improving</em>. That is the real change. The work moves from reactive firefighting to oversight.</p>
<h3 id="whichkubernetesincidentscanberemediatedautomatically">Which Kubernetes incidents can be remediated automatically</h3>
<p>Autonomous response is most valuable on the common, well-understood incidents, the ones that are tedious rather than novel. Each maps to a native Red Hat Kubernetes action the system can take and then verify:</p>
<p>| When this happens | The Red Hat Kubernetes action | And confirms it by |
| ---- | ---- | ---- |
| A service slows down under load | Scales the deployment to add capacity | Watching response times return to normal |
| A service runs out of memory and crashes | Restarts the workload cleanly | Checking it comes back healthy and stays up |
| A recent change breaks something | Rolls back to the last good version | Confirming the service passes its health checks again |</p>
<p>These are the incidents that make up most of the pages a team gets, and they are exactly the ones a closed loop is best suited to take off their plate. Novel or high-stakes incidents still rise to a human, which is by design.</p>
<h2 id="howyoustayincontrolofautomatedremediation">How you stay in control of automated remediation</h2>
<p>Autonomous does not mean unaccountable. The principle is simple: the agent recommends, you decide. The system removes the toil, not the oversight, and a few rules keep humans firmly in charge.  </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc6bce05c0ce3a6e5/6a8e9fd43e7b81f2223189ea/image_3.png" alt="The agent recommends, you decide. A diagnosis is ranked by confidence, then either executed automatically on high confidence or routed to a person for review, and every path ends in verification and a recorded case." /></p>
<ul>
<li><strong>Every action is recorded.</strong> The full sequence, from diagnosis to action to verification, is captured as a reviewable case. Nothing happens off the record.  </li>
<li><strong>Confidence is part of the decision.</strong> Remediation options are ranked by confidence, so high-confidence fixes can run automatically while low-confidence situations are routed to a person instead of acted on.  </li>
<li><strong>Humans set the boundaries.</strong> You decide which actions the system is allowed to take on its own and which require a human to approve. You can keep a person in the loop wherever it matters.  </li>
<li><strong>Your model, in your cluster.</strong> The reasoning runs on Red Hat OpenShift AI inside your own environment, so sensitive telemetry never has to leave your walls. For regulated and sovereign environments, that keeps the whole loop, data and decisions alike, under your control.</li>
</ul>
<p>The goal is a system that earns trust the way a good junior engineer does: it shows its work, it knows when to ask, and it never hides what it did.</p>
<h2 id="howautomatedincidentresponsereducesmttr">How automated incident response reduces MTTR</h2>
<p>The headline outcome is a lower MTTR, because the slow part of an incident, the time before anyone understands it, is largely removed. Teams using Elastic Observability have put real numbers on that shift:</p>
<ul>
<li><a href="https://www.elastic.co/customers/wepay"><strong>WePay</strong></a><strong>, a Chase company, cut the time to find customer impact during incidents by 90%,</strong> improving app performance and releasing product faster.  </li>
<li><a href="https://www.elastic.co/customers/dish-media"><strong>DISH Media</strong></a> <strong>reached 100% visibility, a 10x increase in coverage,</strong> and reduced problem-resolution time for its developers.  </li>
<li><a href="https://www.elastic.co/customers/accolade"><strong>Accolade</strong></a> <strong>monitors about 400 services on Elastic Observability</strong> and doubled developer productivity.</li>
</ul>
<p>Beyond the numbers, the change is structural:</p>
<ul>
<li><strong>Consistency.</strong> The loop responds the same correct way at 3am as it does at 3pm. No fatigue, no improvised fixes.  </li>
<li><strong>Capacity.</strong> Engineers stop spending nights on routine incidents and get that time back for the work only humans can do.  </li>
<li><strong>Resilience.</strong> Faster, more consistent recovery means outages stay small, and small outages are the ones customers never notice.</li>
</ul>
<p>In other words, autonomous SRE does not just make incident response faster. It changes what your best people spend their time on. That direction is also where the market is heading: Elastic was named a Leader in the 2025 Gartner Magic Quadrant for Observability Platforms.</p>
<h2 id="howtogetstartedonyourexistingopenshiftcluster">How to get started on your existing OpenShift cluster</h2>
<p>You do not need to rebuild your stack to begin. Because the pattern ships as a quickstart in the Red Hat catalog, the natural first step is to deploy it on an existing Red Hat OpenShift environment, with Elastic Observability as the unified view across your services, so the context for good decisions already exists. From there you can let the loop run in an advisory mode, where it diagnoses and recommends while a human approves each action, and widen its autonomy as it earns trust on the routine incidents.</p>
<p>Standing it up on Red Hat OpenShift means the observability stack, the reasoning model on Red Hat OpenShift AI, and the remediation agent all come up together inside one cluster, so a team can see the full detect-investigate-resolve-verify loop work end to end before committing to it broadly.</p>
<p>Start by letting the system watch and explain. Let it recommend. Then, incident by incident, let it act. The path to zero-touch operations is incremental, and every step along the way buys back time your team is spending on the 3am page today.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>What is autonomous SRE?</strong> Autonomous SRE is an approach where the observability platform handles the full incident loop on its own: detecting the problem, investigating the likely cause, resolving it with a corrective action, and verifying the service recovered. It automates the routine incident so engineers can focus on the novel and the high-stakes.</p>
<p><strong>Why run autonomous SRE on Red Hat OpenShift?</strong> Running the loop on Red Hat OpenShift keeps the entire stack in-cluster: Elastic Observability (deployed through the ECK operator), the reasoning model on Red Hat OpenShift AI, and the remediation agent all run inside your own environment. No telemetry or decisions leave the cluster, which is what makes the approach a fit for regulated, sovereign, air-gapped, and on-premises deployments. It runs on Red Hat OpenShift Service on AWS, Azure Red Hat OpenShift, or self-managed Red Hat OpenShift.</p>
<p><strong>Does autonomous SRE replace my engineers?</strong> No. It removes the repetitive toil, the routine 3am pages, and shifts engineers from finding-and-fixing to reviewing-and-improving. Humans set which actions are allowed to run automatically, approve anything sensitive, and review every action after the fact.</p>
<p><strong>How does Elastic Observability decide what to do?</strong> It grounds an AI model in real, correlated telemetry, the metrics, logs, and traces from across your environment, plus a live model of how your services depend on each other. The model returns a cause, a confidence level, and the blast radius, and the platform acts only within the boundaries you set.</p>
<p><strong>Is it safe to let a system take action automatically?</strong> Every action is recorded as a reviewable case, remediation options are ranked by confidence, low-confidence situations are routed to a human, and you choose which actions run automatically versus require approval. The remediation steps are ordinary Red Hat OpenShift operations, the same ones your platform team already trusts.</p>
<p><strong>Can this run without sending my data to an outside service?</strong> Yes. With the reasoning model served by Red Hat OpenShift AI inside your cluster, sensitive telemetry never leaves your infrastructure. That makes the approach a fit for regulated, sovereign, and on-premises settings.</p>
<hr />
<p><em>To go deeper on the architecture behind this, see the companion technical walkthrough of the in-cluster autonomous SRE stack on Red Hat OpenShift (coming soon). For the broader picture of how Elastic supports agentic and AI-driven workflows, see <a href="https://www.elastic.co/platform">Elastic's Search AI Platform</a>.</em></p>
<p><em>Gartner, Magic Quadrant for Observability Platforms, 7 July 2025. Gartner does not endorse any vendor, product, or service depicted in its research publications. GARTNER and Magic Quadrant are registered trademarks of Gartner, Inc. and/or its affiliates and are used herein with permission. All rights reserved.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/automated-incident-response-red-hat-openshift</link>
    <guid isPermaLink="false">automated-incident-response-red-hat-openshift</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Matt Isset]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0924018b8ab9b3ab/6a8e9fd773006e0102d8d975/header.png" length="0" type="image/png"/>
    <pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[OpenTelemetry Java extensions: customize traces without forking the agent]]></title>
    <description><![CDATA[One JAR, loaded at startup by the OpenTelemetry Java agent, lets you filter health checks, rename spans, add resource attributes, and control sampling with no application code changes.]]></description>
    <content:encoded><![CDATA[<p>You've just set up auto-instrumentation on a Java application. Without any code changes, traces start flowing to your observability platform.
After a few minutes, you realize health check endpoints are flooding your trace view, and transaction names reflect generic framework patterns rather than your domain operations.</p>
<p>Forking the agent would fix this, but then you own every upstream merge.
You could also use manual instrumentation for complete control, but that requires code changes and ongoing upkeep.
OpenTelemetry Java extensions give you a cleaner path: a separate JAR the agent loads at startup, giving you precise control over what gets captured and exported, without touching agent or application code.</p>
<p>For example, the following challenges are very common:</p>
<ul>
<li>Health check probes are flooding your trace view.</li>
<li>Span names reflect generic framework patterns rather than your domain operations.</li>
<li>Some span names or attributes have high cardinality creating noise in your traces.</li>
<li>Spans are missing attributes relevant to your business logic.</li>
<li>Baggage headers are propagating to downstream services when they shouldn't.</li>
<li>Resource attributes that describe your deployment are not automatically captured because they rely on custom environment variables.</li>
</ul>
<p>Some of those can be solved through configuration, or by using an intermediate OpenTelemetry Collector for processing.
However, this also might add complexity to the telemetry pipeline, and you might prefer to solve this at the source, where the data is captured.</p>
<h2 id="whatareopentelemetryjavaextensions">What are OpenTelemetry Java extensions</h2>
<p>An extension is a JAR file the agent loads at startup. It hooks into the agent's extension points through Java's Service Provider Interface (SPI) mechanism, the same mechanism the agent uses internally.</p>
<p>The extension mechanism works identically with the upstream OpenTelemetry Java agent and with <a href="https://github.com/elastic/elastic-otel-java">Elastic's OpenTelemetry distribution</a>. You write the extension once and it works with either.</p>
<p>For reference, the <a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/">upstream extension documentation</a> provides an exhaustive overview of extension points and a few examples.</p>
<p>This post does not aim to provide a complete reference, but focuses on simple use cases you're likely to reach for in production: renaming spans, filtering noisy traces, or propagating context that the agent doesn't cover in your environment.</p>
<p>Extensions also let you modify and extend the agent instrumentation itself. That goes beyond what this post covers. Here are two starting points:</p>
<ul>
<li><a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/#instrumentercustomizerprovider">Modify instrumentation using instrumenter customizers</a>.</li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/blob/main/examples/extension/src/main/java/com/example/javaagent/instrumentation/DemoServlet3InstrumentationModule.java">Modify or add instrumentation using the instrumentation module</a>.</li>
</ul>
<h2 id="settingupanopentelemetryjavaextensionproject">Setting up an OpenTelemetry Java extension project</h2>
<p>An extension is a standard Java Gradle project with two requirements: the output must be a shadow JAR (a fat JAR with all extension dependencies bundled), and OpenTelemetry dependencies must be declared <code>compileOnly</code> so you don't bundle the SDK itself.</p>
<p>The shadow JAR requirement exists because the agent loads the extension in its own classloader. If you declare a dependency as <code>implementation</code>, it gets bundled and may conflict with the version already in the agent. Using <code>compileOnly</code> keeps those JARs out of the extension JAR entirely.</p>
<p>Here is a minimal <code>build.gradle.kts</code> for a simple extension that does not customize instrumentation and thus relies only on the OpenTelemetry SDK/API.</p>
<pre><code>plugins {
  id("java")
  id("com.gradleup.shadow")
}

repositories {
  mavenCentral()
}

java {
  toolchain {
    languageVersion.set(JavaLanguageVersion.of(8))
  }
}

dependencies {
  // Use BOM to manage OpenTelemetry dependency versions
  compileOnly(platform("io.opentelemetry:opentelemetry-bom:1.64.0"))
  // OpenTelemetry SDK autoconfiguration SPI (provided by agent)
  compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
  // OpenTelemetry SDK
  compileOnly("io.opentelemetry:opentelemetry-sdk")
  // Annotation processor for automatic SPI registration
  compileOnly("com.google.auto.service:auto-service:1.1.1")
  annotationProcessor("com.google.auto.service:auto-service:1.1.1")
}

tasks.assemble {
  dependsOn(tasks.shadowJar)
}
</code></pre>
<p>Check <a href="https://central.sonatype.com/artifact/io.opentelemetry/opentelemetry-bom/">Maven Central</a> for the latest version of the BOM before starting.</p>
<p>Extensions only depend at compile-time on the OpenTelemetry SDK and the autoconfiguration SPI. The agent provides the rest of the SDK and instrumentation implementation at runtime.</p>
<h2 id="loadingopentelemetryjavaextensionsatruntime">Loading OpenTelemetry Java extensions at runtime</h2>
<p>To load an OpenTelemetry Java extension at runtime, you can use the <code>otel.javaagent.extensions</code> system property or <code>OTEL_JAVAAGENT_EXTENSIONS</code> environment variable. The value is a comma-separated list of paths to extension JARs:</p>
<pre><code>java -Dotel.javaagent.extensions=/path/to/my-extension.jar -javaagent:/path/to/opentelemetry-javaagent.jar -jar myapp.jar
</code></pre>
<p>The upstream OpenTelemetry Java agent also lets you <a href="https://opentelemetry.io/docs/zero-code/java/agent/extensions/#embedding-extensions-in-the-agent">embed extensions directly into the agent JAR</a> to simplify deployment.</p>
<h2 id="filteringandrenamingspanswithopentelemetryjavaextensions">Filtering and renaming spans with OpenTelemetry Java extensions</h2>
<p>You can modify spans in two ways:</p>
<ul>
<li>Using a <code>SpanProcessor</code> that is called synchronously when the span starts or ends.</li>
<li>Using a <code>SpanExporter</code> that is called asynchronously when the span is exported.</li>
</ul>
<h3 id="renamespanswithaspanprocessor">Rename spans with a SpanProcessor</h3>
<p><code>SpanProcessor.onStart</code> receives a <code>ReadWriteSpan</code>, which means you can call <code>span.updateName()</code> before the span is exported. This is the right hook for renaming based on attributes that are available at span start.</p>
<pre><code>public class OperationRenamingSpanProcessor implements SpanProcessor {

  @Override
  public void onStart(Context parentContext, ReadWriteSpan span) {
    String operation = span.getAttribute(AttributeKey.stringKey("app.operation"));
    if (operation != null) {
      span.updateName(operation);
    }
  }

  @Override
  public boolean isStartRequired() { return true; }

  @Override
  public void onEnd(ReadableSpan span) {}

  @Override
  public boolean isEndRequired() { return false; }

  @Override
  public CompletableResultCode shutdown() { return CompletableResultCode.ofSuccess(); }

  @Override
  public CompletableResultCode forceFlush() { return CompletableResultCode.ofSuccess(); }
}
</code></pre>
<p>Register the SpanProcessor via <code>AutoConfigurationCustomizerProvider</code>, composing it with whatever processor you have already configured:</p>
<pre><code>@AutoService(AutoConfigurationCustomizerProvider.class)
public class RenamingCustomizerProvider implements AutoConfigurationCustomizerProvider {

  @Override
  public void customize(AutoConfigurationCustomizer customizer) {
    customizer.addTracerProviderCustomizer(this::configureSdkTracerProvider);
  }

  private SdkTracerProviderBuilder configureSdkTracerProvider(
      SdkTracerProviderBuilder tracerProvider, ConfigProperties config) {
    return tracerProvider.addSpanProcessor(new OperationRenamingSpanProcessor());
  }

}
</code></pre>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/extensions/modify-span">modify-span EDOT Java extension example</a> provides a complete implementation.</p>
<h3 id="filterspanswithaspanexporter">Filter spans with a SpanExporter</h3>
<p>A <code>SpanExporter</code> wrapper lets you modify or drop spans before they leave the process. This works well for known noisy endpoints like health checks.</p>
<pre><code>public class FilteringSpanExporter implements SpanExporter {

  private final SpanExporter delegate;

  public FilteringSpanExporter(SpanExporter delegate) {
    this.delegate = delegate;
  }

  @Override
  public CompletableResultCode export(Collection&lt;SpanData&gt; spans) {
    List&lt;SpanData&gt; filtered = new ArrayList&lt;&gt;();
    for (SpanData span : spans) {
      if (!"GET /health".equals(span.getName())) {
        filtered.add(span);
      }
    }
    return delegate.export(filtered);
  }

  @Override
  public CompletableResultCode flush() { return delegate.flush(); }

  @Override
  public CompletableResultCode shutdown() { return delegate.shutdown(); }
}
</code></pre>
<p>Register the FilteringSpanExporter via <code>addSpanExporterCustomizer</code>:</p>
<pre><code>customizer.addSpanExporterCustomizer((existing, config) -&gt; new FilteringSpanExporter(existing));
</code></pre>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/extensions/modify-span">modify-span EDOT Java extension example</a> provides a complete implementation.</p>
<p>The approach has two limitations:</p>
<ul>
<li>This won't discard any child span that may have been created, for example, if the healthcheck calls the database.</li>
<li>Spans filtered at the exporter have already passed through the full processor pipeline and occupied buffer space in the batch processor.</li>
</ul>
<p>If you're dropping a large fraction of your traffic at this stage, a custom <code>Sampler</code> (shown below) is more efficient because it drops spans before any processing happens and also filters out child spans.
Also, when using <a href="https://opentelemetry.io/docs/zero-code/java/agent/declarative-configuration/">declarative configuration</a>, the rule-based sampler lets you implement filtering on rules using only configuration.</p>
<h2 id="addingcustomresourceattributeswitharesourceprovider">Adding custom resource attributes with a ResourceProvider</h2>
<p>Resource attributes describe what's running: the service name, its version, the host. A <code>ResourceProvider</code> lets you attach additional attributes that the agent doesn't know about, such as deployment metadata your platform injects through environment variables.</p>
<p>The example below uses environment variables, but it could also be a configuration file, a cloud metadata service, or any other source available to the agent at startup.</p>
<p>Because the SDK initialization is synchronous, when querying an external service like a metadata endpoint, this can make the agent (and thus the application) startup slower.
If possible, prefer checking environment variables and local config first before calling an external service.</p>
<pre><code>@AutoService(ResourceProvider.class)
public class DeploymentResourceProvider implements ResourceProvider {

  @Override
  public Resource createResource(ConfigProperties config) {
    AttributesBuilder attributes = Attributes.builder();

    String region = System.getenv("DEPLOY_REGION");
    if (region != null) {
      attributes.put(AttributeKey.stringKey("deployment.region"), region);
    }

    String buildVersion = System.getenv("BUILD_VERSION");
    if (buildVersion != null) {
      attributes.put(AttributeKey.stringKey("build.version"), buildVersion);
    }

    return Resource.create(attributes.build());
  }
}
</code></pre>
<p>Attributes from a <code>ResourceProvider</code> merge with the agent's own resource. When two providers supply the same key, the one with the higher <code>order()</code> value wins. The agent's built-in providers use order 0, so overriding <code>order()</code> to return a positive integer gives your provider priority.</p>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/extensions/resource-attribute">resource-attribute EDOT Java extension example</a> provides a complete implementation.</p>
<h2 id="customsamplinginopentelemetryjava">Custom sampling in OpenTelemetry Java</h2>
<p>When filtering at the exporter is too late or too expensive, implement a <code>Sampler</code> directly. The sampler runs before any span processing, so dropped spans never touch the batch buffer.</p>
<p>However, the sampling decision can only rely on attributes that are provided when the span starts. For example, the status code of an HTTP response can't be used as it is only available when the span ends.</p>
<p>The key detail: wrap the existing sampler rather than replacing it. That way, your logic composes with whatever you configured, and parent-based decisions from an upstream service are still respected.</p>
<pre><code>public class HealthCheckSampler implements Sampler {

  private final Sampler delegate;

  public HealthCheckSampler(Sampler delegate) {
    this.delegate = delegate;
  }

  @Override
  public SamplingResult shouldSample(
      Context parentContext,
      String traceId,
      String name,
      SpanKind spanKind,
      Attributes attributes,
      List&lt;LinkData&gt; parentLinks) {
    if (spanKind == SpanKind.SERVER &amp;&amp; name.contains("health")) {
      return SamplingResult.create(SamplingDecision.DROP);
    }
    return delegate.shouldSample(parentContext, traceId, name, spanKind, attributes, parentLinks);
  }

  @Override
  public String getDescription() {
    return "HealthCheckSampler{" + delegate.getDescription() + "}";
  }
}
</code></pre>
<p>Register the HealthCheckSampler via <code>addSamplerCustomizer</code>, which gives you both the existing sampler and the resolved config:</p>
<pre><code>customizer.addSamplerCustomizer((existing, config) -&gt; new HealthCheckSampler(existing));
</code></pre>
<h2 id="communityextensionsinopentelemetryjavacontrib">Community extensions in opentelemetry-java-contrib</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-contrib">opentelemetry-java-contrib</a> repository contains several community-maintained extensions.</p>
<p>Some of them are already included in the OpenTelemetry Java agent (and inherited in the Elastic distribution), but are opt-in:</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/azure-resources">azure-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/aws-resources">aws-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/gcp-resources">gcp-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/cloudfoundry-resources">cloudfoundry-resources</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/baggage-processor">baggage-processor</a></li>
</ul>
<p>Most Elastic distribution <a href="https://www.elastic.co/docs/reference/opentelemetry/edot-sdks/java/features">features</a> exist as extensions in the contrib repository, so you can use them with the upstream agent in a vendor-neutral way.</p>
<ul>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/inferred-spans">inferred-spans</a></li>
<li><a href="https://github.com/open-telemetry/opentelemetry-java-contrib/tree/main/span-stacktrace">span-stacktrace</a></li>
</ul>
<h2 id="furtherreadingandextensionexamples">Further reading and extension examples</h2>
<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/tree/main/examples/extension">upstream extension examples</a> cover additional extension points not shown here, including custom propagators, ID generators, and ignored-type configurers.</p>
<p>The <a href="https://github.com/elastic/elastic-otel-java/tree/main/examples/baggage">Elastic baggage example</a> shows the filtering propagator for baggage running end-to-end with a two-service application, it also demonstrates custom instrumentation to add baggage without modifying the application code.</p>
<p>This post covered the project setup and the patterns most likely to come up in production. Both links above go deeper: the upstream examples add extension points not covered here, and the baggage example shows a complete two-service implementation you can run locally.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-java-extensions</link>
    <guid isPermaLink="false">opentelemetry-java-extensions</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Sylvain Juge]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b379be7ce7ba2c1/6a8ea21cbf814594cbd284ec/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 20 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[vLLM Prometheus metrics for self-hosted LLM tuning: TTFT, KV Cache, and GPU Utilization]]></title>
    <description><![CDATA[Tuning a self-hosted vLLM inference using its Prometheus metrics in Elastic Observability — TTFT, KV cache, prefix caching and DCGM GPU counters]]></description>
    <content:encoded><![CDATA[<p>Somewhere in your company there is a team that cannot use Claude, GPT, or Gemini — not because they don't want to, but because their data isn't allowed to leave a jurisdiction, a network boundary, or due to a contract. Claims files. Patient notes, source code under an export-control regime, etc.</p>
<p>That team still wants a model. So the request lands on an SRE's desk, and it sounds deceptively small: <em>"Can you stand up an open-weight model for the claims team? Sixty people. It has to run on our hardware."</em> They aren't even allowed to use a neocloud. There is a cost associated with this, but we won't explore that part. Just the part that covers running the model and observing the configuration.</p>
<p>Standing it up is the easy half. Four manifests and an afternoon, and you have a model answering questions. The hard part arrives a week later, when someone says <em>"it feels slow"</em> and you realize you have no idea whether the deployment is configured well, badly, or catastrophically — and no obvious way to find out.</p>
<p>This guide shows you how Elastic Observability can help you analyze the metrics from the configuration. It walks through tuning a real vLLM deployment using the metrics vLLM already emits. vLLM exposes these on a <code>/metrics</code> endpoint in <strong>Prometheus exposition format</strong> — no instrumentation, no sidecar, no code change — which is why every query in this guide starts from a Prometheus scrape. The goal: turn "it feels slow" into a specific, defensible decision.</p>
<h3 id="testenvironmentvllmonakubernetesclusterusingnvidiaa10gwithdcgmexporterandprometheusmetrics">Test environment: vLLM on a Kubernetes cluster using NVIDIA A10G with dcgm-exporter and Prometheus metrics</h3>
<p>Every figure in this guide was measured on the following stack — one replica, one GPU, no autoscaling.</p>
<ul>
<li><strong>Workload</strong> — Kubernetes-native load generator, scaled from 8 to 32 concurrent requests.</li>
<li><strong>Model</strong> — <code>Qwen/Qwen2.5-3B-Instruct</code>, bf16, <code>--max-model-len 4096</code></li>
<li><strong>Engine</strong> — vLLM <code>v0.23.0</code>, OpenAI-compatible server, Prometheus <code>/metrics</code> on <code>:8000</code></li>
<li><strong>GPU</strong> — NVIDIA A10G, 24 GB — an AWS <code>g5.xlarge</code></li>
<li><strong>Cluster</strong> — Amazon EKS 1.30, tainted GPU node pool with <code>minSize: 0</code></li>
<li><strong>Telemetry</strong> — Prometheus scraping every 15s, plus <code>dcgm-exporter</code> on <code>:9400</code>, shipped via <code>remote_write</code></li>
<li><strong>Analysis</strong> — Elastic Observability, queried with ES|QL and PromQL</li>
<li><strong>Measured</strong> — 2026-07-27</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd14cf1e2fc1644c5/6a859ab0d6cf297af1bafe8a/arch-measurement-stack.png" alt="Architecture of the measurement stack: a load generator driving a vLLM pod running Qwen on a tainted NVIDIA A10G node in Amazon EKS, with dcgm-exporter as a DaemonSet on the same node and a Prometheus pod scraping both and remote-writing to Elastic Observability" /></p>
<h2 id="whyisithardforansretoselfhostandtuneanopenweightllm">Why is it hard for an SRE to self-host and tune an open-weight LLM?</h2>
<p><strong>The difficulty is not the deployment, it's the tuning which has no feedback loop.</strong> vLLM starts, serves, and reports success whether it's configured brilliantly or wastefully. Nothing tells you which.</p>
<p>When loading up the model, your manifest would have this configuration:</p>
<pre><code>      containers:
        - name: vllm
          image: vllm/vllm-openai:v0.23.0  # pin an exact release — metric names shift between versions
          args:
            - "--model=Qwen/Qwen2.5-3B-Instruct"
            - "--max-model-len=4096"         # cap context → predictable KV-cache size
            # A10G has native bf16 — do NOT add --dtype=half (T4-only).
          ports:
            - name: http
              containerPort: 8000            # OpenAI API + /metrics
</code></pre>
<p>But you can run into specific issues, such as:
Hugging Face downloads take minutes. If your cluster expects a server to start in 30 seconds, it will assume the app is dead and kill it mid-download, putting you in an infinite crash loop.</p>
<p>Or you could have a hardware mismatch, and potentially degrade your model’s speed or precision because hardware architectures vary</p>
<p>or a bevy of other issues.</p>
<p>Once the model is finally running, optimizing performance is complete guesswork because default metrics don't tell you if you're being efficient.</p>
<p>You could use <code>nvidia-smi</code>, but this only understands raw hardware state, not application software logic.</p>
<p>Now that you have it running, a few hours to maybe even a day in, the team says "it feels slow." You are, functionally, tuning blind.</p>
<p><strong>How do you tune a self-hosted vLLM deployment?</strong></p>
<p>You're not an inference engineer, you own forty other services besides this one, and you don't have a forward-deployed engineer from a model vendor on call. But tuning an LLM server turns out to need exactly one skill you already have: <strong>reading telemetry and reasoning about saturation.</strong> The only missing piece is telemetry that exists and means something.</p>
<p>It does. vLLM emits a rich Prometheus endpoint out of the box — latency decomposed by inference phase, cache hit rates, batch occupancy, token accounting, completion outcomes. Almost nobody looks at it. The rest of this guide is how to read it.</p>
<hr />
<h2 id="definingtheworkloadsixtyusersshortpromptsstreamingresponses">Defining the workload: sixty users, short prompts, streaming responses</h2>
<p>With the slowness detected and reported, you gather the usage profile of the users. Their usage pattern is as follows:</p>
<ul>
<li><strong>~60 users, but not concurrent.</strong> Realistic peak is <strong>8–12 simultaneous in-flight requests</strong>; sustained is lower.</li>
<li><strong>Short prompts, long answers.</strong> The user pastes a paragraph and asks for a structured summary. Prompts run ~50 tokens; useful answers run 500–1,000.</li>
<li><strong>Interactive, streaming UI.</strong> Perceived speed is dominated by <strong>time to first token (TTFT)</strong>, not total time — the same psychology as a chat interface.</li>
<li><strong>Heavy prompt reuse.</strong> Every request carries the same system prompt and the same policy-language boilerplate.</li>
</ul>
<p>From that, you write down actual service objectives — the step most self-hosted LLM projects skip:</p>
<ul>
<li><strong>TTFT p95 &lt; 300 ms</strong> </li>
<li><strong>inter-token latency &lt; 50 ms</strong> (≥ 20 tokens/sec, faster than reading speed) </li>
<li><strong>zero queueing at 12 concurrent</strong> </li>
<li><strong>error + abort rate &lt; 0.5%</strong></li>
</ul>
<p>Those four numbers are the point of everything that follows. Without them, "it feels slow" has no answer. With them, every metric below passes or fails a stated bar.</p>
<hr />
<h2 id="howdoyougetprometheusmetricsoutofvllmandthegpu">How do you get Prometheus metrics out of vLLM and the GPU?</h2>
<p><strong>There are two sources, and you need both.</strong> </p>
<ul>
<li>vLLM reports on itself — latency by phase, cache hit rates, batch occupancy, token counts — on <code>/metrics</code> at its serving port, with no adapter and no instrumentation work. </li>
<li>The GPU reports separately, through NVIDIA's <code>dcgm-exporter</code> on <code>:9400</code> (NVIDIA Data Center GPU Manager (DCGM) is a suite of tools and libraries designed to comprehensively manage, monitor, and diagnose enterprise-grade NVIDIA GPUs in clusters and data centers). vLLM tells you what the <em>engine</em> thinks is happening; DCGM tells you what the <em>card</em> is actually doing. Step 6 is built entirely on the gap between those two answers.</li>
</ul>
<p>On this EKS cluster that means three things running side by side:</p>
<ul>
<li><strong>vLLM</strong> as a plain Deployment on the tainted <code>g5.xlarge</code> GPU node pool. For one model on one card, a Deployment and a Service is the whole architecture.</li>
<li><strong><code>dcgm-exporter</code></strong> as a DaemonSet, pinned to the same GPU nodes.</li>
<li><strong>A Prometheus server</strong> on a CPU node, scraping both endpoints every 15 seconds.</li>
</ul>
<p>Nothing here is AWS-specific. The production version is the same manifests on an on-prem cluster with L40S or H100 nodes — which is the point of doing this on Kubernetes rather than on a vendor's platform.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd5929cba73afdd08/6a859ab3078290bd30320cc1/arch-vllm-kubernetes.png" alt="vLLM and DCGM exporter running on a tainted GPU node pool in Kubernetes, scraped by Prometheus" /></p>
<h3 id="whataboutkserveandllmd">What about KServe and llm-d?</h3>
<p><strong>Neither was run for this guide, and neither changes where the metrics come from.</strong> KServe and llm-d sit <em>on top of</em> vLLM rather than replacing it — vLLM is still the engine, so <code>/metrics</code> is still the source of every number here. Each adds its own layer on top (KServe: autoscaler and revision metrics; llm-d: router and cache-routing metrics), but the inference telemetry underneath is identical.</p>
<p>What they change is <em>when</em> you need them — and each promotion is triggered by a metric you're already collecting:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte3690f26168dfe1f/6a859ab5982926339a582e1a/scaling-ladder-kserve-llmd.png" alt="The scaling ladder from a plain vLLM Deployment to KServe to llm-d, with the metric that triggers each promotion" /></p>
<hr />
<h2 id="howdoyoushipthevllmmetricsanddcgmmetricstoobservability">How do you ship the vLLM metrics and DCGM metrics to Observability</h2>
<p><strong>A Prometheus scraping inside the cluster only holds hours of data — the metrics have to reach a store you can still query next week.</strong> There are two paths for that, and they are not equivalent.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt757fb6a3447dfa4d/6a859ab8f61d6e3bac9c209b/pipeline-metrics-to-backend.png" alt="Two paths for shipping vLLM and DCGM metrics off the cluster: an OpenTelemetry Collector over OTLP, or a Prometheus server using native remote_write" /></p>
<p><strong>Path A — OpenTelemetry Collector.</strong> Puts inference metrics into the same pipeline as your traces and logs. One collector, one auth path, one mental model. The cost is that Prometheus metrics sent through OTLP get normalized: the stored schema ends up neither purely Prometheus nor purely OTel, and metric names shift.</p>
<p><strong>Path B — native Prometheus <code>remote_write</code>.</strong> Stands up a small Prometheus that scrapes both endpoints and pushes to a backend speaking the remote-write protocol. Names and labels land untouched, <code>_sum</code> / <code>_count</code> / <code>_bucket</code> histogram parts stay intact, and existing queries keep working.</p>
<p><strong>For a tuning exercise, choose Path B.</strong> That's what produced every number in this guide. The reason is narrow but decisive: tuning means comparing against the vLLM documentation and the vLLM community, and both speak in exact metric names. When your chart says <code>vllm:kv_cache_usage_perc</code>, you can search for it.</p>
<p>The deployment is two YAML files — a Prometheus Deployment with two scrape jobs and a <code>remote_write</code> block, plus a Secret holding the backend credential. In this build the destination was an Elastic Serverless project, which exposes a Prometheus remote-write endpoint and lands data in a time-series data stream, <code>metrics-vllm.prometheus-inference</code>.</p>
<p>Two things cost me real time. If your backend has a separate ingest host for OTLP versus its main API, remote-write usually lives on the <strong>main API host</strong>, not the ingest one — pointing at the wrong one returns a 404 that looks like a path error. And the credential needs <strong>index-write privileges</strong>, not just ingest authentication; a key that works fine for OTLP can authenticate successfully and then 403 on every sample. Check <code>prometheus_remote_storage_samples_failed_total</code> on the Prometheus itself before looking anywhere else.</p>
<h3 id="howdoyouconfirmvllmmetricslandedinelastic">How do you confirm vLLM metrics landed in Elastic?</h3>
<p>Once the pipeline is up, look at the field list. Roughly <strong>127 metric series</strong> arrive from a single vLLM pod plus DCGM:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc8fad7c5f6812920/6a859abb682666f68a1ea1af/metrics-landed-discover.png" alt="vLLM and DCGM metric fields arriving in the observability backend, with per-metric sparklines" /></p>
<p>This screen is more useful than it looks. Scanning the field list is how you confirm your vLLM version's exact metric names — <strong>they do shift between major vLLM releases</strong>, and a dashboard built against the wrong names fails silently by returning nothing rather than erroring.</p>
<h3 id="twogotchaswhenqueryingvllmmetrics">Two gotchas when querying vLLM metrics</h3>
<p>Both produce results that look like "the metrics aren't working" when the pipeline is perfectly healthy.</p>
<p><strong>vLLM metric names contain a colon</strong> (<code>vllm:num_requests_running</code>), so they need escaping in most query languages. More insidiously, if you filter by metric <em>name</em> across several metrics and then aggregate only one of them, you get rows back — full of nulls, with no error. Each Prometheus metric lands in its own field, so <strong>naming the field is the filter</strong>; you don't need the name predicate at all.</p>
<p><strong>Counters need rate functions, gauges don't.</strong> <code>vllm:generation_tokens_total</code> is cumulative and monotonic — taking a max of it gives the pod's lifetime total, not its throughput. Gauges like <code>vllm:num_requests_running</code>, <code>vllm:num_requests_waiting</code> and <code>vllm:kv_cache_usage_perc</code> are instantaneous and want max or average. Mixing these up produces charts that are wrong but plausible, which is considerably worse than charts that are empty.</p>
<hr />
<h2 id="whichvllmprometheusmetricsactuallymatter">Which vLLM Prometheus metrics actually matter?</h2>
<p>A reference for the metrics used in this guide, what each tells you, and the condition worth watching. Names are as vLLM emits them; the <code>DCGM_FI_*</code> series come from <code>dcgm-exporter</code>.</p>
<p>| Metric | Type | What it tells you | Watch for |
|---|---|---|---|
| <code>vllm:time_to_first_token_seconds</code> | Histogram | TTFT — how long before the first token streams | p95 above your interactive bar (300 ms here) |
| <code>vllm:inter_token_latency_seconds</code> | Histogram | Streaming speed after the first token | Above ~50 ms is slower than reading speed |
| <code>vllm:e2e_request_latency_seconds</code> | Histogram | Total request time | Rising while TTFT is flat = decode or workload change |
| <code>vllm:request_queue_time_seconds</code> | Histogram | Time waiting for admission | <strong>Earliest saturation signal</strong> — any sustained rise |
| <code>vllm:request_prefill_time_seconds</code> | Histogram | Time processing the prompt | Dominant share = prefill-bound workload |
| <code>vllm:request_decode_time_seconds</code> | Histogram | Time generating tokens | Dominant share = memory-bandwidth-bound |
| <code>vllm:num_requests_running</code> | Gauge | Requests currently being decoded | Batch occupancy |
| <code>vllm:num_requests_waiting</code> | Gauge | Requests queued for admission | Sustained non-zero = add a replica |
| <code>vllm:kv_cache_usage_perc</code> | Gauge | Occupancy of the KV block pool — <strong>not VRAM</strong> | Autoscaling trigger (~60%) |
| <code>vllm:prompt_tokens_total</code> + <code>vllm:prompt_tokens_cached_total</code> | Counters | Prefix-cache hit rate | A drop means routing scattered your prefixes |
| <code>vllm:generation_tokens_total</code> | Counter | Output throughput in tokens/sec | Headline throughput number |
| <code>vllm:request_prompt_tokens</code> + <code>vllm:request_generation_tokens</code> | Histograms | Per-request token sizes; their ratio is the workload's shape | A moving ratio means the workload changed character |
| <code>vllm:iteration_tokens_total</code> | Histogram | Tokens advanced per forward pass | Near 1.0 with concurrency = batching broken |
| <code>vllm:request_success_total{finished_reason}</code> | Counter | Completion outcomes | <code>error</code>/<code>abort</code> = SLO; <code>length</code> share = truncation |
| <code>http_requests_total{status}</code> | Counter | Server-level requests | Catches 4xx and malformed requests <code>vllm:*</code> never sees |
| <code>DCGM_FI_DEV_FB_USED</code> / <code>_FB_FREE</code> | Gauge | Physical VRAM | Capacity planning only — never alert on it |
| <code>DCGM_FI_DEV_GPU_UTIL</code> | Gauge | "A kernel is resident" | <strong>Not</strong> a measure of useful work |
| <code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE</code> | Gauge | Tensor-core activity | Low here + high DRAM = memory-bound |
| <code>DCGM_FI_PROF_DRAM_ACTIVE</code> | Gauge | Memory-bandwidth activity | High = the bottleneck is bandwidth |
| <code>DCGM_FI_DEV_POWER_USAGE</code> | Gauge | Watts drawn | Pairs with throughput for tokens-per-watt |</p>
<hr />
<h2 id="step1wheredoesvllmlatencygottftprefillanddecodedecomposed">Step 1: Where does vLLM latency go? TTFT, prefill, and decode decomposed</h2>
<p><strong>Decompose total latency into queue, prefill, and decode before optimizing anything.</strong> vLLM reports all three separately, and they have completely different fixes. This is the single most valuable chart in the setup.</p>
<p>The query averages each phase's cumulative time by request count in the same window — in Prometheus terms, <code>rate(vllm:request_prefill_time_seconds_sum) / rate(vllm:e2e_request_latency_seconds_count)</code>, and the same for queue and decode:</p>
<pre><code>TS metrics-vllm.prometheus-inference
| WHERE @timestamp &gt; NOW() - 30 minutes
| STATS reqs = SUM(RATE(`metrics.vllm:e2e_request_latency_seconds_count`)),
        q_s  = SUM(RATE(`metrics.vllm:request_queue_time_seconds_sum`)),
        pf_s = SUM(RATE(`metrics.vllm:request_prefill_time_seconds_sum`)),
        dc_s = SUM(RATE(`metrics.vllm:request_decode_time_seconds_sum`))
    BY minute = BUCKET(@timestamp, 1 minute)
| EVAL queue_ms   = ROUND(q_s  / reqs * 1000, 2),
       prefill_ms = ROUND(pf_s / reqs * 1000, 1),
       decode_ms  = ROUND(dc_s / reqs * 1000, 1)
| KEEP minute, queue_ms, prefill_ms, decode_ms
| SORT minute ASC
</code></pre>
<p>At 8 concurrent requests on the A10G:</p>
<pre><code>minute    | queue_ms | prefill_ms | decode_ms | e2e_ms  | ttft_ms | inter_token_ms
22:55:00  | 0.01     | 37.98      | 1664.69   | 1715.18 | 50.81   | 16.23
22:56:00  | 0.01     | 40.99      | 1670.44   | 1723.78 | 53.50   | 16.20
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9369d6f7665848b2/6a859abe9bf994282509fb28/latency-decomposition.png" alt="Latency decomposition over time — queue, prefill, decode, e2e, TTFT and inter-token latency" /></p>
<p><strong>Read it against the objectives:</strong></p>
<ul>
<li><strong>TTFT is 51 ms against a 300 ms target.</strong> Passing, with almost 6× headroom. Perceived responsiveness is not the problem, whatever was said in the meeting.</li>
<li><strong>Inter-token latency is 16 ms — about 62 tokens/sec</strong> against a 50 ms / 20 tok-s bar. Text arrives roughly three times faster than a person reads it.</li>
<li><strong>Queue time is 0.01 ms.</strong> Nothing is waiting for admission; the engine has capacity to spare at this concurrency.</li>
<li><strong>Decode is 1,665 ms against 38 ms of prefill — 97% of the time is decode.</strong></li>
</ul>
<p>That last line is the finding. <strong>Every optimization aimed at prefill is worthless for this workload.</strong> Chunked prefill, prompt compression, a faster attention kernel for long contexts — all real techniques, all irrelevant when prefill is 2% of the time. Decode is memory-bandwidth-bound, so the levers that would actually move it are <strong>quantization, tensor parallelism across two cards, or a smaller model</strong>. A single chart eliminated the wrong shopping list.</p>
<p><strong>Watch <code>vllm:request_queue_time_seconds</code> specifically.</strong> It is the earliest saturation signal in the entire vLLM metric set — queue time climbs <em>before</em> <code>vllm:num_requests_waiting</code> becomes visibly non-zero, because a request can wait milliseconds for admission without ever registering as queued at scrape time. If you alert on one thing from this section, alert on queue time crossing a small absolute threshold.</p>
<hr />
<h2 id="step2isvllmusingthegpuefficientlykvcacheprefixcachingandbatchoccupancy">Step 2: Is vLLM using the GPU efficiently? KV cache, prefix caching, and batch occupancy</h2>
<p><strong>Four metrics answer this: prefix-cache hit rate, tokens per iteration, KV-cache occupancy, and running-vs-waiting requests.</strong> Latency tells you the experience is good; these tell you whether you're overpaying for it.</p>
<pre><code>TS metrics-vllm.prometheus-inference
| WHERE @timestamp &gt; NOW() - 30 minutes
| STATS ptok    = SUM(RATE(`metrics.vllm:prompt_tokens_total`)),
        cached  = SUM(RATE(`metrics.vllm:prompt_tokens_cached_total`)),
        gen     = SUM(RATE(`metrics.vllm:generation_tokens_total`)),
        it_s    = SUM(RATE(`metrics.vllm:iteration_tokens_total_sum`)),
        it_c    = SUM(RATE(`metrics.vllm:iteration_tokens_total_count`)),
        running = MAX(`metrics.vllm:num_requests_running`),
        waiting = MAX(`metrics.vllm:num_requests_waiting`),
        kv      = MAX(`metrics.vllm:kv_cache_usage_perc`)
    BY minute = BUCKET(@timestamp, 1 minute)
| EVAL prefix_cache_hit_pct = ROUND(cached / ptok * 100, 1),
       tokens_per_iteration = ROUND(it_s / it_c, 2),
       gen_tokens_per_sec   = ROUND(gen, 1),
       kv_cache_pct         = ROUND(kv * 100, 3)
| KEEP minute, prefix_cache_hit_pct, tokens_per_iteration,
       gen_tokens_per_sec, running, waiting, kv_cache_pct
| SORT minute ASC
</code></pre>
<pre><code>minute   | prefix_cache_hit_pct | tokens_per_iteration | gen_tok/s | running | waiting | kv_cache_pct
22:55:00 | 32.5                 | 10.36                | 479.1     | 8.0     | 0.0     | 0.255
22:59:00 | 32.3                 | 10.34                | 480.0     | 8.0     | 0.0     | 0.121
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9c70c464d265fbcd/6a859ac127c5cd723f5f68b4/efficiency-kv-cache.png" alt="Efficiency panel — prefix cache hit rate, tokens per iteration, throughput, running/waiting, KV cache occupancy" /></p>
<p><strong>A 32% prefix-cache hit rate is a third of all prefill work simply not done.</strong> The claims team's requests share a system prompt and policy boilerplate, and vLLM's automatic prefix caching recognizes that. This is a direct argument for <em>raising</em> prompt standardization: the more the application puts shared context in a consistent leading position, the higher this climbs and the cheaper every request gets. It is also the number that will crater the day a naive round-robin load balancer sits in front of two replicas — precisely the condition that justifies llm-d's cache-aware routing.</p>
<p><strong><code>tokens_per_iteration ≈ 10.3</code> with 8 concurrent requests is continuous batching working correctly.</strong> Each forward pass through the model advances about ten sequences at once. If this sat near 1.0 with multiple requests in flight, batching would be broken and you'd be paying full model-forward cost per token per user. This metric proves you're getting vLLM's core value.</p>
<h3 id="whatdoesvllmkv_cache_usage_percactuallymeasure">What does <code>vllm:kv_cache_usage_perc</code> actually measure?</h3>
<p><strong><code>vllm:kv_cache_usage_perc</code> reports occupancy of vLLM's pre-allocated KV block pool — not physical GPU memory.</strong> At startup, vLLM reserves a fraction of VRAM (governed by <code>--gpu-memory-utilization</code>, default 0.9) and carves a KV block pool out of that reservation. This gauge reports how full <em>that pool</em> is.</p>
<p>That's why it read <strong>0.25%</strong> here. Eight concurrent requests holding ~150 tokens each barely touch an A10G's block budget. The pool is large, and correctly so. Push the same server to 32 concurrent requests with 512–1,024 token generations and it moves — to about <strong>2.8%</strong>. Still small.</p>
<p>The instinct is to read a number that low as "the cache is broken" or "I've massively over-provisioned." Both are wrong. <strong>Treating this gauge as a VRAM proxy is the most common self-hosted vLLM configuration error I see.</strong> Step 6 shows exactly how far apart the two are.</p>
<hr />
<h2 id="step3whatshapeisyourvllminferenceworkload">Step 3: What shape is your vLLM inference workload?</h2>
<p><strong>Confirm the workload is what you think it is before tuning anything.</strong> This is the panel that explains a latency "regression" that isn't your fault.</p>
<pre><code>minute   | requests_per_min | avg_prompt_tokens | avg_generated_tokens | avg_max_tokens | gen_to_prompt_ratio
22:55:00 | 282.2            | 49.6              | 103.6                | 103.6          | 2.09
23:01:00 | 276.0            | 50.6              | 103.0                | 103.0          | 2.04
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23956fea686f8579/6a859ac59829261f8f582e20/workload-shape.png" alt="Workload shape — request rate, prompt and generation token averages, generation-to-prompt ratio" /></p>
<p><strong>The generation-to-prompt ratio is 2.04 — this workload writes twice as much as it reads.</strong> That single ratio <em>is</em> the explanation for Step 1's 97%-decode finding, and it holds for any summarize-and-draft use case. If the team later adds a long-document RAG feature, prompts jump to thousands of tokens, the ratio inverts, the workload becomes prefill-bound, and the correct tuning changes completely. <strong>Watching this ratio is how you learn your workload changed character before someone files a ticket.</strong></p>
<p>Now the column that should bother you: <strong><code>avg_generated_tokens</code> equals <code>avg_max_tokens</code> exactly.</strong> Every request is stopping because it hit its token ceiling, not because the model finished its thought. The screenshot shows the same pattern holding as generation lengths scale to ~685 tokens against a ~777 ceiling.</p>
<p>In a load test that's an artifact of the generator. <strong>In production, that number is users getting cut off mid-sentence</strong> — and it is invisible in every latency metric you have. Which brings us to the metric that catches it.</p>
<hr />
<h2 id="step4arevllmrequestsactuallysucceedingcheckingfinished_reason">Step 4: Are vLLM requests actually succeeding? Checking finished_reason</h2>
<p><strong>Break <code>vllm:request_success_total</code> down by its <code>finished_reason</code> label.</strong> This is the closest thing self-hosted inference has to an application-level SLI, and it catches a failure mode no latency chart can.</p>
<pre><code>TS metrics-vllm.prometheus-inference
| WHERE @timestamp &gt; NOW() - 30 minutes
| STATS completions_per_min = ROUND(SUM(RATE(`metrics.vllm:request_success_total`)) * 60, 2)
    BY minute = BUCKET(@timestamp, 1 minute), finish_reason = labels.finished_reason
| SORT minute ASC, finish_reason
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd26d2ce94404b523/6a859ac818249ce7a418ecaa/health-finish-reason.png" alt="Completion outcomes broken down by finish reason — stop, length, abort, error, repetition" /></p>
<p>Five outcomes, each meaning something different operationally:</p>
<p>| <code>finished_reason</code> | What it means | What to do about it |
|---|---|---|
| <code>stop</code> | The model finished naturally | This is the number you want large |
| <code>length</code> | Truncated at <code>max_tokens</code> | High share means users are cut off — raise the ceiling, or shorten the ask |
| <code>abort</code> | The client disconnected first | Users giving up, or a proxy timeout shorter than your generations |
| <code>error</code> | The engine failed | Your hard SLO signal. Should be flat zero |
| <code>repetition</code> | Degenerate looping output | A sampling-parameter problem, not an infrastructure one |</p>
<p>Under the small load generator the split was <strong>100% <code>length</code></strong> — expected, since it requested a fixed ceiling. In the screenshot, at a mixed load, <code>stop</code> and <code>length</code> run side by side at roughly 51 and 32 completions/min. That mix is the healthy shape: most requests finishing on their own, a minority hitting the ceiling.</p>
<p><strong>The lesson generalizes.</strong> <code>error</code> and <code>abort</code> are what you page on. But the <strong><code>stop</code>-to-<code>length</code> ratio is what you review weekly</strong>, because drift toward <code>length</code> means answers are being truncated and no latency dashboard on earth will tell you.</p>
<p>One blind spot to close: <strong><code>vllm:*</code> metrics only count requests the engine accepted.</strong> Malformed JSON, 4xx, auth failures and dropped connections never reach it. Those live in <code>http_requests_total</code> with <code>status</code> and <code>handler</code> labels — worth a panel beside this one, because "the model is broken" reports frequently turn out to be the gateway in front of it.</p>
<hr />
<h2 id="step5whatnvidiadcgmmetricssaythegpuisactuallydoing">Step 5: What NVIDIA DCGM metrics say the GPU is actually doing</h2>
<p><strong>Use NVIDIA DCGM as an independent witness to vLLM's own account.</strong> Everything so far is the engine describing itself; DCGM describes the silicon.</p>
<pre><code>FROM metrics-vllm.prometheus-inference
| WHERE @timestamp &gt; NOW() - 30 minutes
| STATS gpu_util_pct    = MAX(`metrics.DCGM_FI_DEV_GPU_UTIL`),
        mem_bw_util_pct = MAX(`metrics.DCGM_FI_DEV_MEM_COPY_UTIL`),
        vram_used_mib   = MAX(`metrics.DCGM_FI_DEV_FB_USED`),
        vram_free_mib   = MIN(`metrics.DCGM_FI_DEV_FB_FREE`),
        power_w         = ROUND(MAX(`metrics.DCGM_FI_DEV_POWER_USAGE`), 1),
        temp_c          = MAX(`metrics.DCGM_FI_DEV_GPU_TEMP`)
    BY minute = BUCKET(@timestamp, 1 minute)
| SORT minute ASC
</code></pre>
<p>→ <strong>100% GPU util · 21,483 MiB used / 1,352 MiB free · 240 W · 73 °C</strong></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6aa395617cbe369d/6a859acb43c0b731cd2efb39/dcgm-hardware.png" alt="DCGM GPU hardware panel — utilization, VRAM used and free, power draw, temperature, SM clock" /></p>
<p>100% utilization and 94% VRAM. Under the conventional reading, this card is maxed out and it's time to ask for more hardware. That reading is wrong.</p>
<h3 id="whyisgpuutilizationamisleadingmetricforllminference">Why is GPU utilization a misleading metric for LLM inference?</h3>
<p><strong><code>DCGM_FI_DEV_GPU_UTIL</code> means "a kernel is resident on the device," not "the device is doing useful work."</strong> It reads 100% for a perfectly-tuned server and 100% for a badly-tuned one, so it cannot distinguish them. The DCGM profiling counters can:</p>
<pre><code>gr_engine_active 99.8%  ·  tensor_active 16.6%  ·  dram_active 80.4%
</code></pre>
<p><strong>Read those three together.</strong> The GPU's compute engine is busy essentially all the time — but its <strong>tensor cores, the units that do the actual matrix math, are active only 16.6%</strong>, while <strong>DRAM is active 80.4%</strong>. The card is not computing. It is <strong>waiting on memory.</strong></p>
<p>This is independent, hardware-level confirmation of what Step 1 inferred purely from timings: decode is memory-bandwidth-bound. Two entirely different instruments, two different layers of the stack, one conclusion — the difference between a hypothesis and a finding.</p>
<p>It also permanently retires GPU utilization as a capacity metric for LLM inference. <strong>If your GPU capacity planning rests on <code>DCGM_FI_DEV_GPU_UTIL</code> — and most does — it rests on nothing.</strong></p>
<hr />
<h2 id="step6vllmkvcachevsgpuvramandwhytheydisagree">Step 6: vLLM KV cache vs GPU VRAM, and why they disagree</h2>
<p><strong>Put <code>vllm:kv_cache_usage_perc</code> and physical VRAM usage on one 0–100% axis.</strong> They describe the same GPU memory, they sit at opposite ends of the chart, and both are correct.</p>
<pre><code>minute   | running | gen_tok_s | kv_cache_pct | vram_used_pct | gpu_util | dram_active_pct | tensor_active_pct | tokens_per_watt
00:05:00 | 31      | 1627.5    | 2.82         | 94.1          | 100.0    | 80.4            | 16.6              | 6.80
</code></pre>
<p><strong>KV cache at 2.8%. VRAM at 94.1%.</strong></p>
<p>vLLM pre-allocates a large fraction of VRAM at startup — governed by <code>--gpu-memory-utilization</code>, default 0.9 — and carves its KV block pool out of that reservation. <code>vllm:kv_cache_usage_perc</code> reports occupancy <em>of the pool</em>. DCGM reports what the <strong>driver</strong> sees, which is the whole reservation, whether or not it's holding anything.</p>
<p>The operational consequences are precise, and they're the practical payoff of the entire exercise:</p>
<ul>
<li><strong>Autoscale on <code>vllm:kv_cache_usage_perc</code> and <code>vllm:num_requests_waiting</code>.</strong> These describe admission capacity — whether the engine can take another request right now.</li>
<li><strong>Capacity-plan on VRAM.</strong> This describes physical space — whether a second model could ever fit on this card. (It can't. 1.3 GB free.)</li>
<li><strong>Never alert on VRAM.</strong> It will page you at 3 a.m. for a healthy, mostly-idle server, every single night, forever.</li>
</ul>
<p>And <strong><code>tokens_per_watt</code> — generated tokens divided by power draw, 6.8 here — is a genuine cost-efficiency metric.</strong> It's comparable across GPU models, batch settings and quantization levels in a way that neither latency nor utilization is. When you go back to Finance for card number two, this is the number that makes the argument: <em>at 32 concurrent we sustain 1,627 tokens/sec at 240 watts, and here's what that becomes on an L40S.</em></p>
<hr />
<h2 id="vllmtuningdecisionswhatthesredoeswiththeseprometheusmetrics">vLLM tuning decisions: what the SRE does with these Prometheus metrics</h2>
<p>Six steps, thirty minutes, one server. The verdict against the stated objectives:</p>
<p>| Objective | Measured | Verdict |
|---|---|---|
| TTFT p95 &lt; 300 ms | <strong>51 ms</strong> | Pass, 6× headroom |
| Inter-token &lt; 50 ms | <strong>16 ms</strong> (≈62 tok/s) | Pass |
| Zero queueing at 12 concurrent | <strong><code>queue_ms</code> 0.01, <code>waiting</code> 0</strong> at 8; still 0 at 32 | Pass, large margin |
| Error + abort &lt; 0.5% | <strong>0%</strong> | Pass |</p>
<p><strong>The configuration is correct for this department, and the department is over-provisioned rather than under-provisioned.</strong> That's a defensible, evidence-backed answer to "it feels slow" — and it redirects the investigation to the app, the gateway, or the prompt, which is where the problem actually is.</p>
<p>The concrete follow-ups, each tied to a metric rather than a hunch:</p>
<ol>
<li><strong>Stop optimizing prefill.</strong> — <code>vllm:request_decode_time_seconds</code> vs <code>vllm:request_prefill_time_seconds</code>. Decode is 97% of the time against prefill's 2%, confirmed twice. Chunked prefill and prompt compression are off the table for this workload.</li>
<li><strong>If more throughput is needed, quantize before buying hardware.</strong> — <code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE</code> (16.6%) vs <code>DCGM_FI_PROF_DRAM_ACTIVE</code> (80.4%). The bottleneck is memory bandwidth, not compute, so an FP8 or AWQ build of the same model is the highest-leverage single change: it moves fewer bytes per token, which is exactly the constrained resource.</li>
<li><strong>Raise the client-side <code>max_tokens</code> ceiling.</strong> — <code>vllm:request_success_total{finished_reason}</code>. Every request finishing on <code>length</code> rather than <code>stop</code> is a user getting cut off mid-answer. This is the one finding that's a live user-experience defect. You need to increase the prompt max_token limit.</li>
<li><strong>Standardize the prompt prefix.</strong> — <code>vllm:prompt_tokens_cached_total</code> over <code>vllm:prompt_tokens_total</code>, 32% today. But it should be better (more like 70%) More shared boilerplate in a consistent leading position raises it, and it's free.</li>
<li><strong>Set the autoscaling trigger now, before it's needed.</strong> — <code>vllm:kv_cache_usage_perc</code> and <code>vllm:num_requests_waiting</code>. Scale when the first crosses ~60% or the second stays above zero. Do <em>not</em> scale on <code>DCGM_FI_DEV_GPU_UTIL</code> — it's pinned at 100% regardless.</li>
<li><strong>Alert on queue time, not on VRAM.</strong> — <code>vllm:request_queue_time_seconds</code> is the earliest true saturation signal; <code>DCGM_FI_DEV_FB_USED</code> is a constant that looks like an emergency.</li>
<li><strong>Revisit when the workload changes shape.</strong> — <code>vllm:request_generation_tokens</code> over <code>vllm:request_prompt_tokens</code>, 2.04 today. When the RAG feature ships that ratio inverts, the workload becomes prefill-bound, and half of this analysis needs redoing. The chart tells you the day it happens.</li>
</ol>
<p>Notice that most of the metrics are looking at the vLLM metrics not the GPU metrics in helping optimize. These are still within the limit of a single service, but when you get <code>vllm:num_requests_waiting</code> to persistently non-zero, then you need to run KServe or you can use KEDA and HPA autoscaling. But you get the metrics to help you determine or allow KServe to scale. So you can see that understanding these metrics are crucial to tuning the inference service. </p>
<p>Elastic Observability can provide this to you.</p>
<hr />
<h2 id="whyselfhostedllmtuningisansreproblemnotanmlproblem">Why self-hosted LLM tuning is an SRE problem, not an ML problem</h2>
<p><strong>Self-hosting an open-weight model is not primarily an ML problem. It is a capacity and saturation problem</strong> — something SREs have been extremely good at for twenty years. The blocker was never skill. It was that the telemetry sat unexamined on a <code>/metrics</code> endpoint nobody scraped, in a schema nobody had mapped to the questions they actually had.</p>
<p>Once it's collected, the reasoning is familiar work in unfamiliar clothes:</p>
<ul>
<li>Decompose latency by phase before optimizing anything (queue / prefill / decode).</li>
<li>Distinguish the logical resource from the physical one (KV block pool ≠ VRAM), and know which each metric describes.</li>
<li>Never trust a single-source utilization number — corroborate the engine's account with the hardware's.</li>
<li>Tie every knob to a metric and every metric to a stated objective, so tuning converges instead of wandering.</li>
</ul>
<p>For a team that isn't allowed to send its data anywhere, that difference — between running a model and <em>operating</em> one — is the whole ballgame. The department gets a capability it's otherwise locked out of, and the SRE gets to answer questions about it with numbers.</p>
<hr />
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>What does <code>vllm:kv_cache_usage_perc</code> measure?</strong>
It measures occupancy of vLLM's pre-allocated KV block pool, not physical GPU memory. vLLM reserves a fraction of VRAM at startup (<code>--gpu-memory-utilization</code>, default 0.9) and carves the KV pool from that reservation. In this deployment it read 2.8% while DCGM reported 94.1% VRAM used on the same card at the same moment. Use it as an autoscaling signal; use VRAM for capacity planning.</p>
<p><strong>Why is my vLLM deployment decode-bound?</strong>
Because the workload generates more tokens than it reads. Compare <code>vllm:request_decode_time_seconds</code> against <code>vllm:request_prefill_time_seconds</code>, and check the generation-to-prompt token ratio. In this deployment the ratio was 2.04 — twice as many output tokens as input — which produced 1,665 ms of decode against 38 ms of prefill. Decode is memory-bandwidth-bound, so quantization, tensor parallelism, or a smaller model help; prefill optimizations do not.</p>
<p><strong>Should I autoscale vLLM on GPU utilization?</strong>
No. <code>DCGM_FI_DEV_GPU_UTIL</code> means a kernel is resident on the device, not that the device is doing useful work — it reads 100% for both a well-tuned and a badly-tuned server. Autoscale on <code>vllm:kv_cache_usage_perc</code> (around 60%) or on <code>vllm:num_requests_waiting</code> staying above zero, since those describe whether the engine can admit another request.</p>
<p><strong>Why does my GPU show 100% utilization when it isn't fully used?</strong>
Because GPU utilization only reports kernel residency. Check the DCGM profiling counters instead: in this deployment <code>DCGM_FI_PROF_GR_ENGINE_ACTIVE</code> was 99.8% while <code>DCGM_FI_PROF_PIPE_TENSOR_ACTIVE</code> was only 16.6% and <code>DCGM_FI_PROF_DRAM_ACTIVE</code> was 80.4%. That combination means the GPU is waiting on memory bandwidth rather than computing.</p>
<p><strong>How do I get vLLM metrics into Prometheus?</strong>
vLLM already exposes Prometheus exposition format on <code>/metrics</code> at its serving port — no adapter or instrumentation needed. Point a Prometheus scrape job at the vLLM Service, add a second job for <code>dcgm-exporter</code> on <code>:9400</code>, and use <code>remote_write</code> to ship to long-term storage. Sending through an OpenTelemetry Collector also works but normalizes the metric names, which makes them harder to match against vLLM documentation.</p>
<p><strong>What TTFT should I target for an interactive LLM application?</strong>
For a streaming chat-style interface, a p95 time-to-first-token under 300 ms feels immediate, and inter-token latency under 50 ms (about 20 tokens/sec) outpaces reading speed. This deployment measured 51 ms TTFT and 16 ms inter-token latency on a 3B model on a single NVIDIA A10G, leaving roughly 6× headroom.</p>
<p><strong>Why are all my vLLM requests finishing with <code>length</code>?</strong>
Because they're hitting the <code>max_tokens</code> ceiling instead of the model choosing to stop. Break <code>vllm:request_success_total</code> down by its <code>finished_reason</code> label: a high <code>length</code> share means answers are being truncated mid-sentence. This is invisible in every latency metric, so review the <code>stop</code>-to-<code>length</code> ratio regularly and raise the client-side ceiling if it drifts.</p>
<p><strong>When should I move from a plain vLLM Deployment to KServe or llm-d?</strong>
Move to KServe when <code>vllm:num_requests_waiting</code> is persistently non-zero at peak and you need replicas to appear without human intervention. Move to llm-d when your prefix-cache hit rate collapses across replicas — a sign that load balancing scattered conversations that shared a prefix — or when prefill time starts stealing measurably from decode.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/tune-vllm-prometheus-metrics-elastic</link>
    <guid isPermaLink="false">tune-vllm-prometheus-metrics-elastic</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt33f0247e8d048208/6a859acebc5bb37efef81125/header-vllm-tuning.png" length="0" type="image/png"/>
    <pubDate>Thu, 13 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[From CrashLoopBackOff to OOMKilled with PromQL in Elasticsearch and Kibana]]></title>
    <description><![CDATA[Use PromQL in Elasticsearch and Kibana to move from a CrashLoopBackOff alert to OOMKilled, memory versus the limit, and a verified fix.]]></description>
    <content:encoded><![CDATA[<p>A <code>CrashLoopBackOff</code> alert on <code>checkout-api</code> is paging you.
With <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/promql">PromQL</a> in Elasticsearch and Kibana, you can move from that alert to <code>OOMKilled</code>, prove the container is hitting its memory limit (not the node), raise the limit, and watch the alert recover.
If you are new to PromQL in Elastic, start with <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch supports PromQL</a>, <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">PromQL queries in Kibana</a>, and <a href="https://www.elastic.co/observability-labs/blog/promql-investigate-kubernetes-infrastructure">Investigate Kubernetes infrastructure with PromQL</a>.</p>
<h2 id="whatyouneed">What you need</h2>
<ul>
<li>An <strong>Observability</strong> <a href="https://www.elastic.co/docs/solutions/observability/get-started">serverless project</a>, or an Elastic Cloud Hosted or self-managed stack at <strong>version 9.4 or later</strong>.
PromQL is <strong>generally available</strong> in Elastic Cloud Serverless and Elastic Stack 9.5, and available as a <strong>technical preview</strong> in Elastic Stack 9.4.</li>
<li>Kubernetes state and container memory metrics in Elasticsearch.</li>
</ul>
<h2 id="whatisthealerttellingus">What is the alert telling us?</h2>
<p>This is the alert that opened the investigation:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd2f013a3a12a2919/6a7f19f35967e55ed15dd6b9/active-alert.png" alt="Active checkout API CrashLoopBackOff alert in Kibana" /></p>
<p>It comes from this waiting-reason query:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    max_over_time(
      kube_pod_container_status_waiting_reason{
        namespace="checkout",
        pod=~"checkout-api-.*",
        container="api",
        reason="CrashLoopBackOff"
      }[2m]
    )
  ) == 1
</code></pre>
<p>A result of <code>1</code> means Kubernetes is delaying another start because the container has failed repeatedly.
That is the correct paging signal here because the checkout path has a single replica: when that replica restarts, requests fail.</p>
<p>The <code>max_over_time(...[2m])</code> range keeps the alert tied to recent samples.
Without it, the last observed value of <code>1</code> can outlive the pod, and the rule keeps matching after that pod is gone.</p>
<p>That PromQL query ran every minute over a two-minute window and created an alert after one matching run.</p>
<h2 id="whydidthelastcontainerstop">Why did the last container stop?</h2>
<p>The alert shows what Kubernetes is doing now.
It does not show how the previous container ended:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    kube_pod_container_status_last_terminated_reason{
      namespace="checkout",
      pod=~"checkout-api-.*",
      container="api",
      reason="OOMKilled"
    }
  ) == 1
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1b7cd5d1c73d9d32/6a7f19f6e02fac5a485d69a3/last-termination-oom.png" alt="PromQL result showing OOMKilled as the last termination reason for checkout-api-8655769b49-vwddl" /></p>
<p>A result of <code>1</code> for the same namespace, pod, and container means the last recorded exit was out of memory.
Kube-state-metrics keeps that last reason as a gauge, so the value can stay visible after recovery.
It points the investigation at memory; it does not prove that every restart in the window was an OOM kill.</p>
<h2 id="isthefailurerepeating">Is the failure repeating?</h2>
<p>A single restart can still be transient.
The restart counter shows whether the failure keeps happening:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    increase(
      kube_pod_container_status_restarts_total{
        namespace="checkout",
        pod=~"checkout-api-.*",
        container="api"
      }[10m]
    )
  )
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte798ed61aa455a3b/6a7f19f9bdcff037f7c4329f/restart-history.png" alt="PromQL chart showing repeated checkout API container restarts during the incident" /></p>
<p><code>increase()</code> shows how much the restart counter rose over the selected range.
Repeated increases during the incident window explain why Kubernetes entered backoff.</p>
<h2 id="howcloseismemorytothelimit">How close is memory to the limit?</h2>
<p>We need to know how close the container is to its memory limit, and whether that gap collapses right before each restart.
This deployment allows only 128MiB, so the next query divides working-set memory by that configured limit:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    container_memory_working_set_bytes{
      namespace="checkout",
      pod=~"checkout-api-.*",
      container="api",
      image!=""
    }
  )
  /
  max by (namespace, pod, container) (
    container_spec_memory_limit_bytes{
      namespace="checkout",
      pod=~"checkout-api-.*",
      container="api",
      image!=""
    }
  )
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfba66d8eb586ec9a/6a7f19fc33fa8af83e202b78/memory-limit-percent.png" alt="Checkout API memory repeatedly climbing toward its 128MiB container limit before OOMKilled restarts" /></p>
<p>The chart shows a repeating sawtooth: memory approaches 90% of the limit, drops when the process stops, and climbs again after each restart.</p>
<p>Working set is the better signal here than total usage.
Total usage includes reclaimable file cache, so it can sit near the limit without a kill.
Working set is closer to the memory that triggers OOMKilled for this workload.</p>
<h2 id="isthenodeundermemorypressure">Is the node under memory pressure?</h2>
<p><code>OOMKilled</code> can mean the container hit its own limit, or the node ran low on memory and Kubernetes started reclaiming.
To separate those cases, first find which node runs the pod, then check whether that node (or any peer) reported <code>MemoryPressure</code>.</p>
<pre><code>PROMQL
  max by (namespace, pod, node) (
    kube_pod_info{
      namespace="checkout",
      pod=~"checkout-api-.*"
    }
  ) == 1
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt831b6ef6ee5cb750/6a7f19ffe02fac7f585d69a7/pod-node.png" alt="PromQL result mapping the checkout API pod to ip-10-0-2-18.ec2.internal" /></p>
<p>The pod sits on <code>ip-10-0-2-18.ec2.internal</code>.
That is the node whose <code>MemoryPressure</code> result matters most for this incident:</p>
<pre><code>PROMQL
  max by (node) (
    max_over_time(
      kube_node_status_condition{
        condition="MemoryPressure",
        status="true"
      }[30m]
    )
  )
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltec63d50e8f11cd0f/6a7f1a023ce8e24429cf57a1/node-memory-pressure.png" alt="PromQL result showing no Kubernetes MemoryPressure on the cluster nodes" /></p>
<p>Every node returns <code>0</code>, including <code>ip-10-0-2-18.ec2.internal</code>.
So the host was not under node-wide memory pressure.
The kill came from the container limit itself.</p>
<h2 id="doesraisingthelimitclearthealert">Does raising the limit clear the alert?</h2>
<p><code>checkout-api</code> was healthy, then began building an in-memory cache that grows to 200MiB in 10MiB steps.
The container only allows 128MiB, so the process is killed with <code>OOMKilled</code> before that cache is fully allocated.</p>
<p>We will raise the memory limit to 512MiB so the 200MiB cache fits with room for the runtime, then check whether <code>CrashLoopBackOff</code> clears:</p>
<pre><code>kubectl set resources deployment/checkout-api -n checkout --limits=memory=512Mi
</code></pre>
<p>The same waiting-reason query then stops matching.
<code>CrashLoopBackOff</code> drops off:</p>
<pre><code>PROMQL
  max by (namespace, pod, container) (
    max_over_time(
      kube_pod_container_status_waiting_reason{
        namespace="checkout",
        pod=~"checkout-api-.*",
        container="api",
        reason="CrashLoopBackOff"
      }[2m]
    )
  ) == 1
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d0d9f8d705b70eb/6a7f1a0473d9bde46629df4f/waiting-reason-cleared.png" alt="PromQL result showing CrashLoopBackOff clearing after the memory limit increase" /></p>
<p>And the alert that started this investigation? Gone.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte501026dbe06098d/6a7f1a0805b7b57f9d18bd3d/recovery.png" alt="Recovered checkout API CrashLoopBackOff alert in Kibana" /></p>
<p>That is how you detect and investigate a Kubernetes CrashLoopBackOff with PromQL: from the firing alert, through <code>OOMKilled</code> and the limit mismatch, to a recovered alert.
Elasticsearch holds the metrics; Kibana runs the same PromQL queries you already know from Prometheus.</p>
<h2 id="tryit">Try it</h2>
<ol>
<li>Open an <a href="https://cloud.elastic.co/serverless-registration">Observability project on Elastic Cloud Serverless</a>, or use Elastic Stack 9.4 or later.</li>
<li>In the ES|QL editor in Kibana, run the waiting-reason query against a workload you care about.</li>
<li>Follow the same path from that alert to termination reason, restarts, memory versus the limit, and recovery.</li>
</ol>
<p>For more PromQL in Elastic, see <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch supports PromQL</a>, <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">PromQL queries in Kibana</a>, and <a href="https://www.elastic.co/observability-labs/blog/promql-investigate-kubernetes-infrastructure">Investigate Kubernetes infrastructure with PromQL</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/promql-kubernetes-oomkilled-crashloopbackoff</link>
    <guid isPermaLink="false">promql-kubernetes-oomkilled-crashloopbackoff</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt256fcae0e6b1797d/6a7f1a0bfc63ab76c464d06c/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 06 Aug 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[CrashLoopBackOff to root cause in seconds: automating the 20-minute Kubernetes investigation with Elastic Observability]]></title>
    <description><![CDATA[Elastic's Kubernetes Experience fires alongside the CrashLoopBackOff alert and delivers a root-cause hypothesis with evidence before you even open it.]]></description>
    <content:encoded><![CDATA[<p>It's the middle of your on-call rotation and your phone buzzes. <code>CrashLoopBackOff</code>. A pod is stuck in a restart cycle, and now the clock is running.</p>
<p>If you've been an SRE for any length of time, you know what usually comes next. You acknowledge the page, open your observability tool, and start the <em>process</em>: pull up the cluster dashboard, find the namespace, find the pod, check restart counts, pivot to logs, check whether an upstream dependency is degraded, compare against last week, and slowly assemble a picture from a dozen tabs. It works, but it's a process, and the process is where the minutes go.</p>
<p>Elastic's new Kubernetes Experience changes the starting point. When the CrashLoopBackOff alert fires, an <strong>Investigation Workflow runs automatically alongside it</strong>. By the time you open the alert, the evidence has already been gathered and a root-cause hypothesis is waiting for you. Instead of a blank dashboard, you open the alert to an answer. Or at minimum, a strong starting point that tells you exactly where to look next.</p>
<p>This post walks through a typical CrashLoopBackOff scenario end to end. The sections that follow break down what Elastic's UI shows at each step and why it saves you time.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt295cd19d441aab2c/6a7f04823ce8e25835cf502e/workflow-root-cause-summary.png" alt="Elastic Workflows synthesize step" /></p>
<h2 id="themanualcrashloopbackoffinvestigationsixstepseverysreruns">The manual CrashLoopBackOff investigation: six steps every SRE runs</h2>
<p>Here's the shape of a normal CrashLoopBackOff investigation, minus Elastic's workflow:</p>
<ol>
<li><strong>You get paged.</strong> Something restarted too many times.</li>
<li><strong>You orient.</strong> Which pod? Which namespace? Which deployment owns it?</li>
<li><strong>You characterize.</strong> How many restarts? What was the last termination reason — OOMKilled, a failed liveness probe, a bad exit code?</li>
<li><strong>You classify.</strong> Is this a memory problem, a config problem, a dependency problem, a scheduling problem?</li>
<li><strong>You corroborate.</strong> Pull Kubernetes events, read the pod logs, check whether an upstream service started erroring first, compare current behavior against a healthy baseline.</li>
<li><strong>You conclude.</strong> Only now can you form a hypothesis and act.</li>
</ol>
<p>Every one of those steps is a query, a click, or a context switch. None of them is hard on its own. Together, on a bad night, they're twenty minutes you don't have and they're twenty minutes of <em>the same steps you ran during the last incident, and the one before that</em>.</p>
<p>The insight behind Elastic's Kubernetes Experience is simple: <strong>that sequence is deterministic enough to automate.</strong> So Elastic automated it.</p>
<hr />
<h2 id="alerttorootcauseinminutes">Alert to Root cause in minutes</h2>
<p>Elastic's Kubernetes integration ships pre-built alert rule templates for states that are wrong by definition; no baseline or warmup required. A pod in CrashLoopBackOff is <em>always</em> a problem, so the rule fires the moment the restart count crosses your configured threshold within a rolling window.</p>
<p>Here's the alert firing in this scenario, both the <code>[Kubernetes OTel] Pod CrashLoopBackOff</code> and <code>OOMKilled containers</code> rules light up for the <code>recommendation</code> group in the <code>otel-demo</code> namespace:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8969107211092a4b/6a7f04852f00b25aefefe7e1/crashloopbackoff-alert-list.png" alt="Elastic Alerts page" /></p>
<p>The alert itself is defined by an ES|QL query, so it's transparent and tunable; you can read exactly what triggers it and adjust the threshold to match your environment. And its <strong>action</strong> is what makes the rest of this post possible: the rule runs the <code>K8s CrashLoopBackOff Investigation (OTel)</code> workflow <em>per alert</em>, the instant a new alert fires. (For a refresher on the alert library and how these templates work, see <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection#alert-rules-that-fire-on-day-one">Part 1</a>.)</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc30b604aa6b6931a/6a7f0488e88c65ccfb00b2b6/alert-config.png" alt="CrashLoopBackOff alert rule configuration in Elastic" /></p>
<p>In this scenario the rule fires on the <code>recommendation</code> pod (<code>recommendation-788dc88c6c-56w74</code>) in the <code>otel-demo</code> namespace. When you open the alert, Elastic's AI Agent is already summarizing what happened; no rule-details spelunking required:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte1d391aa18ae2850/6a7f048b73d9bd94f229d790/alert-detail-ai-summary.png" alt="CrashLoopBackOff alert detail" /></p>
<p>But the alert firing is only half the story. Attached to it is a <strong>Kubernetes Investigation Workflow</strong> (technical preview), a directed graph of steps that triggers the instant the alert does. While your phone is still buzzing, the workflow is already querying your cluster, branching on what it finds, and synthesizing an answer.</p>
<hr />
<h2 id="insidetheinvestigationwhattheworkflowactuallydoes">Inside the investigation: what the workflow actually does</h2>
<p>The workflow mirrors the exact sequence an experienced SRE would run by hand, except it runs in seconds and writes nothing down that it can't back up with evidence. For our CrashLoopBackOff scenario, here's the path it takes.</p>
<h3 id="step1whatdoestheworkflowcheckfirstonthecrashingpod">Step 1: What does the workflow check first on the crashing pod?</h3>
<p>The workflow queries Kubernetes metrics for the restart count, the last termination reason, and utilization against the pod's declared limits.</p>
<p><strong>Result:</strong> last termination reason <code>OOMKilled</code>, restart count <code>7</code>. Memory utilization data happened to be unavailable at query time, but the <code>OOMKilled</code> reason is definitive: the container is being killed by the kernel for exceeding its memory limit on each startup, then immediately restarting.</p>
<p>The <code>OOMKilled</code> termination reason determines everything that follows. The workflow branches down the <strong>memory-investigation path</strong> rather than the log-investigation path it would take for a non-memory crash.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt17a7d46eeae29ded/6a7f048f227b1c51c7598272/workflow-step-characterize-oomkilled.png" alt="Elastic Workflows execution" /></p>
<h3 id="step2howdoestheworkflowtellamemoryleakfromaloadspike">Step 2: How does the workflow tell a memory leak from a load spike?</h3>
<p>The ML anomaly check is the step that separates a good investigation from a fast-but-wrong one. <code>OOMKilled</code> does <strong>not</strong> automatically mean "memory leak." Rather than recompute memory trends from scratch, the workflow queries the ML anomaly index for an active <code>k8s_pod_memory_growth</code> anomaly on this pod.</p>
<p><strong>Result:</strong> no anomaly. The memory spike is flagged as <strong>load-driven, not a suspected leak.</strong> The ML baseline — established over the preceding days — didn't see the slow, creeping growth trajectory that characterizes a leak. It saw a jump consistent with real traffic.</p>
<p>Distinguishing a load-driven memory spike from a genuine leak would take a human several minutes and a good deal of judgment. The workflow reaches it because Part 1's <a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection#anomaly-detection-jobs-with-ml-baselines">anomaly detection jobs</a> were already learning the workload's baseline in the background.</p>
<h3 id="step3isthefailurespreadingtootherkubernetesservices">Step 3: Is the failure spreading to other Kubernetes services?</h3>
<p>A crashing pod is often a <em>symptom</em>, not a cause, and it can also <em>cause</em> problems downstream. So the workflow enumerates the pod's dependencies from APM <code>service_destination</code> aggregates and compares the current error rate and latency against baseline. An AI classification step decides whether the failure is spreading.</p>
<p><strong>Result:</strong> the sole direct caller is the <code>frontend</code> service, which is absorbing the impact with just a <strong>0.14% error rate</strong> against the recommendation service  and no other service exceeds its degradation threshold relative to baseline. The <strong>blast radius is isolated to the recommendation service</strong>; there's no significant downstream cascade. The problem is local to this pod.</p>
<h3 id="step4didarecentchangeinthenamespacecausethecrashloop">Step 4: Did a recent change in the namespace cause the crash loop?</h3>
<p>Finally, the workflow scans the namespace event log. It finds a continuous <code>Pulled → Created → Started → Killing → BackOff</code> cycle running from roughly <strong>18:51 to 18:54 UTC</strong>, the textbook signature of an active crash loop at the time the alert fired. Nothing changed operationally; this is a steady-state resource problem.</p>
<h3 id="whatthecrashloopbackoffrootcausehypothesislookslike">What the CrashLoopBackOff root-cause hypothesis looks like</h3>
<p>When you open the alert, this is what greets you:</p>
<pre><code>ROOT CAUSE HYPOTHESIS (confidence: high)

The recommendation service pod (recommendation-788dc88c6c-56w74) is in a
crash-loop caused by repeated OOMKilled terminations. The pod has restarted
7 times and Kubernetes events confirm a continuous BackOff/restart cycle
since at least 18:51 UTC. Memory utilization data was unavailable at query
time, but the OOMKilled termination reason is definitive: the container is
exceeding its configured memory limit on each startup, being killed by the
kernel, and immediately restarting. No memory leak was detected by ML
anomaly analysis, indicating the memory pressure is load-driven — the
container's memory limit is simply insufficient for the current request
volume. The frontend service (the sole direct caller) is absorbing the
impact with a 0.14% error rate on the recommendation service itself, but
no significant downstream cascade is observed.

EVIDENCE
- Pod restarted 7 times; last termination reason: OOMKilled — container is
  consistently exceeding its memory limit
- ML memory anomaly check: no anomaly found; memory spike assessed as
  load-driven, not a leak
- Blast radius is isolated to the recommendation service; no other service
  exceeds degradation thresholds relative to baseline
- Continuous BackOff events from 18:51–18:54 UTC confirm active crash-loop
  at alert time

PROBABLE CAUSE: The recommendation container's memory limit is too low for
current traffic load, causing repeated OOMKilled terminations and a
crash-loop backoff.

RECOMMENDED NEXT STEPS
1. Immediately increase the memory limit (and request) for the
   recommendation container in its Deployment spec to provide headroom
   above the observed peak usage, then redeploy to break the crash-loop.
2. Profile the recommendation service under representative load to
   determine the actual memory working set and set a right-sized limit
   with a safe buffer (e.g., 20–30% above peak observed).
3. Add a Kubernetes HorizontalPodAutoscaler or VPA policy for the
   recommendation service so memory resources scale with traffic rather
   than requiring manual intervention.
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt295cd19d441aab2c/6a7f04823ce8e25835cf502e/workflow-root-cause-summary.png" alt="Elastic Workflows synthesize step" /></p>
<p>Read that again from the perspective of the on-call engineer. You were paged. You opened the alert. And the alert didn't hand you a pile of logs and a dashboard; it handed you a <strong>calibrated root-cause hypothesis with the evidence attached and the next actions spelled out.</strong> The six manual steps from the "old way" are done. Your job is now to <em>decide</em>, not to <em>dig</em>.</p>
<p>That's the time savings: not shaving a few seconds off each query, but removing the entire investigative scavenger hunt from the critical path.</p>
<hr />
<h2 id="howdoeselasticobservabilityavoidmisdiagnosingoomkilledasamemoryleak">How does Elastic Observability avoid misdiagnosing OOMKilled as a memory leak?</h2>
<p>Speed is worthless if the answer is wrong, so it's worth noting <em>how</em> the workflow avoids the classic misdiagnoses. It encodes the reasoning an experienced SRE applies instinctively:</p>
<ul>
<li><strong><code>OOMKilled</code> is not automatically a leak.</strong> It compares against a 7-day baseline before ever claiming one. Here, that check is what turned "the app is leaking memory" into the correct "the limit is undersized for real load."</li>
<li><strong>Co-symptoms are not causes.</strong> It explicitly checks whether the upstream degraded <em>first</em> before blaming or clearing it.</li>
<li><strong>Absence of evidence is not evidence.</strong> If a query returns zero rows, it reports "no data available" rather than inventing a failure mode.</li>
<li><strong>It's honest about confidence.</strong> The hypothesis is labeled <code>high</code>, <code>medium</code>, or <code>low</code>. When two failure modes fit the evidence, the workflow names both and says which it believes is causal and why. Manufacturing false confidence is treated as a failure of the investigation itself.</li>
</ul>
<p>This is the same diagnostic protocol encoded in Elastic's <code>observability-k8s-investigation</code> Skill,  a failure-mode taxonomy covering 16 distinct Kubernetes failure patterns, from OOMKilled and CPU throttling through scheduling and networking issues. (More on that in <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp#observability-skill-for-kubernetes-investigations">Part 2</a>.)</p>
<hr />
<h2 id="stillwanttodoublecheckdashboardsdiscoverandapm">Still want to double-check? Dashboards, Discover, and APM</h2>
<p>The workflow gives you the answer. But a good root-cause tool should also make it trivial to <em>verify</em> that answer because sometimes you want to see it with your own eyes, and sometimes the workflow returns <code>medium</code> confidence and you need to close the gap yourself. Everything the workflow reasoned over is available to you directly.</p>
<p><strong>Dashboards — confirm the restart cascade visually.</strong> The Kubernetes dashboards are built for drill-down. Start at the cluster <strong>Overview</strong>, where "top namespaces by container restarts" surfaces the problem at a glance. Click into the flagged namespace, then the pod driving the restarts. The <strong>Pods</strong> view flags container restarts on the <code>recommendation</code> pod and plots memory against requests and limits over time — you'll see the working set pressing against the limit exactly as the workflow described. It's roughly four clicks from cluster to container.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7c8b428c0dcd661/6a7f04926693f80ed8663bc1/dashboard-overview.png" alt="Kubernetes OTel Overview dashboard" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltad1fe324b28c4f88/6a7f049696b5a694cd87b0c1/dashboard-overview-part2.png" alt="Kubernetes OTel Overview dashboard, Namespaces section" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf90ad0e667f86b70/6a7f0499ea068d7e4af09ae3/dashboard-pod-detail-restarts.png" alt="Kubernetes Pods dashboard in Elastic" /></p>
<p><strong>Discover — read the raw evidence.</strong> The pod detail dashboard links directly to correlated pod logs and events in Discover. Here you can confirm the <code>OOMKilled</code> events and the restart cadence in the raw log and event stream, and run your own ES|QL queries if you want to slice the data differently.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt870c24a37ac11428/6a7f049cc2e914ef5d016836/discover-backoff-events.png" alt="Discover in Elastic running an ES|QL query over Kubernetes events" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbfa295a9478e4883/6a7f04a0ead8ecac6bbaa485/discover-oomkilled-utilization.png" alt="Discover in Elastic running a TS ES|QL query" /></p>
<p><strong>APM — verify the blast radius is really contained.</strong> The workflow found the impact isolated to the recommendation service, with <code>frontend</code> (the sole caller) absorbing it at a 0.14% error rate. You can confirm that independently in the APM UI: open the service map, check the caller's latency and error rate over the incident window, and compare against the weekly baseline yourself.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06e97005715181a0/6a7f04a3448e4e1e6e5c035e/apm-frontend-blast-radius.gif" alt="Animated walkthrough of the APM UI" /></p>
<p>The point isn't that you <em>have</em> to do any of this; it's that the workflow's conclusion is fully auditable. Fast when you trust it, transparent when you want to check.</p>
<hr />
<h2 id="thesameinvestigationfromyouridethemcpapp">The same investigation from your IDE: the MCP App</h2>
<p>Not every investigation starts from a Kibana alert. Sometimes a developer just asks, "why is this service crashing?" from their editor. Elastic's <strong>Observability MCP App</strong> (technical preview) exposes the same telemetry (and the same investigation workflow) as AI-callable tools that render interactive views <strong>inline in your chat or IDE</strong>, no context switch to Kibana required.</p>
<p>For our CrashLoopBackOff scenario, the flow looks like this from an MCP-compatible client such as Claude Desktop or VS Code:</p>
<p><strong>"What's broken?"</strong> → the cluster health rollup returns an overall health badge, degraded services, top memory consumers, and a Kubernetes breakdown (CPU, memory, restarts, nodes) in one inline view. Here it flags a critical cluster with <code>payment</code>, <code>cart</code>, <code>frontend</code>, and <code>frontend-proxy</code> degraded.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta1fe86e1a29f6b1b/6a7f04a6e02fac45915d620d/mcp-app-cluster-health.png" alt="Elastic Observability MCP App rendering a cluster health" /></p>
<p><strong>"Is anything anomalous in the recommendation pod?"</strong> → the memory analysis view confirms the spike is load-driven, not a leak — the crashing <code>788dc88c6c</code> pods report <em>null</em> memory (they die too fast to emit a sample) while the healthy pods sit flat at 45MB, exactly the ML result the workflow used.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4f90704b7c542f6/6a7f04aac2cc09230824918e/mcp-app-memory-analysis.png" alt="Elastic Observability MCP App rendering an inline memory analysis for the recommendation pods" /></p>
<p><strong>"Why is the recommendation service crashing?"</strong> → the agent returns the <strong>same structured root-cause reasoning</strong> you'd see on the alert, rendered inline: memory limit set below what the container needs to boot, the ReplicaSet has been intermittently OOMing for weeks, and a concrete mitigation (roll back, then fix the limit), all without leaving the editor.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3817791e4172bef8/6a7f04ad3cab1c477a0e44b4/mcp-app-root-cause.png" alt="Elastic Observability MCP App rendering the inline root-cause answer" /></p>
<p>Same evidence, same root cause, delivered wherever you happen to be working. (For the full set of MCP App views and architecture, see <a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp#observability-mcp-app-that-renders-where-you-work">Part 2</a>.)</p>
<hr />
<h2 id="frompagedtodecidedskippingthekubernetesinvestigationentirely">From paged to decided: skipping the Kubernetes investigation entirely</h2>
<p>The change here is not "a better dashboard." It's a shift in <em>what you do when you get paged.</em></p>
<p>Before, the alert was the <strong>start</strong> of the investigation. You were notified, and then you went and found the answer. Now, the alert arrives <strong>with</strong> the investigation already run: evidence gathered, dead ends eliminated, a calibrated hypothesis, and next steps in hand. You go straight from "notified" to "deciding," and you keep the full trail of dashboards, Discover, and APM for whenever you want to verify or dig deeper.</p>
<p>For a single incident that's a few minutes saved. Across a quarter of on-call rotations, across every engineer who no longer re-runs the same six steps at 3 a.m., it's real time back and a lot less alert fatigue.</p>
<hr />
<h2 id="tryityourself">Try it yourself</h2>
<p>You don't need a production incident to see this. The <strong>OpenTelemetry Astronomy Shop</strong> demo environment ships with a feature-flag service that lets you trigger failure scenarios on demand. Enable a cart/checkout failure, watch the restart cascade unfold, and the CrashLoopBackOff alert rule fires with the investigation workflow running right behind it.</p>
<p>To get set up:</p>
<ol>
<li><strong>Install the Kubernetes integration</strong> — dashboards are available immediately. (<a href="https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection#getting-started">Part 1: Getting started</a>)</li>
<li><strong>Deploy data collection</strong> via the EDOT Collector (OpenTelemetry) or standalone Elastic Agent, both Helm-based.</li>
<li><strong>Enable the alert rule templates</strong> in Observability &gt; Alerts, including CrashLoopBackOff, and connect your notification channel.</li>
<li><strong>Let the ML modules warm up</strong> for 24–48 hours so anomaly baselines are ready when you need them.</li>
<li><strong>Enable the Investigation Workflow</strong> (technical preview) — import the Kubernetes Crashloop Investigation Workflow from the Workflows page and configure it to trigger on the alert. (<a href="https://www.elastic.co/observability-labs/blog/ai-powered-kubernetes-observability-elastic-mcp#getting-started">Part 2: Getting started</a>)</li>
<li><strong>Install the MCP App</strong> (technical preview) on your favorite agentic client to bring investigations into your IDE.</li>
</ol>
<hr />
<p><em>Running Kubernetes on Elastic today? Tell us which investigation steps you still repeat by hand on every incident, and which remediations you'd trust a workflow to propose. Join the <a href="https://discuss.elastic.co/c/observability">Elastic Community Discussion</a>.</em></p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/crashloopbackoff-root-cause-kubernetes</link>
    <guid isPermaLink="false">crashloopbackoff-root-cause-kubernetes</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt774f48dbd750660d/6a7f04b042a11784a095bb3e/crashloopbackoff-scenario-poster.png" length="0" type="image/png"/>
    <pubDate>Fri, 31 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Four clicks from alert to root cause: how Elastic Observability links APM services to Kubernetes infrastructure]]></title>
    <description><![CDATA[Check service dependencies and compare per-pod CPU, memory and network trends on the Infrastructure tab to find which instance is causing trouble, all without leaving the alert investigation.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability links your <a href="https://www.elastic.co/docs/solutions/observability/apm/opentelemetry">OTel-instrumented services</a> to the Kubernetes hosts, containers, and pods they run on.
The <a href="https://www.elastic.co/docs/solutions/observability/apm/infrastructure">Infrastructure tab</a> in APM puts per-instance CPU, memory and network trends a few clicks away, so when a service degrades you can spot which pod lines up with when the problem started, all from inside the investigation.
This walkthrough follows a latency alert on a recommendation service from notification to the problematic pod in four steps.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte83736c1f42a7f15/6a7f02af2f00b296c3efe6ef/step-04-check-infra-metric-trends.gif" alt="Correlating service latency with per-pod infrastructure metrics" /></p>
<h2 id="availability">Availability</h2>
<p>This is available in Elastic Observability serverless today and is coming to Elastic Cloud Hosted and self-managed deployments in 9.5.</p>
<h2 id="prerequisitesforlinkingapmservicestokubernetesinfrastructure">Prerequisites for linking APM services to Kubernetes infrastructure</h2>
<p>You need application traces and Kubernetes infrastructure metrics in the same Elastic Observability project.</p>
<ul>
<li><strong>Application instrumentation:</strong> EDOT-instrumented services sending traces via the EDOT Collector or an <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/upstream-collector">upstream OpenTelemetry Collector</a> with both the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmconnector"><code>elasticapm</code> connector</a> and the <a href="https://www.elastic.co/docs/reference/edot-collector/components/elasticapmprocessor"><code>elasticapm</code> processor</a>. The EDOT Collector includes both by default; for a custom upstream pipeline, see the <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/upstream-collector">upstream collector setup</a>.</li>
<li><strong>Kubernetes observation:</strong> the cluster observed via OpenTelemetry with host and Kubernetes metrics from the EDOT Collector. See the <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/quickstart/serverless/k8s">Kubernetes quickstarts</a> and <a href="https://www.elastic.co/docs/solutions/observability/get-started/opentelemetry/use-cases/kubernetes">Kubernetes observability with EDOT</a> for setup.</li>
<li><strong>Backend:</strong> Observability serverless today, or Elastic Stack 9.5 on Elastic Cloud Hosted and self-managed when 9.5 releases.</li>
</ul>
<h2 id="apmalerttriagefromnotificationtoproblematicpod">APM alert triage: from notification to problematic pod</h2>
<h3 id="step1confirmtheservicedegradationontheapmalertdetailpage">Step 1: Confirm the service degradation on the APM alert detail page</h3>
<p>The redesigned <a href="https://www.elastic.co/docs/solutions/observability/apm/create-apm-rules-alerts">alert detail page</a> in Elastic Observability shows the impacted service, environment, endpoint and RED metrics in one view.
Open it from the alert notification.</p>
<p>You can clearly see which service is impacted, which environment it runs in, what endpoint is being affected and easily look for correlations in their RED metrics.
In this case, we can immediately rule out a spike in traffic as the throughput is clearly stable.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9ee2e802b0b09fc9/6a7f02b34c4bfbf920ccd0fd/step-01-alert-detail.gif" alt="Alert showing high transaction latency on the recommendation service" /></p>
<h3 id="step2ruleoutservicedependencieswiththeembeddedservicemap">Step 2: Rule out service dependencies with the embedded service map</h3>
<p>The newly embedded <a href="https://www.elastic.co/docs/solutions/observability/apm/service-map">service map</a> preview on the alert detail page shows the health and RED metrics of every dependent service, so you can rule out upstream causes without navigating away.
In this case, we have been able to quickly rule out problems with other services causing the symptom with the symptomatic service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1711cc95500177fb/6a7f02b7448e4e15195c0268/step-02-check-dependencies.gif" alt="Service map showing healthy dependent services" /></p>
<h3 id="step3reviewkubernetesinfrastructuremetricsperpodcontainerandhost">Step 3: Review Kubernetes infrastructure metrics per pod, container and host</h3>
<p>After ruling out service dependencies, open the service's updated <a href="https://www.elastic.co/docs/solutions/observability/apm/infrastructure"><strong>Infrastructure</strong> tab</a> in Elastic Observability to check for infrastructure-level patterns.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta5526498737af6af/6a7f02ba227b1cf310598174/step-03-view-service-check-infra.gif" alt="Infrastructure tab showing average metrics per instance for the symptomatic service" /></p>
<h3 id="step4compareperinstancemetrictrendstofindtherootcause">Step 4: Compare per-instance metric trends to find the root cause</h3>
<p>The <a href="https://www.elastic.co/docs/solutions/observability/apm/infrastructure">Infrastructure tab</a> shows the average metric values over the specified time period.
To really understand whether there is a problem with the infrastructure, we need to <strong>compare the pod, container and host metrics over time</strong>.
This allows us to easily spot differences between different entities that may correlate with when the service started showing symptoms.
In our example, we can clearly see a difference between some of the metrics between the pods that correlates with when the service symptoms began.
So we know there is something going on with the infrastructure that needs investigating:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte83736c1f42a7f15/6a7f02af2f00b296c3efe6ef/step-04-check-infra-metric-trends.gif" alt="Infrastructure metric trends correlating latency with a change in CPU or network" /></p>
<h2 id="summaryfromapmalerttorootcauseinfourclicks">Summary: from APM alert to root cause in four clicks</h2>
<p>In just a few clicks from an alert in Elastic Observability, you can rule out healthy dependent services without leaving the alert detail page, then compare per-pod infrastructure metrics to see which instance correlates with when the symptoms started.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/apm-kubernetes-infrastructure-metrics-analysis</link>
    <guid isPermaLink="false">apm-kubernetes-infrastructure-metrics-analysis</guid>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Roshan Gonsalkorale,Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9d1c405e47bfbf5/6a7f02beeab5be600a20a278/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Mon, 27 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Migrate Datadog Kubernetes dashboards to Elastic Observability in under an hour]]></title>
    <description><![CDATA[See how the migration CLI translates a real Datadog Kubernetes dashboard into validated Kibana panels and uploads it to your cluster in under an hour, no manual widget rebuilds required.]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://github.com/elastic/observability-migration-platform">Observability Migration Platform</a> takes a Datadog Kubernetes dashboard and turns it into ES|QL-backed Lens panels in Kibana. It validates queries against your live cluster before upload, and the whole process typically fits in under an hour. This walkthrough uses the <strong>Kubernetes - Overview</strong> board: pod CPU, working set memory, pod phases, and CrashLoopBackOff counts. Elasticsearch runs ES|QL time series queries up to 30× faster than Prometheus on common gauge and counter workloads in published benchmarks, with up to 2.5× better storage efficiency. Review the migration report and enable alerts when you are ready.</p>
<h2 id="thedatadogkubernetesdashboardusedinthismigration">The Datadog Kubernetes dashboard used in this migration</h2>
<p>The walkthrough uses <strong>Kubernetes - Overview</strong> from <code>infra/datadog/dashboards/integrations/kubernetes.json</code> in the migration repository. It is a cluster-wide board with the signals operators check during an incident: pod counts, CPU and memory by host or pod, non-running pods, and containers stuck in CrashLoopBackOff.</p>
<p>Below are representative queries from the source dashboard:</p>
<pre><code># Pod CPU by host
sum:kubernetes.cpu.usage.total{$scope,$cluster,$label,$node} by {host}
</code></pre>
<pre><code># Pod memory by pod
sum:kubernetes.memory.usage{$scope,$deployment,$statefulset,$replicaset,$daemonset,$cluster,$namespace,!pod_name:no_pod,$label,$service,$node} by {pod_name}
</code></pre>
<pre><code># Pods not running (pressure / scheduling signal)
sum:kubernetes_state.pod.status_phase{$scope,$cluster,$namespace,$deployment,$statefulset,$replicaset,$daemonset,!pod_phase:running,!pod_phase:succeeded,$label,$node,$service} by {kube_cluster_name,kube_namespace,pod_phase}
</code></pre>
<pre><code># CrashLoopBackOff
sum:kubernetes_state.container.status_report.count.waiting{$cluster,$namespace,$deployment,$statefulset,$replicaset,$daemonset,reason:crashloopbackoff,$scope,$daemonset,$label,$node,$service} by {pod_name}
</code></pre>
<p>If this board translates cleanly, most production Datadog Kubernetes folders are worth testing with the same workflow.</p>
<h2 id="whydatadogtoelasticmigrationisfasternow">Why Datadog-to-Elastic migration is faster now</h2>
<p>The migration platform automates the query translation and panel rebuilds that used to dominate Datadog moves. Elasticsearch stores Kubernetes metrics efficiently and runs the ES|QL queries those panels use. See <a href="https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-faster-cheaper-datadog">Elasticsearch as a metrics backend</a> for benchmark context and storage comparisons.</p>
<p>The platform maps Datadog queries to Kibana panels, validates ES|QL against live data, and writes artifacts you can inspect before anything goes to production.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>You need an <a href="https://www.elastic.co/docs/solutions/observability/get-started">Elastic Observability Serverless</a> project, a <a href="https://www.elastic.co/docs/deploy-manage/api-keys/serverless-project-api-keys">project API key</a>, and the migration CLI installed from the <a href="https://github.com/elastic/observability-migration-platform">Observability Migration Platform</a> repository.</p>
<p>Export your endpoints and API key:</p>
<pre><code>export ELASTICSEARCH_ENDPOINT="https://YOUR_ES_ENDPOINT"
export KIBANA_ENDPOINT="https://YOUR_KIBANA_ENDPOINT"
export KEY="YOUR_API_KEY"
</code></pre>
<p>Install the CLI and confirm the toolchain:</p>
<pre><code>python3 -m venv .venv
.venv/bin/pip install ".[all]"
.venv/bin/obs-migrate doctor
</code></pre>
<p>The <code>doctor</code> command checks compile and lint dependencies. Resolve any errors before you migrate production dashboards. Pin a release tag if you plan to run this in CI.</p>
<p>To pull dashboards from the Datadog API instead of JSON files, copy <code>datadog_creds.env.example</code> to <code>datadog_creds.env</code> and set <code>DD_API_KEY</code>, <code>DD_APP_KEY</code>, and <code>DD_SITE</code>.</p>
<h2 id="ingestkubernetesmetricsfirst">Ingest Kubernetes metrics first</h2>
<p>Empty panels after upload usually mean Elasticsearch does not yet have the series the Datadog queries reference. Make sure to confirm ingest before you run the migration.</p>
<p>There are two common paths to do so:</p>
<ol>
<li>OpenTelemetry into managed OTLP with Kubernetes receivers (<code>kubeletstats</code>, <code>k8s_cluster</code>), then explore in Discover</li>
<li>Existing Prometheus or agent pipelines that already write pod and node metrics to <code>metrics-*</code></li>
</ol>
<p>The migration CLI accepts <code>--field-profile otel</code> to map Datadog tags such as <code>pod_name</code>, <code>kube_namespace</code>, and <code>kube_cluster_name</code> to OpenTelemetry fields like <code>kubernetes.pod.name</code> and <code>kubernetes.namespace</code>. If panels are empty after migration, verify field mapping and the selected time range before you change translator settings.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d1b8627fecca77c/6a85cd129a32f11916a7e026/metrics-exploration.jpg" alt="Kubernetes metrics exploration in Discover with live CPU and memory charts after OpenTelemetry ingest" /></p>
<h2 id="runthedatadogdashboardmigrationcli">Run the Datadog dashboard migration CLI</h2>
<p>Export the Datadog dashboard JSON from the UI, or copy the sample <code>kubernetes.json</code> from <code>infra/datadog/dashboards/integrations/</code> in the migration repo. Place files in a directory such as <code>./datadog_k8s_exports/</code>.</p>
<p>Run the migration from that directory:</p>
<pre><code>datadog-migrate \
  --source files \
  --input-dir ./datadog_k8s_exports \
  --output-dir ./migration_output \
  --assets all \
  --field-profile otel \
  --data-view "metrics-*" \
  --logs-index "logs-*" \
  --upload \
  --kibana-url "$KIBANA_ENDPOINT" \
  --kibana-api-key "$KEY" \
  --ensure-data-views \
  --create-alert-rules \
  --validate \
  --es-url "$ELASTICSEARCH_ENDPOINT" \
  --es-api-key "$KEY"
</code></pre>
<p>These flags matter for Kubernetes boards:</p>
<ul>
<li><code>--field-profile otel</code> maps Datadog Kubernetes fields to OpenTelemetry field names in Elasticsearch</li>
<li><code>--assets all</code> includes dashboards and Datadog monitor definitions when present</li>
<li><code>--validate</code> runs emitted ES|QL against your cluster before upload</li>
<li><code>--create-alert-rules</code> creates Kibana rules in a disabled state</li>
</ul>
<p>The unified CLI performs the same work:</p>
<pre><code>obs-migrate migrate \
  --source datadog \
  --input-mode files \
  --input-dir ./datadog_k8s_exports \
  --output-dir ./migration_output \
  --assets all \
  --field-profile otel \
  --data-view "metrics-*" \
  --logs-index "logs-*" \
  --validate \
  --es-url "$ELASTICSEARCH_ENDPOINT" \
  --es-api-key "$KEY" \
  --kibana-url "$KIBANA_ENDPOINT" \
  --kibana-api-key "$KEY" \
  --upload \
  --create-alert-rules
</code></pre>
<p>To fetch a dashboard from Datadog directly:</p>
<pre><code>datadog-migrate \
  --source api \
  --env-file datadog_creds.env \
  --dashboard-ids YOUR_DASHBOARD_ID \
  --output-dir ./migration_output \
  --assets all \
  --field-profile otel \
  --data-view "metrics-*" \
  --validate \
  --es-url "$ELASTICSEARCH_ENDPOINT" \
  --es-api-key "$KEY" \
  --kibana-url "$KIBANA_ENDPOINT" \
  --kibana-api-key "$KEY" \
  --upload
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb0d737676b7d2089/6a85cd1543c0b782232f065e/migration-flow.png" alt="End-to-end Observability Migration Platform flow from Datadog extract through translate, validate, compile, and upload to Kibana" /></p>
<h2 id="validatethemigrateddatadogdashboardinkibana">Validate the migrated Datadog dashboard in Kibana</h2>
<p>Open Kibana → <strong>Dashboards</strong> and locate the migrated <strong>Kubernetes - Overview</strong> board. Confirm that cluster and namespace pod counts, CPU and memory series, pod phase panels, CrashLoopBackOff widgets, and deployment replica charts return data for your selected time range.</p>
<p>If you migrated monitors, open <strong>Observability → Rules</strong>. Imported rules remain disabled until you enable them after reviewing thresholds.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltab0ca21249e6b216/6a85cd17078290f6ba3217a4/kibana-k8s-overview.jpg" alt="Kubernetes overview dashboard in Kibana with cluster and node CPU, memory, and readiness views after Datadog migration" /></p>
<p>The CLI also writes local artifacts under <code>./migration_output/</code>:</p>
<ul>
<li><code>dashboards/yaml/</code> contains the translated dashboard definition.</li>
<li><code>dashboards/migration_report.json</code> lists panels that translated automatically and panels flagged for manual review.</li>
<li><code>alerts/</code> contains monitor translations when monitors were included in the export.</li>
</ul>
<h2 id="handlemanualreviewpanels">Handle manual-review panels</h2>
<p>Some Datadog widget types do not translate on the first pass. Exotic formulas, log-only panels, and unsupported widgets appear as manual-review entries in the migration report rather than as silently broken charts.</p>
<p>| Result | Recommended action |
| --- | --- |
| Panel returns data | Accept the translation and continue |
| Panel is empty | Confirm metric names and <code>data_stream.dataset</code> values in <code>metrics-*</code>, then widen or shift the time range |
| Manual-review marker | Open the original Datadog query and simplify or redesign the panel |
| Monitor never fires | Confirm the rule is enabled and thresholds match your environment |</p>
<p>Datadog coverage is narrower than Grafana in some areas. Read the migration report before you commit to full parity with leadership. The platform prefers conservative failures over uploading panels that look correct but query the wrong fields.</p>
<h2 id="relateddatadogandgrafanamigrationguides">Related Datadog and Grafana migration guides</h2>
<p>For the Grafana PromQL version of this workflow, see <a href="https://www.elastic.co/observability-labs/blog/grafana-elastic-kubernetes-dashboard-migration">Migrate your Grafana Kubernetes dashboard to Elastic Observability</a>. For platform-level context, see <a href="https://www.elastic.co/observability-labs/blog/migrate-datadog-grafana-dashboards-alerts-to-kibana">Migrating Datadog and Grafana dashboards and alerts to Kibana</a>. Review <a href="https://github.com/elastic/observability-migration-platform/blob/main/docs/known-limitations.md">known limitations</a> before you migrate every production folder.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/datadog-kubernetes-dashboard-migration</link>
    <guid isPermaLink="false">datadog-kubernetes-dashboard-migration</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Data Management]]></category>
    <dc:creator><![CDATA[Peter Simkins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb63e39bf3a11e972/6a85cd1a9bf994ca880a05af/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Thu, 23 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Migrate your Grafana Kubernetes dashboard to Elastic Observability: same PromQL, 30x faster queries]]></title>
    <description><![CDATA[Take a real Grafana Kubernetes dashboard covering pod CPU, memory, node pressure, and restart counts, then migrate it into Elastic Observability with native PromQL in under an hour.]]></description>
    <content:encoded><![CDATA[<p>Elasticsearch now runs PromQL natively. Migrate a Grafana <strong>Kubernetes / Views / Global</strong> dashboard into <a href="https://www.elastic.co/docs/solutions/observability">Elastic Observability</a> with the <a href="https://github.com/elastic/observability-migration-platform">Observability Migration Platform</a>. The sample board covers pod CPU, working set memory, throttling signals, and container restart counts. With Kubernetes metrics already in Elasticsearch, the translation, validation, and upload steps typically fit in under an hour.</p>
<p>The migration tool keeps panel queries in PromQL instead of rewriting them into a new dialect, validates them when you pass <code>--validate</code>, and uploads compiled dashboards to Kibana. You still review the migration report and enable alerts on your schedule.</p>
<h2 id="samplegrafanakubernetesdashboardusedinthismigration">Sample Grafana Kubernetes dashboard used in this migration</h2>
<p>The walkthrough uses <strong>Kubernetes / Views / Global</strong>, a community-style Grafana dashboard in the migration repository. It includes the signals operators check during an incident: namespace CPU and memory, throttling pressure, and restart counts.</p>
<p>Below are representative PromQL queries from the source dashboard:</p>
<pre><code># Pod / container CPU by namespace
sum(rate(container_cpu_usage_seconds_total{image!="", cluster="$cluster"}[$__rate_interval])) by (namespace)
</code></pre>
<pre><code># Memory working set by namespace
sum(container_memory_working_set_bytes{image!="", cluster="$cluster"}) by (namespace)
</code></pre>
<pre><code># Node / CPU pressure style signal: throttled seconds
sum(rate(container_cpu_cfs_throttled_seconds_total{image!="", cluster="$cluster"}[$__rate_interval])) by (namespace) &gt; 0
</code></pre>
<pre><code># Container restart counts
sum(increase(kube_pod_container_status_restarts_total{cluster="$cluster"}[$__rate_interval])) by (namespace) &gt; 0
</code></pre>
<p>If this board translates cleanly, most production Grafana Kubernetes folders are worth testing with the same workflow.</p>
<h2 id="whygrafanatoelasticmigrationisfasternow">Why Grafana-to-Elastic migration is faster now</h2>
<p>The migration platform automates the query translation and panel rebuilds that used to dominate Grafana moves. Native <a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write</a> and <a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">PromQL in Kibana</a> let you keep the dialect your on-call team already uses. The <code>--native-promql</code> flag passes PromQL through unchanged. See <a href="https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-faster-cheaper-datadog">Elasticsearch as a metrics engine</a> for storage and query context.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>You need an <a href="https://www.elastic.co/docs/solutions/observability/get-started">Elastic Observability Serverless</a> project, a <a href="https://www.elastic.co/docs/deploy-manage/api-keys/serverless-project-api-keys">project API key</a>, and the migration CLI installed from the <a href="https://github.com/elastic/observability-migration-platform">observability-migration-platform</a> repository.</p>
<p>Export your endpoints and API key:</p>
<pre><code>export ELASTICSEARCH_ENDPOINT="https://YOUR_ES_ENDPOINT"
export KIBANA_ENDPOINT="https://YOUR_KIBANA_ENDPOINT"
export KEY="YOUR_API_KEY"
</code></pre>
<p>Install the CLI and confirm the toolchain:</p>
<pre><code>python3 -m venv .venv
.venv/bin/pip install ".[all]"
.venv/bin/obs-migrate doctor
</code></pre>
<p>The <code>doctor</code> command checks compile and lint dependencies. Resolve any errors before you migrate production dashboards. Pin a release tag if you plan to run this in CI.</p>
<h2 id="howdoyougetkubernetesmetricsintoelasticsearch">How do you get Kubernetes metrics into Elasticsearch?</h2>
<p>Empty panels after upload usually mean Elasticsearch does not yet have the series the PromQL references. Make sure to confirm ingest before you run the migration.</p>
<p>There are two common paths to do so:</p>
<ol>
<li><a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Prometheus Remote Write into Elasticsearch</a> from kube-state-metrics, cAdvisor or kubelet metrics, and node exporters.</li>
<li>OpenTelemetry into managed OTLP for Kubernetes receivers, then explore in Discover.</li>
</ol>
<p>Test with a query such as <code>sum(rate(container_cpu_usage_seconds_total[5m])) by (namespace)</code> against Elastic. If that returns data, continue. If it does not, fix ingest first.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0e2a3acc12c55296/6a85cd31f61d6e81da9c2b5f/metrics-exploration.jpg" alt="Kubernetes metrics exploration in Discover with live CPU and memory charts" /></p>
<h2 id="runthegrafanadashboardmigrationcli">Run the Grafana dashboard migration CLI</h2>
<p>Export your Grafana dashboard JSON, or copy the sample <code>k8s-views-global.json</code> from <code>infra/grafana/dashboards/</code> in the migration repo. Place files in a directory such as <code>./grafana_k8s_exports/</code>.</p>
<p>Run the migration from that directory:</p>
<pre><code>grafana-migrate \
  --source files \
  --input-dir ./grafana_k8s_exports \
  --output-dir ./migration_output \
  --assets all \
  --native-promql \
  --data-view "metrics-*" \
  --esql-index "metrics-*" \
  --upload \
  --kibana-url "$KIBANA_ENDPOINT" \
  --kibana-api-key "$KEY" \
  --ensure-data-views \
  --create-alert-rules \
  --validate \
  --es-url "$ELASTICSEARCH_ENDPOINT" \
  --es-api-key "$KEY"
</code></pre>
<p>These flags matter for Kubernetes boards:</p>
<ul>
<li><code>--native-promql</code> keeps pod CPU, memory, throttling, and restart queries in PromQL</li>
<li><code>--assets all</code> includes dashboards and Grafana PromQL alert definitions when present</li>
<li><code>--validate</code> runs emitted queries against Elasticsearch before upload</li>
<li><code>--create-alert-rules</code> creates Kibana rules in a disabled state</li>
</ul>
<p>The unified CLI performs the same work:</p>
<pre><code>obs-migrate migrate \
  --source grafana \
  --input-mode files \
  --input-dir ./grafana_k8s_exports \
  --output-dir ./migration_output \
  --assets all \
  --native-promql \
  --data-view "metrics-*" \
  --validate \
  --es-url "$ELASTICSEARCH_ENDPOINT" \
  --es-api-key "$KEY" \
  --kibana-url "$KIBANA_ENDPOINT" \
  --kibana-api-key "$KEY" \
  --upload \
  --create-alert-rules
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb2b8eab23dc07def/6a85cd349d2b71883df939e6/migration-flow.png" alt="End-to-end Observability Migration Platform flow from Grafana extract through translate, validate, compile, and upload to Kibana" /></p>
<h2 id="validatethemigratedgrafanadashboardinkibana">Validate the migrated Grafana dashboard in Kibana</h2>
<p>Open Kibana → <strong>Dashboards</strong> and locate <strong>Kubernetes / Views / Global</strong>. Confirm that namespace or pod CPU utilization, memory working set panels, throttling or pressure widgets, and restart charts return data for your selected time range.</p>
<p>If you migrated alerts, open <strong>Observability → Rules</strong>. Imported rules remain disabled until you enable them after reviewing thresholds.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt43d391e3d324bed2/6a85cd37501a858109fbb37b/kibana-k8s-overview.jpg" alt="Kubernetes overview dashboard in Kibana with cluster and node CPU, memory, and readiness views after Grafana migration" /></p>
<p>The CLI also writes local artifacts under <code>./migration_output/</code>:</p>
<ul>
<li><code>dashboards/yaml/</code> contains the translated dashboard definition.</li>
<li><code>dashboards/migration_report.json</code> lists panels that translated automatically and panels flagged for manual review.</li>
<li><code>alerts/</code> contains alert translations when alert definitions were included in the export.</li>
</ul>
<h2 id="whatdoyoudowhengrafanapanelsdontmigrateautomatically">What do you do when Grafana panels don't migrate automatically?</h2>
<p>The Observability Migration Platform flags PromQL expressions that do not translate automatically. Hard joins, unusual arithmetic, and a few Alertmanager-era edge cases appear as manual-review entries in the migration report rather than as silently broken charts.</p>
<p>| Result | Recommended action |
| --- | --- |
| Panel returns data | Accept the translation and continue |
| Panel is empty | Confirm metric names exist in <code>metrics-*</code>, then widen or shift the time range |
| Manual-review marker | Open the original PromQL and simplify or redesign the panel |
| Alert never fires | Confirm the rule is enabled and thresholds match your environment |</p>
<p>This path migrates Grafana PromQL dashboards and Grafana unified PromQL alert definitions into Kibana. It does not ingest a raw <code>alertmanager.yml</code>. The goal is to keep the PromQL your pager already trusts instead of rebuilding the Kubernetes board from zero.</p>
<h2 id="relatedguides">Related guides</h2>
<p>For platform-level context, see <a href="https://www.elastic.co/observability-labs/blog/migrate-datadog-grafana-dashboards-alerts-to-kibana">Migrating Datadog and Grafana dashboards and alerts to Kibana</a>. Review <a href="https://github.com/elastic/observability-migration-platform/blob/main/docs/known-limitations.md">known limitations</a> before you migrate every production folder.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/grafana-elastic-kubernetes-dashboard-migration</link>
    <guid isPermaLink="false">grafana-elastic-kubernetes-dashboard-migration</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Peter Simkins]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb0d0d5c14499f847/6a85cd3a331d7a6951c317f1/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 22 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Prometheus metrics in Elastic Observability: your PromQL runs unchanged]]></title>
    <description><![CDATA[Point Prometheus from your Kubernetes cluster at Elastic Observability with one config block. PromQL runs unchanged, keep your PromQL no cardinality billing.]]></description>
    <content:encoded><![CDATA[<p>It is 2:14 AM and an alert fires on your Kubernetes cluster. You open Grafana for the memory graph, then Loki for the container logs, then your APM tool to check whether the upstream service was already degrading. Three tabs and eleven minutes later you have a hypothesis, and the label you needed to confirm it was dropped last quarter to keep the metrics bill down.</p>
<p>Elasticsearch now stores Prometheus metrics in the same columnar backend as your logs and traces, at 3.75 bytes per datapoint, with no custom-metric penalty. The graph, the logs, and the traces answer to one query language.</p>
<p>Using an existing Kubernetes cluster (AWS EKS in this example): point Prometheus at
Elastic with one config block, see every metric render in Discover with no dashboard to
build, run most of your existing PromQL unchanged, find where ES|QL takes you past what
PromQL can express, and finish by reading your logs with the same query language.</p>
<p>Nothing about your collection changes. Your scrape configs, relabeling rules, and service discovery carry over as-is. Most of your PromQL comes with you too — see the coverage note in Step 5 for the gaps.</p>
<h2 id="step1getaprometheusendpointandanapikeyfromelasticobservability">Step 1: get a Prometheus endpoint and an API key from Elastic Observability</h2>
<p>You need Elastic Cloud Serverless or Elastic Cloud Hosted. Both expose the Prometheus endpoints with no configuration.</p>
<p><strong>Serverless</strong> is the fastest start. Sign in at <a href="https://cloud.elastic.co">cloud.elastic.co</a> and create an Observability project. There is nothing to size and nothing to provision.</p>
<p><strong>Elastic Cloud Hosted</strong> works the same way for everything in this post, and is the right choice when you need a specific stack version, a specific region topology, or the deployment-level controls that come with a managed cluster.</p>
<h3 id="findtheprometheusendpointandcreateanapikeyintheui">Find the Prometheus endpoint and create an API key in the UI</h3>
<p>For Prometheus endpoint and the API key go to Kibana, click <strong>Add data</strong> in the left nav.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt93ddb01d86974daf/6a859a888c29449904b8857d/add-data-page.png" alt="Add data page in Elastic Observability" /></p>
<p>For Prometheus, scroll to <strong>Connect directly to the endpoint</strong> at the bottom and select the <strong>Prometheus</strong> tab.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca384e5a9f888d4c/6a859a8b33f244fbc549ea21/prometheus-endpoint.png" alt="The &quot;Connect directly to the endpoint&quot; panel with the Prometheus tab selected" /></p>
<p>Two things to copy:</p>
<p><strong>The endpoint.</strong> It looks like <code>https://my-observability-project-xxx.ingest.us-west-2.aws.elastic.cloud</code>. Note the <code>.ingest.</code> host. This is not the same host as your Elasticsearch search endpoint or your Kibana URL.</p>
<p><strong>The API key.</strong> Click <strong>Create key</strong>. Copy the value before you close the dialog, because it is not retrievable afterward. </p>
<p>If you want to scope it by hand instead, <strong>Open in API keys</strong> takes you to the full editor, and the minimum privilege for metrics ingest is:</p>
<pre><code>{
  "ingest": {
    "indices": [
      {
        "names": ["metrics-*"],
        "privileges": ["auto_configure", "create_doc"]
      }
    ]
  }
}
</code></pre>
<p>Keep both values. Every remaining step uses them.</p>
<h2 id="step2makesureprometheusisscrapingyourkubernetescluster">Step 2: make sure Prometheus is scraping your Kubernetes cluster</h2>
<p>Prometheus should already be scraping your cluster.</p>
<p>If it is not, the shortest path is the <code>kube-prometheus-stack</code> Helm chart, which installs the Prometheus Operator, Prometheus itself, <code>kube-state-metrics</code>, and <code>node-exporter</code> in one command:</p>
<pre><code>helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
helm repo update

helm install prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring --create-namespace
</code></pre>
<p>That gives you the metrics you see for the rest of this post: </p>
<ul>
<li>cAdvisor container metrics (<code>container_cpu_usage_seconds_total</code>, <code>container_memory_working_set_bytes</code>)</li>
<li>kube-state-metrics cluster objects (<code>kube_deployment_spec_replicas</code>, <code>kube_daemonset_status_number_ready</code>).</li>
</ul>
<h2 id="step3configureprometheusremotewritetotheprometheusendpointinstep1">Step 3: configure Prometheus remote write to the Prometheus endpoint in Step 1</h2>
<p>Elasticsearch implements the Prometheus Remote Write protocol natively. There is no adapter, no sidecar, and no translation layer. You add one block and the data flows on the next scrape interval.</p>
<h3 id="ifyouruntheprometheusoperator">If you run the Prometheus Operator</h3>
<p>The Operator does not read a <code>prometheus.yml</code> you write by hand. It generates one from the <code>Prometheus</code> custom resource, and <code>authorization.credentials</code> there is a reference to a Kubernetes Secret, not an inline value. Create the secret first:</p>
<pre><code>kubectl create secret generic elastic-prometheus \
  --namespace monitoring \
  --from-literal=api_key='YOUR_API_KEY'
</code></pre>
<p>Then reference it from <code>values.yaml</code>:</p>
<pre><code>prometheus:
  prometheusSpec:
    remoteWrite:
      - url: "https://my-observability-project-xxxx.ingest.us-west-2.aws.elastic.cloud:443/api/v1/write"
        authorization:
          type: ApiKey
          credentials:
            name: elastic-prometheus
            key: YOUR_API_KEY
</code></pre>
<p>And apply it:</p>
<pre><code>helm upgrade prometheus prometheus-community/kube-prometheus-stack \
  --namespace monitoring \
  -f values.yaml
</code></pre>
<h3 id="configureprometheusremotewritewithprometheusyml">Configure Prometheus remote write with prometheus.yml</h3>
<p>Same thing, in <code>prometheus.yml</code>:</p>
<pre><code>remote_write:
  - url: "https://YOUR_ES_ENDPOINT/_prometheus/metrics/node/eks/api/v1/write"
    authorization:
      type: ApiKey
      credentials: YOUR_API_KEY
</code></pre>
<h3 id="ifyourungrafanaalloyussthefollowingconfiguration">If you run Grafana Alloy uss the following configuration</h3>
<pre><code>prometheus.remote_write "elasticsearch" {
  endpoint {
    url = "https://YOUR_ES_ENDPOINT/_prometheus/metrics/node/eks/api/v1/write"
    headers = {"Authorization" = "ApiKey YOUR_API_KEY"}
  }
}
</code></pre>
<h3 id="howtheremotewriteurlmapstoelasticsearchdatastreams">How the remote write URL maps to Elasticsearch data streams</h3>
<p>You do not name an index anywhere. There is no index field in the payload and no data stream in your <code>remote_write</code> config. Elasticsearch derives the target data stream from the write path and creates it on the first sample. The two path segments after <code>/metrics/</code> are the dataset and the namespace:</p>
<p>| URL | Data stream |
|---|---|
| <code>/_prometheus/api/v1/write</code> | <code>metrics-generic.prometheus-default</code> |
| <code>/_prometheus/metrics/{dataset}/api/v1/write</code> | <code>metrics-{dataset}.prometheus-default</code> |
| <code>/_prometheus/metrics/{dataset}/{namespace}/api/v1/write</code> | <code>metrics-{dataset}.prometheus-{namespace}</code> |</p>
<p>The examples above use <code>/metrics/node/eks/</code>, which writes to <code>metrics-node.prometheus-eks</code>. That is the data stream you will see in Discover in the next step. Use dataset and namespace to separate production from staging, or to give each cluster and each team a data stream with its own retention and downsampling policy.</p>
<p>If you would rather keep a bare <code>/api/v1/write</code> URL, you can route per time series instead: attach <code>data_stream_dataset</code> and <code>data_stream_namespace</code> labels to the series, and they take precedence over the URL path. These two are control fields, so they route the document without being stored in its <code>labels</code> object.</p>
<p>Elasticsearch installs the index template for <code>metrics-*.prometheus-*</code> itself. You do not create templates or mappings.</p>
<h3 id="whatprometheusmetricslooklikeinelasticobservability">What Prometheus metrics look like in Elastic Observability</h3>
<p>Every Prometheus sample becomes a document. Labels become keyword fields that serve as time series dimensions. The value goes under <code>metrics.&lt;metric_name&gt;</code>:</p>
<pre><code>{
  "@timestamp": "2026-07-02T10:30:00.000Z",
  "data_stream": {
    "type": "metrics",
    "dataset": "node.prometheus",
    "namespace": "eks"
  },
  "labels": {
    "__name__": "container_memory_working_set_bytes",
    "pod": "checkout-7d9f6c4b8-x2kqp",
    "namespace": "oteldemo",
    "container": "checkout",
    "node": "ip-10-0-3-14.us-west-2.compute.internal"
  },
  "metrics": {
    "container_memory_working_set_bytes": 36700160
  }
}
</code></pre>
<p><strong>The one gotcha to know now.</strong> Elasticsearch infers whether a metric is a counter or a gauge from its name. Names ending in <code>_total</code>, <code>_sum</code>, <code>_count</code>, or <code>_bucket</code> are counters. Everything else is a gauge. That inference is correct for <code>container_cpu_usage_seconds_total</code> and correct for <code>container_memory_working_set_bytes</code>. It is wrong for any metric in your estate that does not follow Prometheus naming convention, and a misclassified metric gets rejected by the function that should accept it: <code>RATE(my_metric::counter)</code> works on counters only. Step 6 shows how to override the inference.</p>
<p><strong>Current limits.</strong> Remote Write v1 only. Classic histograms and summaries are supported through their <code>_bucket</code>, <code>_sum</code>, and <code>_count</code> series, each mapped to the right metric type. Native (sparse) histograms and exemplars arrive with Remote Write v2, which is on the roadmap. Staleness markers are not stored or respected, and non-finite values (NaN, Infinity) are dropped silently. See the <a href="https://www.elastic.co/docs/manage-data/data-store/data-streams/tsds-ingest-prometheus-remote-write">Prometheus remote write ingest docs</a> for the full list.</p>
<h2 id="step4verifyprometheusmetricsareflowingintoelasticsearch">Step 4: verify Prometheus metrics are flowing into Elasticsearch</h2>
<p>Do not go build a dashboard. Confirm the pipeline first, and Elastic makes that a single command.</p>
<p>In Kibana, open <strong>Discover</strong>, switch the query editor to ES|QL, and type the name of the data stream you just wrote to:</p>
<pre><code>TS metrics-node.prometheus-eks
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6656243127573f59/6a859a8e43c0b719e12efb15/discover-prometheus-metrics.png" alt="ES|QL query &quot;TS metrics-node.prometheus-eks&quot;" /></p>
<p>That is the whole query. Discover reads the data stream, finds every metric in it, and renders each one as its own time series chart. Fifty metrics, fifty charts, no dashboard to build and no query to write per metric. It reads the metric type and charts each one correctly: gauges as averages, counters as rates, histograms as p95 distributions.</p>
<p>You can also get here without typing anything. <strong>Observability</strong> → <strong>Streams</strong> lists every data stream in the cluster. A <strong>Time series</strong> badge means it is a time series data stream. Click <strong>View in Discover</strong> and the <code>TS</code> query is filled in for you.</p>
<p>This is your ingest health check. Three things to look for.</p>
<ul>
<li><strong>Data is flowing.</strong> Recent, continuous values. Not gaps, and not a line that stops an hour ago.</li>
<li><strong>Values are plausible.</strong> Memory in the tens of megabytes for a small container. CPU as a fraction of a core. Network bytes tracking real traffic.</li>
<li><strong>Coverage is what you expected.</strong> If <code>kube_pod_container_status_restarts_total</code> is missing, your kube-state-metrics scrape config is wrong, and you want to know that now rather than when you are building an alert on it.</li>
</ul>
<p>Widen the time picker before you conclude anything is broken. A 15-minute window over a quiet period makes healthy data look flat.</p>
<p>To list what actually has data rather than what the mapping declares:</p>
<pre><code>TS metrics-node.prometheus-eks | METRICS_INFO | SORT metric_name
</code></pre>
<p>The <strong>No dimensions selected</strong> control above the charts lets you break every chart out by a label: select <code>pod</code> and each chart splits into one series per pod.</p>
<h2 id="step5runpromqlqueriesonprometheusmetricsinelasticobservability">Step 5: run PromQL queries on Prometheus metrics in Elastic Observability</h2>
<p>If your team writes PromQL, keep writing PromQL. <code>PROMQL</code> is a source command in ES|QL, alongside <code>FROM</code> and <code>TS</code>, and it runs anywhere ES|QL runs: Discover, dashboard panels, and alert rules.</p>
<p>It does not run a separate engine. It parses the expression, resolves each function to its ES|QL equivalent, and builds a <code>TS</code> execution plan, so your PromQL gets the same vectorized, parallel execution as native ES|QL.</p>
<p><strong>Current limits.</strong> <code>PROMQL</code> is generally available on Elastic Cloud Serverless and a tech preview on Elastic Stack 9.4, benchmarked at over 80% query coverage against popular Grafana OSS dashboards. The gaps worth knowing before you paste a dashboard in: <code>histogram_quantile</code> is not yet implemented, which matters most because it is how nearly every latency dashboard computes p95; group modifiers (<code>on(...) group_left(...)</code>) and the set operators <code>or</code>, <code>and</code>, and <code>unless</code> are unsupported; and <code>predict_linear</code>, <code>label_join</code>, and <code>label_replace</code> are not yet available. Time buckets also align to fixed calendar boundaries rather than the query start time, so short ranges or large steps can differ slightly from Prometheus. See the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/promql"><code>PROMQL</code> command reference</a> for the current list.</p>
<h3 id="cpuperpodcpuratewithpromql">CPU: per-pod CPU rate with PromQL</h3>
<p>The per-second CPU rate across containers, grouped by pod. This is the first thing you look at when something is hot:</p>
<pre><code>PROMQL sum by (pod) (rate(container_cpu_usage_seconds_total[5m]))
</code></pre>
<p>Broken out by namespace instead, to find which team is burning the cluster:</p>
<pre><code>PROMQL sum by (namespace) (rate(container_cpu_usage_seconds_total[5m]))
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd77344beab424b2b/6a859a914710c6851ad3c0bd/promql-cpu.png" alt="The PromQL CPU query" /></p>
<p>Note what came back: a <code>pod</code> column, a <code>step</code> column, and the value column named after the expression itself. That is a normal ES|QL table, which is the whole point and the thing Step 6 builds on.</p>
<h3 id="memoryworkingsetandrsswithpromql">Memory: working set and RSS with PromQL</h3>
<p>Working set is the number that matters for OOM risk. It is what the kernel counts against the limit, and it is not the same as total allocated memory:</p>
<pre><code>PROMQL sum by (pod) (container_memory_working_set_bytes)
</code></pre>
<p>Resident set, for comparison, when you are trying to tell a real leak from page cache:</p>
<pre><code>PROMQL sum by (pod) (container_memory_rss)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta775eef49c896a49/6a859a94f5f1a066672ebf42/promql-memory.png" alt="The PromQL memory working set query" /></p>
<h3 id="networkreceiveratebypodwithpromql">Network: receive rate by pod with PromQL</h3>
<pre><code>PROMQL sum by (pod) (rate(container_network_receive_bytes_total[5m]))
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt860489ec46eaef6f/6a859a97342d69db5b21a5a4/promql-network.png" alt="PromQL network receive rate by pod running in Discover" /></p>
<h3 id="clusterobjectsreplicacountswithpromql">Cluster objects: replica counts with PromQL</h3>
<p>Deployments that are not running the replica count they declare:</p>
<pre><code>PROMQL kube_deployment_spec_replicas
</code></pre>
<p>One nicety worth calling out: in Prometheus every query needs an explicit <code>start</code>, <code>end</code>, and <code>step</code>. In Kibana you drop all three. The date picker supplies the range and Kibana derives the step, which is why every query above is a single line.</p>
<h3 id="buildakibanadashboardfrompromqlqueries">Build a Kibana dashboard from PromQL queries</h3>
<p>Every query above is a dashboard panel. In Discover, click <strong>Save</strong>, or go to <strong>Dashboards</strong> → <strong>Create</strong> → <strong>Add panel</strong> → <strong>ES|QL</strong> and paste the query in. The date picker drives <code>start</code>, <code>end</code>, and <code>step</code>, so a panel written once works at every zoom level.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdfd4713140505956/6a859a9b27c5cd313e5f68a8/prometheus-dashboard.png" alt="Prometheus Metrics for B10 Cluster" /></p>
<p>Four panels, four one-line PromQL queries, no translation step. If you are coming from Grafana, this is the same dashboard you already have, rebuilt in about five minutes. If you would rather not rebuild it at all, keep Grafana and point its existing Prometheus datasource at Elasticsearch. That is covered at the end of the post.</p>
<h2 id="step6queryprometheusmetricswithesqlbeyondwhatpromqlcanexpress">Step 6: query Prometheus metrics with ES|QL, beyond what PromQL can express</h2>
<p>The <code>PROMQL</code> command in ES|QL compiles to <code>TS</code>. These two queries are equivalent:</p>
<pre><code>PROMQL sum by (pod) (rate(container_cpu_usage_seconds_total[5m]))
</code></pre>
<pre><code>TS metrics-node.prometheus-eks
| WHERE TRANGE(1h)
| STATS SUM(RATE(metrics.container_cpu_usage_seconds_total, 5m)) BY labels.pod, TBUCKET(1m)
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt07ecc21afdaad511/6a859a9e43c0b7c8ee2efb1f/ESQL-CPU-usage-pod.png" alt="The TS form of the CPU query running in the Kibana ES|QL editor in Discover: SUM(RATE(container_cpu_usage_seconds_total, 5m)) by pod, per-pod CPU rate bars across kube-system pods, 176 results in 11ms" /></p>
<p>The second form is where metrics stop being a separate world from logs and traces —
and where the real joins happen.</p>
<h3 id="topnandfiltering">Top-N and filtering</h3>
<p>A <code>TS</code> query returns a normal ES|QL table, so <code>SORT</code>, <code>LIMIT</code>, and <code>WHERE</code> all work downstream. Top-N needs no special function, and no <code>topk</code>. The ten pods using the most memory are a <code>SORT</code> and a <code>LIMIT</code>:</p>
<pre><code>TS metrics-node.prometheus-eks
| WHERE `labels.pod` IS NOT NULL
| STATS mem = SUM(metrics.container_memory_working_set_bytes) BY `labels.pod`
| SORT mem DESC
| LIMIT 10
</code></pre>
<p>Filtering is the same move, a <code>WHERE</code> on the aggregated column. Only the pods over 50 MB:</p>
<pre><code>TS metrics-node.prometheus-eks
| WHERE `labels.pod` IS NOT NULL
| STATS mem = SUM(metrics.container_memory_working_set_bytes) BY `labels.pod`
| WHERE mem &gt; 50000000
| SORT mem DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte74a13a2d1124d56/6a859aa19a32f11279a7d507/pods-over-50ms.png" alt="The top-N memory query running in the Kibana ES|QL editor in Discover" /></p>
<p>You actually can pipe the results of a PROMQL query and post-process with regular ES|QL.</p>
<h3 id="memoryusageagainsttherequest">Memory usage against the request</h3>
<p>A useful question during a memory scare: which pods are using more than they requested, and by how much. That combines two metrics, and ES|QL expresses it in a single pass with filtered aggregations, one <code>MAX</code> per metric:</p>
<pre><code>TS metrics-node.prometheus-eks
| WHERE `labels.pod` IS NOT NULL AND `labels.namespace` IS NOT NULL
| STATS
    used_memory      = MAX(metrics.container_memory_working_set_bytes),
    requested_memory = MAX(metrics.kube_pod_container_resource_requests)
                       WHERE `labels.resource` == "memory"
  BY `labels.pod`, `labels.namespace`, time_bucket = TBUCKET(5 minute)
| EVAL pct_of_request = 100 * used_memory / requested_memory
| WHERE pct_of_request &gt; 80
| SORT pct_of_request DESC
| LIMIT 100
</code></pre>
<p>The <code>WHERE</code> attached to <code>requested_memory</code> is a filtered aggregation: <code>kube_pod_container_resource_requests</code> carries both CPU and memory under a <code>labels.resource</code> dimension, and the filter keeps only the memory rows, so the ratio is memory over memory. Each metric lives in its own documents; the shared <code>BY</code> key lands both aggregates on one row.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt08d2c5ef4cbb4d6f/6a859aa4e2447a2b478b08d1/ESQL-pod-memory-and-requests.png" alt="memory-vs-request query in the Kibana ES|QL" /></p>
<h2 id="step7queryprometheusmetricsandlogstogetherwithesql">Step 7: query Prometheus metrics and logs together with ES|QL</h2>
<p>Over the last few sections we used ES|QL and PromQL to explore metrics. ES|QL reads logs too. Here is a quick query against the OpenTelemetry demo running on this cluster:</p>
<pre><code>FROM logs-*
| WHERE TRANGE(30m) AND kubernetes.pod.name == "checkout-9656cbd88-fsr9v"
| STATS events = COUNT(*) BY log.level, TBUCKET(1m)
| SORT events DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a436e9d7fcaabb7/6a859aa79a32f17f35a7d511/logs-correlation.png" alt="ESQL Logs query" /></p>
<p>Same editor, same date picker you used for metrics. Only the source command changed, from <code>TS</code> to <code>FROM</code>, and now you are reading log volume by level per minute. One query language across metrics and logs, no tab switch and no second tool.</p>
<h2 id="whatsrunninginelasticobservabilitynow">What's running in Elastic Observability now</h2>
<p>Prometheus is writing to Elastic with one config block. Every metric renders in Discover with no dashboard built. Most of your existing PromQL runs unchanged, and ES|QL takes you further: top-N, filtering, and cross-metric ratios you can pipe into the rest of the language. Metrics and logs answer to the same query in the same window.</p>
<p>You did not drop a single label to get here, and you are not being billed for cardinality.</p>
<h2 id="nextstepsgrafanadashboardmigrationandopentelemetry">Next steps: Grafana, dashboard migration, and OpenTelemetry</h2>
<ol>
<li><strong>Keep Grafana if you want it.</strong> Elasticsearch exposes a Prometheus-compatible read API at <code>&lt;endpoint&gt;/_prometheus</code>. Point Grafana's existing Prometheus datasource at it, set <code>httpMethod: GET</code> on the datasource, and your PromQL dashboards keep working. Keep Grafana, replace Mimir.</li>
<li><strong>Migrate your Grafana dashboards.</strong> When you are ready to move off Grafana rather than point it at Elasticsearch, the <a href="https://github.com/elastic/observability-migration-platform">Observability Migration Platform</a> translates Grafana dashboards, panels, and alert rules into Kibana-native equivalents. It is a source-agnostic CLI (<code>obs-migrate</code>) that converts what it can and flags what needs a human, with a migration report showing what translated cleanly and where semantic gaps remain, so nothing is silently dropped. The <a href="https://www.elastic.co/observability-labs/blog/migrate-datadog-grafana-dashboards-alerts-to-kibana">walkthrough</a> covers a Grafana and Datadog migration end to end.</li>
<li><strong>Want to add OpenTelemetry?</strong> Read Part 2 if you are also running OpenTelemetry, or if you would rather collect with an OTel Collector than with Prometheus. Both land in the same store and the same queries read across both.</li>
</ol>
<p><strong>Start a free trial:</strong> <a href="https://cloud.elastic.co/registration">cloud.elastic.co/registration</a>
<strong>Docs:</strong> <a href="https://www.elastic.co/docs/solutions/observability">elastic.co/docs/solutions/observability</a></p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<p><strong>How do I send Prometheus metrics to Elasticsearch?</strong>
Add a <code>remote_write</code> block to your Prometheus configuration pointing to
<code>https://&lt;YOUR_ES_ENDPOINT&gt;/_prometheus/metrics/{dataset}/{namespace}/api/v1/write</code>
with an <code>Authorization: ApiKey &lt;YOUR_KEY&gt;</code> header. Elasticsearch implements the
Prometheus Remote Write v1 protocol natively — no adapter or sidecar required.</p>
<p><strong>Can I run existing PromQL queries in Elasticsearch?</strong>
Most of them, yes. ES|QL includes a <code>PROMQL</code> source command that accepts standard PromQL
expressions, benchmarked at over 80% coverage against popular Grafana OSS dashboards. It
is generally available on Elastic Cloud Serverless and a tech preview on Elastic Stack
9.4. <code>histogram_quantile</code>, <code>predict_linear</code>, <code>label_join</code>, and <code>label_replace</code> are not
yet implemented, and group modifiers and the set operators <code>or</code>, <code>and</code>, and <code>unless</code> are
unsupported.</p>
<p><strong>Does Elasticsearch charge based on Prometheus metric cardinality?</strong>
No. Elasticsearch stores Prometheus metrics at 3.75 bytes per datapoint with no
cardinality-based billing and no custom-metric penalty, regardless of how many unique
label combinations your metrics produce.</p>
<p><strong>How do I route Prometheus metrics to different data streams in Elasticsearch?</strong>
The two path segments after <code>/metrics/</code> in the Remote Write URL set the dataset and
namespace. <code>/metrics/node/eks/api/v1/write</code> writes to <code>metrics-node.prometheus-eks</code>.
You can also route per time series using <code>data_stream_dataset</code> and
<code>data_stream_namespace</code> labels, which take precedence over the URL path.</p>
<p><strong>What Prometheus metric types does Elasticsearch support?</strong>
Elasticsearch supports Remote Write v1, including counters, gauges, classic histograms,
and summaries. Counter vs. gauge classification is inferred from the metric name: names
ending in <code>_total</code>, <code>_sum</code>, <code>_count</code>, or <code>_bucket</code> are counters; everything else is a
gauge. Native histograms and exemplars require Remote Write v2, which is on the roadmap.</p>
<p><strong>Can I query Prometheus metrics and logs together in Elasticsearch?</strong>
Yes. ES|QL reads both time series metrics and logs in the same query editor with the
same date picker. Switch from <code>TS metrics-node.prometheus-eks</code> to <code>FROM logs-*</code> — same
syntax, same window, no second tool.</p>
<h2 id="relatedreading">Related reading</h2>
<ul>
<li><a href="https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-faster-cheaper-datadog">Elasticsearch: best-in-class for logs, now best-in-class for metrics</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/prometheus-remote-write-elasticsearch">Ship Prometheus metrics to Elasticsearch with Remote Write</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Query Prometheus metrics in Elasticsearch with native PromQL support</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/esql-ts-command-querying-metrics">Don't leave metrics on the table: query them with the ES|QL TS command</a></li>
<li><a href="https://www.elastic.co/observability-labs/blog/query-prometheus-metrics-grafana-elasticsearch">Elasticsearch as a backend for Grafana</a></li>
<li><a href="https://www.elastic.co/search-labs/blog/elasticsearch-metrics-columnar-engine">How we rebuilt Elasticsearch as a columnar metrics engine</a></li>
</ul>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/prometheus-metrics-elasticsearch-getting-started</link>
    <guid isPermaLink="false">prometheus-metrics-elasticsearch-getting-started</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[What's New]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e9f1c7205265bf7/6a859aab18249c724c18ec9c/header.png" length="0" type="image/png"/>
    <pubDate>Mon, 20 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Common ES|QL queries for Kubernetes monitoring]]></title>
    <description><![CDATA[Copy-paste ES|QL queries for Elasticsearch that turn memory pressure and error spikes into a five-minute diagnosis.]]></description>
    <content:encoded><![CDATA[<p>This post has nine ES|QL queries for diagnosing Kubernetes problems in Elasticsearch. They cover crash-looping pods, memory pressure before an OOM kill, saturated nodes, and error spikes by namespace. Every query runs against Kubernetes data collected with the <a href="https://www.elastic.co/docs/reference/opentelemetry">Elastic Distributions of OpenTelemetry (EDOT)</a> and pastes into Discover with little to no editing. Turn any of them into a dashboard panel or an alert once you've found the one you need. Jump straight to the query that matches what you're seeing or read through the whole set to get a feel for your cluster.</p>
<h2 id="whatyouneedbeforerunningthesekubernetesesqlqueries">What you need before running these Kubernetes ES|QL queries</h2>
<p>To follow the queries in this article, you need:</p>
<ul>
<li>Elasticsearch 9.2 or later.</li>
<li>EDOT Collectors running in your cluster and shipping data to Elasticsearch,
with the <code>kubeletstats</code>, <code>k8s_cluster</code>, and <code>filelog</code> receivers enabled.</li>
</ul>
<h2 id="whyesqlforkubernetesmonitoring">Why ES|QL for Kubernetes monitoring</h2>
<p>Elasticsearch <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL</a> is a piped query language that lets you start from a data source and then add one operation per line: filter, compute, aggregate, sort. That structure fits investigation work well because you refine a query step by step as you narrow down a problem.</p>
<h2 id="whentousetheesqltscommandforkubernetesmetrics">When to use the ES|QL TS command for Kubernetes metrics</h2>
<p>The <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code></a> command understands time series. This matters because most Kubernetes metrics are counters or gauges sampled over time. Using <code>TS</code> for metrics avoids the common mistake of summing cumulative counters across pods and getting a meaningless number.</p>
<h2 id="edotreceiversthesekubernetesqueriesdependon">EDOT receivers these Kubernetes queries depend on</h2>
<p>The queries below assume EDOT Collectors are running in your cluster and shipping data to Elasticsearch. A typical setup uses:</p>
<ul>
<li>The <code>kubeletstats</code> receiver for pod, container, and node resource metrics.</li>
<li>The <code>k8s_cluster</code> receiver for object state such as pod phase and container restarts.</li>
<li>The <code>filelog</code> receiver with the <code>k8sattributes</code> processor for container logs.</li>
</ul>
<p>Resource attributes follow the <a href="https://opentelemetry.io/docs/specs/semconv/">OpenTelemetry semantic conventions</a>. Pod, namespace, and node identifiers appear as <code>k8s.pod.name</code>, <code>k8s.namespace.name</code>, and <code>k8s.node.name</code>. Metric names are defined by the receiver that emits them, not by the semconv spec itself: <code>k8s.pod.phase</code> comes from the <code>k8s_cluster</code> receiver, while utilization metrics like <code>k8s.container.memory_limit_utilization</code> come from <code>kubeletstats</code>. EDOT preserves all of these names natively in Elasticsearch. The exact fields you have depend on which receivers you enabled, so treat the queries as templates and adjust field names if a metric is missing.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3633a3879fe6bdc6/6a85c83b99083f767840f971/image2.jpg" alt="Kubernetes cluster overview" /></p>
<h2 id="exploringthecluster">Exploring the cluster</h2>
<p>Start broad. Before investigating a specific symptom, it helps to see what the cluster looks like in your data.</p>
<p>This query counts the pods reporting metrics in each namespace over the past hour.</p>
<pre><code>FROM metrics-*
| WHERE @timestamp &gt;= NOW() - 1 hour
| STATS pod_count = COUNT_DISTINCT(k8s.pod.name) BY k8s.namespace.name
| SORT pod_count DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt808ee0ccf116be22/6a85c83df5f1a039cd2ec873/image3.jpg" alt="Pod count by namespace" /></p>
<p><code>COUNT_DISTINCT</code> collapses the many metric samples per pod into a single count per namespace. The result is a quick inventory: which namespaces are busy and whether anything you expected to be running is missing.</p>
<h2 id="findingpodrestartsandcrashloops">Finding pod restarts and crash loops</h2>
<p>Restarts are usually the first signal that something is wrong. The <code>k8s.container.restarts</code> metric is a gauge that reports the current restart count for each container.</p>
<p>This query surfaces the containers that have restarted the most in the last 24 hours.</p>
<pre><code>FROM metrics-*
| WHERE @timestamp &gt;= NOW() - 24 hours AND k8s.container.restarts IS NOT NULL
| STATS restarts = MAX(k8s.container.restarts)
    BY k8s.namespace.name, k8s.pod.name, k8s.container.name
| WHERE restarts &gt; 0
| SORT restarts DESC
| LIMIT 20
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1a4a354576a92c07/6a85c840982926cb3e583888/image4.jpg" alt="Container restarts leaderboard" /></p>
<p><code>MAX</code> takes the highest restart count seen in the window, which reflects the latest value of the gauge. A container with a high and climbing restart count is the textbook sign of a crash loop. Once you have the pod name, you can pivot straight to its logs with the queries further down.</p>
<h2 id="spottingpodsthatarenotrunning">Spotting pods that are not running</h2>
<p>A restart count tells you a pod recovered. The pod phase tells you whether it is healthy right now. The <code>k8s.pod.phase</code> metric encodes the phase as a number:</p>
<p>| Value | Phase |
| :---- | :---- |
| 1 | Pending |
| 2 | Running |
| 3 | Succeeded |
| 4 | Failed |
| 5 | Unknown |</p>
<p>This query uses <code>TS</code> to read the latest phase per pod and keeps anything that is not Running.</p>
<pre><code>TS metrics-*
| WHERE TRANGE(15m)
| STATS phase = MAX(LAST_OVER_TIME(k8s.pod.phase))
    BY k8s.namespace.name, k8s.pod.name
| WHERE phase != 2
| EVAL phase_name = CASE(
    phase == 1, "Pending",
    phase == 3, "Succeeded",
    phase == 4, "Failed",
    phase == 5, "Unknown",
    "Other")
| SORT phase_name
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc48536c7b187dae4/6a85c84333f244bcc149f492/image5.jpg" alt="Non-running pods" /></p>
<p><code>LAST_OVER_TIME</code> picks the most recent sample for each pod's time series, so you compare the current state rather than an average. Pods stuck in <code>Pending</code> often point to scheduling problems, such as insufficient CPU or memory on the nodes. Pods in <code>Failed</code> or <code>Unknown</code> are worth an immediate look.</p>
<h2 id="catchingmemorypressurebeforetheoomkill">Catching memory pressure before the OOM kill</h2>
<p>Out-of-memory kills are one of the most common Kubernetes failures, and they are easier to prevent than to debug after the fact. When you enable limit metadata on the <code>kubeletstats</code> receiver, EDOT reports <code>k8s.container.memory_limit_utilization</code> as a fraction between 0 and 1 of the container's memory limit.</p>
<p>This query finds containers that ran close to their limit in the last hour.</p>
<pre><code>TS metrics-*
| WHERE TRANGE(1h)
| STATS peak_mem_pct = MAX(MAX_OVER_TIME(k8s.container.memory_limit_utilization))
    BY k8s.namespace.name, k8s.pod.name, k8s.container.name
| EVAL peak_mem_pct = ROUND(peak_mem_pct * 100, 1)
| WHERE peak_mem_pct &gt; 85
| SORT peak_mem_pct DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt525f72c7f235f1d5/6a85c84680984ca3b5668f7a/image1.jpg" alt="Memory pressure near limit" /></p>
<p><code>MAX_OVER_TIME</code> finds the peak within each container's series, and the outer <code>MAX</code> keeps that peak per container. A container that repeatedly touches 95% or higher is a strong candidate for the next OOM kill. Pair this with the restart query above: a container with both a rising restart count and high memory utilization was very likely OOM killed.</p>
<h2 id="trackingcpuusageandnodepressure">Tracking CPU usage and node pressure</h2>
<p>CPU problems show up as throttling and slow response times rather than crashes. The <code>k8s.pod.cpu.node.utilization</code> metric reports each pod's CPU use as a fraction of total node capacity.</p>
<p>This query charts the busiest pods over the last hour in five-minute buckets.</p>
<pre><code>TS metrics-*
  | WHERE TRANGE(1h)
  | STATS avg_cpu = AVG(AVG_OVER_TIME(k8s.pod.cpu.node.utilization))
      BY k8s.pod.name, TBUCKET(5m)
  | SORT avg_cpu DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c8cf93267186f11/6a85c84999083fa62340f975/image6.jpg" alt="CPU usage by pod over time" /></p>
<p><code>AVG_OVER_TIME</code> averages the samples inside each pod's series for the bucket, and the outer <code>AVG</code> combines series that share a pod name. <code>TBUCKET(5m)</code> produces one point every five minutes, which renders cleanly as a time series chart.</p>
<p>To check whether the nodes themselves are saturated, query node utilization directly.</p>
<pre><code>TS metrics-*
  | WHERE TRANGE(1h)
  | STATS cpu = AVG(AVG_OVER_TIME(k8s.node.cpu.usage)),
          mem = AVG(LAST_OVER_TIME(k8s.node.memory.usage))
      BY k8s.node.name, TBUCKET(5m)
  | SORT cpu DESC
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcaac108bf0586043/6a85c84c501a852e4efbb2b4/image7.jpg" alt="Node CPU and memory utilization" /></p>
<p>A node sitting near full CPU explains throttled pods across many namespaces at once, which is easy to misread as an application bug when you only look at a single pod.</p>
<h2 id="investigatingcontainerlogs">Investigating container logs</h2>
<p>Once metrics point you at a pod, logs explain what it was doing. EDOT stores the log message in <code>body.text</code> and the level in <code>severity_text</code>, alongside the same <code>k8s.*</code> fields as the metrics.</p>
<p>This query ranks namespaces and pods by error volume in the last hour.</p>
<pre><code>FROM logs-*
| WHERE @timestamp &gt;= NOW() - 1 hour
  AND severity_text IN ("ERROR", "FATAL")
| STATS errors = COUNT(*)
    BY k8s.namespace.name, k8s.pod.name
| SORT errors DESC
| LIMIT 20
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt042eff9ad8d18180/6a85c84ee2447a79218b13a4/image8.jpg" alt="Error count by pod" /></p>
<p>Counting by pod tells you whether errors are concentrated in one workload or spread across the cluster. A single noisy pod and a cluster-wide spike call for very different responses.</p>
<p>To read what a specific pod is logging, filter by pod name and search the message text.</p>
<pre><code>FROM logs-*
| WHERE @timestamp &gt;= NOW() - 1 hour
  AND k8s.pod.name == "checkout-&lt;your-hash&gt;"
  AND body.text LIKE "*timeout*"
| KEEP @timestamp, severity_text, body.text
| SORT @timestamp DESC
| LIMIT 50
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt158bdcfec830c991/6a85c85133f2443e7649f49a/image9.jpg" alt="Pod log messages filtered by keyword" /></p>
<p><code>LIKE "*timeout*"</code> does a simple wildcard match on the message. For full-text relevance instead of wildcards, swap it for <a href="https://www.elastic.co/docs/reference/query-languages/esql/functions-operators/search-functions"><code>MATCH(body.text, "timeout")</code></a>.</p>
<h2 id="chainingesqlqueriestoinvestigateakubernetesincident">Chaining ES|QL queries to investigate a Kubernetes incident</h2>
<p>Real Kubernetes investigations chain multiple ES|QL queries together instead of running one in isolation.</p>
<p>A useful loop looks like this:</p>
<ol>
<li>Count errors by pod to find the noisy workload.</li>
<li>Check that pod's restart count and memory utilization to see if it is crashing or starved.</li>
<li>Read its recent logs to find the specific failure.</li>
</ol>
<p>Because every query uses the same <code>k8s.namespace.name</code> and <code>k8s.pod.name</code> fields, you can carry a pod name straight from one query to the next. The same fields let you build a single dashboard where a metrics panel and a logs panel filter together as you click through namespaces.</p>
<h2 id="turningqueriesintoalertsanddashboards">Turning queries into alerts and dashboards</h2>
<p>Any ES|QL query in this post that produces an aggregated value can back an alert, not just support ad hoc investigation.</p>
<p>For example, the restart query becomes an alert when you keep only containers above a threshold and trigger on a non-empty result.</p>
<pre><code>FROM metrics-*
| WHERE @timestamp &gt;= NOW() - 15 minutes AND k8s.container.restarts IS NOT NULL
| STATS restarts = MAX(k8s.container.restarts)
    BY k8s.namespace.name, k8s.pod.name, k8s.container.name
| WHERE restarts &gt;= 5
</code></pre>
<p>Wire this into an <a href="https://www.elastic.co/docs/explore-analyze/alerts-cases/alerts/rule-type-es-query">Elasticsearch query rule</a> and you get notified the moment a container crosses five restarts in fifteen minutes, instead of finding out when a user does. The same pattern applies to memory utilization, node saturation, and error counts.</p>
<h2 id="buildingakubernetesmonitoringtoolkitwithesql">Building a Kubernetes monitoring toolkit with ES|QL</h2>
<p>ES|QL gives you one language for every Kubernetes signal, from object state to resource metrics to container logs. Start with the exploration query to understand your cluster's shape, then keep the restart, phase, memory, CPU, and log queries close for the next incident. Use <code>TS</code> when you need time series functions like <code>MAX_OVER_TIME</code> or <code>TBUCKET</code> to aggregate correctly within each pod or container series. For counting distinct values or taking a simple MAX on a gauge, <code>FROM</code> is enough.</p>
<p>From here, you can adapt the field names to your own receivers, save the most useful queries as dashboard panels, and promote the critical ones to alerts.</p>
<p>To go deeper, see the <a href="https://www.elastic.co/docs/reference/query-languages/esql">ES|QL reference</a>, the <a href="https://www.elastic.co/docs/reference/query-languages/esql/commands/ts"><code>TS</code> command documentation</a>, and the <a href="https://www.elastic.co/docs/reference/opentelemetry/use-cases/kubernetes">EDOT Kubernetes guide</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/esql-kubernetes-monitoring</link>
    <guid isPermaLink="false">esql-kubernetes-monitoring</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <dc:creator><![CDATA[Jeffrey Rengifo]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd8fedad7d2a4e61e/6a85c854d6cf29b0cabb089c/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 08 Jul 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kubernetes observability: MCP specialist agents for safer EKS triage]]></title>
    <description><![CDATA[Scope a specialist EKS MCP agent for cluster checks while the Elastic AI Agent triages; fix a service misconfiguration using the specialist agent in a few prompts.]]></description>
    <content:encoded><![CDATA[<p>Elastic Observability shows you which services and edges in your service map are failing. You may still need to access details like live kubernetes service specs and containerPort to targetPort mapping, which still reside at the cluster. They can be made available in Elasticsearch via EKS MCP. The fix is to equip your Elastic AI Agent with a focused set of EKS tools, through a specialist agent. The Elastic AI agent keeps its stock tools and remains the only surface your SREs interact with. A specialist K8s Troubleshooter agent carries ~20 EKS MCP tools, scoped to a single IAM identity and Kubernetes RBAC. They hand off through an <a href="https://www.elastic.co/docs/explore-analyze/workflows">Elastic Workflow</a> that calls the <a href="https://www.elastic.co/docs/explore-analyze/ai-features/elastic-agent-builder">Agent Builder</a> <a href="https://www.elastic.co/docs/explore-analyze/ai-features/agent-builder/kibana-api#chat-and-conversations">converse</a> API, so the boundary between observability reasoning and cluster actions is callable, reviewable, and auditable. To prove it works, we break targetPort on product-catalog in <a href="https://github.com/elastic/opentelemetry-demo">elastic-opentelemetry-demo</a> and recover it in 4 prompts on a single thread.</p>
<h2 id="problemcontext">Problem context</h2>
<p>Outages often show up as correlated errors on multiple services like checkout, frontend, and recommendation in Elasticsearch.
That pattern can mean a shared dependency, or it can mean Kubernetes is misleading callers: wrong targetPort, empty Endpoints, or pods that never become ready.
Observability tools like Elasticsearch tell you <em>that</em> callers fail and <em>which</em> edges look wrong.
They generally do not fetch the live Service spec or compare containerPort to targetPort for you.</p>
<p>The Elastic AI Agent in Agent Builder is built for APM, logs, metrics, dependencies, and service maps.
It is not a full EKS operations console.
You could attach all EKS MCP tools to the same agent, but long tool lists increase wrong-tool calls, slow planning, and widen blast radius if a prompt accidentally asks for mutating actions.</p>
<h2 id="solutionoverview">Solution overview</h2>
<p>Use <strong>Elastic AI Agent</strong> as the only agent your SRE chats with.
It reasons from Elasticsearch first.
When evidence points to cluster config, it calls a workflow tool that invokes the <strong>K8s Troubleshooter agent</strong> over <code>/api/agent_builder/converse</code> with a structured <code>user_prompt</code>.
The <strong>K8s Troubleshooter agent</strong> carries only the EKS MCP tools, and cluster access stays scoped to one specialist identity, IAM, and RBAC. You can audit like any other integration.</p>
<p>Elasticsearch reaches EKS through an in-cluster bridge, exposed to Kibana as an MCP connector with a shared secret.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt00eb659814dcf87b/6a7f05ce05b7b5561e18b681/solution_overview.png" alt="Solution Overview" /></p>
<h2 id="beforeyoustart">Before you start</h2>
<p>You need:</p>
<ul>
<li>An EKS cluster with kubectl configured.</li>
<li>An Elasticsearch 9.3+ deployment, an OTLP endpoint, an Elasticsearch API key, Agent Builder, and rights to create agents, MCP tools, and Workflows.</li>
<li>An AI Connector in Elasticsearch for your chosen LLM.</li>
<li>Budget two to four hours the first time you run these steps.</li>
</ul>
<h2 id="implementationwalkthrough">Implementation walkthrough</h2>
<h3 id="step1deploytheelasticopentelemetrydemoandshiptelemetrytoelasticobservability">Step 1: deploy the Elastic OpenTelemetry Demo and ship telemetry to Elastic Observability</h3>
<p>Follow <a href="https://github.com/elastic/opentelemetry-demo"><strong>elastic/opentelemetry-demo</strong></a> for Kubernetes and deploy elastic-opentelemetry-demo application to your EKS cluster.
Configure your Elasticsearch OTLP endpoint and API key, confirm workloads are running, and note the namespace.
In Kibana (APM, Logs, or Service Map), confirm data for checkout, frontend, recommendation, and product-catalog.</p>
<p>If you see healthy traffic to <code>product-catalog</code>, you are ready for the failure drill.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt92e83f13b69e3bc4/6a7f05d1e3a21975f399f1a4/04-service-map-or-errors.png" alt="Healthy Elastic Observability service map for demo services." /></p>
<h3 id="step2runtheeksmcpbridgeregistertheconnectorandbulkimporteksmcptools">Step 2: run the EKS MCP bridge, register the connector, and bulk import EKS MCP tools</h3>
<p>Complete the steps in <a href="https://github.com/ramp-km/aws-eks-mcp-setup/blob/main/README.md"><strong>aws-eks-mcp-setup</strong></a> end to end.
The flow you would be following is: </p>
<ol>
<li>Build and push the EKS MCP Bridge image</li>
<li>Create IAM policies</li>
<li>Create IRSA Service Account</li>
<li>Map IRSA role in aws-auth and apply Kubernetes RBAC</li>
<li>Deploy the bridge with a strong API_ACCESS_TOKEN to the EKS cluster</li>
<li>Connect Elastic Agent Builder with EKS MCP</li>
</ol>
<p>A green MCP connector proves Kibana can reach the bridge.</p>
<p>For production, restrict LoadBalancer security groups to known Elasticsearch egress, prefer TLS on real paths, store tokens in Kubernetes Secrets, and use read-only MCP modes when you only diagnose.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1bf20bb14fb36532/6a7f05d477b034a7333ff22b/05-eks-mcp.png" alt="MCP connector pointed at the EKS bridge." /></p>
<h3 id="step3createak8stroubleshooteragentwithekstoolsonly">Step 3: create a <strong>K8s Troubleshooter agent</strong> with EKS tools only</h3>
<p>In Agent Builder, create an agent with agent ID <code>k8s_troubleshooter</code>, display name <code>K8s Troubleshooter</code>, and custom instructions from <a href="https://github.com/ramp-km/blogs/blob/main/Custom%20K8s%20Troubleshooter/k8s_troubleshooter_agent.md">k8s_troubleshooter_agent</a>.
Attach only EKS MCP tools to this agent.</p>
<p>Chat directly with <strong>K8s Troubleshooter agent</strong> once and confirm a harmless read (for example list pods in the demo namespace).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5c03fc63aa35b190/6a7f05d7c2e914cf690168ff/02-k8s-troubleshooter-agent.png" alt="K8s Troubleshooter agent" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb8c1ac20d8b1e65c/6a7f05db6693f8f101663c85/02-k8s-troubleshooter-agent-2.png" alt="K8s Troubleshooter agent with EKS MCP tools attached." /></p>
<h3 id="step4elasticsearch93onlyclonetheobservabilityagentwithoutekstools">Step 4 (<code>Elasticsearch 9.3 only</code>): clone the Observability Agent without EKS tools</h3>
<p>Clone the bundled <code>Observability Agent</code> (Agent Builder → Manage Agents → Observability Agent → Clone) and name it <strong>Elastic AI Agent</strong> so it keeps the stock Observability system instructions and tools.
Do not attach EKS MCP tools to this copy.</p>
<p>The parent <strong>Elastic AI Agent</strong> stays an observability-first interface for whoever chats with it.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a3f591e0527edc7/6a7f05dfe88c65e1bb00b3b8/01-observability-agent-v2.png" alt="Observability Agent v2 tools and instructions." /></p>
<h3 id="step5createtheworkflowandmakeitacallabletool">Step 5: create the workflow and make it a callable tool</h3>
<p><a href="https://www.elastic.co/docs/explore-analyze/workflows/get-started/build-your-first-workflow">Create</a> a new Elastic Workflow by importing <a href="https://github.com/ramp-km/blogs/blob/main/Custom%20K8s%20Troubleshooter/k8s_troubleshooter_workflow.yaml">k8s_troubleshooter_workflow.yaml</a> and enable it.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt24957065ecb6477b/6a7f05e233fa8a71032023ca/05-workflow.png" alt="Kibana Workflows editor: k8s_troubleshooter workflow YAML enabled." /></p>
<p>Create a new tool in Agent Builder of type <code>Workflow</code>. Select the <code>k8s_troubleshooter</code> workflow, set tool ID <code>custom.k8s_troubleshooter</code>, and set the description to <code>Tool to triage and troubleshoot kubernetes related issues</code> (or equivalent wording your team standardizes on).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf334d5d0b642e7fd/6a7f05e52f00b2c209efe8ce/05-workflow_tool_k8s_troubleshooter.png" alt="Agent Builder: Workflow tool wired to k8s_troubleshooter with custom tool id and description." /></p>
<p>On <strong>Elastic AI Agent</strong>, attach the <code>custom.k8s_troubleshooter</code> workflow tool that you just created.</p>
<p>The parent’s tool list should show the <code>custom.k8s_troubleshooter</code> workflow tool attached, and <strong>K8s Troubleshooter agent</strong> should still answer when invoked on its own.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b029c737e738946/6a7f05e873d9bd41d829d874/03-workflow-tool-parent-agent.png" alt="Workflow registered as a tool on the parent agent." /></p>
<h3 id="step6injecttheproductcatalogservicemisconfiguration">Step 6: inject the product-catalog service misconfiguration</h3>
<p>Save the original <code>targetPort</code>, then patch to a wrong value (for example 9999).</p>
<pre><code>kubectl get svc -A | grep product-catalog
kubectl get svc product-catalog -n YOUR_NAMESPACE -o yaml
</code></pre>
<pre><code>kubectl patch svc product-catalog -n YOUR_NAMESPACE --type='json' \
  -p='[{"op": "replace", "path": "/spec/ports/0/targetPort", "value": 9999}]'
</code></pre>
<pre><code>kubectl rollout restart deployment/checkout deployment/recommendation deployment/frontend -n YOUR_NAMESPACE
</code></pre>
<p>Callers still resolve Endpoints, but traffic lands on a port the container does not listen on, so Elasticsearch shows downstream errors on checkout, frontend, and recommendation.</p>
<p>You now have symptoms in Elasticsearch and a clear kubernetes cluster-side fault.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7371f0ca6cee50d/6a7f05ebb4377063a24d69dd/06-demo-services-service-map-or-errors.png" alt="Elastic Observability service map or error view after the misconfiguration." /></p>
<h3 id="step7runtwopromptsontheparentagent">Step 7: run two prompts on the parent agent</h3>
<p>Use AI Agent chat on <strong>Elastic AI Agent</strong>, not on the specialist.</p>
<p><code>Note:</code> If you are using Elasticsearch 9.3, make sure you use the <strong>Elastic AI Agent</strong> that you created, not the stock agent.</p>
<p>Prompt 1: <em>Why are failure transactions increasing for services like checkout and frontend?</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to narrow the issue to the product-catalog service and note possible configuration issues as one of the probable causes, without yet invoking the <code>custom.k8s_troubleshooter</code> tool.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2bf7243ff1f82610/6a7f05ee42a117b6d895bbe2/07-ai-agent-product-catalog-issues.png" alt="Elastic AI Agent identifying product catalog issues" /></p>
<p>Prompt 2: <em>Why is product-catalog service not servicing any requests in (insert your k8s cluster name) cluster? Is there any misconfiguration in the service?</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to call the <code>custom.k8s_troubleshooter</code> tool, which invokes the <strong>K8s Troubleshooter agent</strong> and reads Service, Endpoints, and pods, compares <code>targetPort</code> to <code>containerPort</code>, and explains the mismatch with evidence. Expect to also see the recommended remediation steps.</p>
<p><code>Note:</code> depending on the LLM you are using, the response from the agents may vary.</p>
<p>You get agent-led triage in Elastic Observability and cluster-grounded confirmation in the same thread, along with recommended remediation steps.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaaa6b9e3ffc40882/6a7f05f1e02fac3bf25d62e0/07-ai-agent-chat-custom-k8s-troubleshooter.png" alt="Agent Builder chat on Observability Agent v2 invoking the K8s Troubleshooter agent workflow." /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e0151dfb52c819e/6a7f05f405b7b5ead618b6b6/07-ai-agent-chat-port-misconfiguration.png" alt="Agent Builder chat on Observability Agent v2 identifying port misconfiguration." /></p>
<h3 id="step8patchtheproductcatalogservice">Step 8: patch the product-catalog service</h3>
<p>Prompt 3: <em>Patch the product-catalog service in (your EKS cluster name) cluster to have 8080 as the targetPort</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to call the <code>custom.k8s_troubleshooter</code> tool, which invokes the <strong>K8s Troubleshooter agent</strong> and patches the product-catalog service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaea2e5aa61e128e2/6a7f05f833fa8ab2262023de/08-ai-agent-chat-patch-product-catalog.png" alt="Agent Builder chat on Observability Agent v2 patching product-catalog service." /></p>
<p>Prompt 4: <em>Rollout restart upstream services of product-catalog service</em></p>
<p>Expect <strong>Elastic AI Agent</strong> to identify all upstream services of product-catalog and call the <code>custom.k8s_troubleshooter</code> tool, which invokes the <strong>K8s Troubleshooter agent</strong> to roll out restarts for upstream services such as checkout, frontend, and recommendation.</p>
<p>Confirm product-catalog and upstream services recover in Elasticsearch.</p>
<h2 id="validationandtradeoffs">Validation and trade-offs</h2>
<p>You validated that <strong>Elastic AI Agent</strong> stays the main surface, that ~20 EKS tools live on one specialist <strong>K8s Troubleshooter agent</strong>, and that the Workflow plus Agent Builder <code>converse</code> API keeps a clear boundary for audits and reviews.</p>
<p>Trade-offs: MCP bridges need ongoing token and network hygiene, and you should keep mutating tools off or tightly RBAC-scoped until you accept the risk.</p>
<h2 id="frequentlyaskedquestions">Frequently asked questions</h2>
<h3 id="whyiselasticaiagentnottriagingtheissuesasexplainedinthisarticle">Why is Elastic AI Agent not triaging the issues as explained in this article?</h3>
<p>There could be two primary reasons. (A) If you are on Elasticsearch 9.3, make sure you chat on the Elastic AI Agent that you created, and not on the stock agent. (B) Make sure to use one of the LLM models rated <code>Excellent</code> or <code>Great</code> in <a href="https://www.elastic.co/docs/solutions/observability/ai/llm-performance-matrix">Large language model performance matrix for Observability</a></p>
<h3 id="whydomyserviceslookunhealthyinelasticsearchwhentheappcodedidnotchange">Why do my services look unhealthy in Elasticsearch when the app code did not change?</h3>
<p>Kubernetes can mislead HTTP clients: a bad Service <code>targetPort</code>, empty Endpoints, or pods that never become ready can fan out as errors on multiple edges in traces and service maps. Elastic Observability shows which dependencies fail; confirming the live Service spec usually needs cluster access.</p>
<h3 id="howdoigivekubernetesaccesstoelasticaiagentwithoutputtingeveryekstoolonit">How do I give Kubernetes access to Elastic AI Agent without putting every EKS tool on it?</h3>
<p>Run two Agent Builder agents: keep the stock tools on the parent (Elastic AI Agent), and attach only EKS MCP tools to a specialist agent(K8s Troubleshooter agent). Invoke the specialist through a workflow that calls the Agent Builder converse API so the boundary is explicit and auditable.</p>
<h3 id="whychainagentswithelasticworkflowsinsteadofonelongsystemprompt">Why chain agents with Elastic Workflows instead of one long system prompt?</h3>
<p>Workflows give a callable, reviewable step between observability reasoning and cluster actions, which helps with governance and keeps the parent agent’s tool list short. Long unified tool lists often increase mistaken tool use and broaden blast radius if a prompt requests a mutating operation.</p>
<h3 id="howdoesthiscomparetokubectloracloudconsoleforincidentresponse">How does this compare to kubectl or a cloud console for incident response?</h3>
<p>Consoles and kubectl stay the source of truth for live object state. This pattern automates the handoff from Elastic Observability signals to those checks through MCP, while still relying on IAM and Kubernetes RBAC on the specialist identity.</p>
<h3 id="whatarethemainlimitationsorrisksofaneksmcpbridgewithagentbuilder">What are the main limitations or risks of an EKS MCP bridge with Agent Builder?</h3>
<p>MCP bridges need token rotation, network restrictions, and TLS discipline on real paths. Mutating EKS tools should stay off or tightly RBAC-scoped until you accept operational risk.</p>
<h3 id="whydoweneedaneksmcpbridge">Why do we need an EKS MCP bridge?</h3>
<p>The managed EKS MCP server authenticates via AWS SigV4 through a stdio-based proxy (mcp-proxy-for-aws). Elastic's MCP connector requires an HTTP/SSE endpoint. The bridge pod runs mcp-proxy to expose the stdio proxy as an SSE/HTTP endpoint.</p>
<h3 id="canireusethesamelayoutongkeaksorselfmanagedkubernetes">Can I reuse the same layout on GKE, AKS, or self-managed Kubernetes?</h3>
<p>Yes. The separation principle is the same: observability data in Elasticsearch plus a specialist agent with cluster-scoped tools and a workflow-mediated handoff. Swap the MCP server or bridge, adjust RBAC, and parameterize cluster name or region in workflow inputs where needed.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/eks-agent-builder-mcp-kubernetes-troubleshooting</link>
    <guid isPermaLink="false">eks-agent-builder-mcp-kubernetes-troubleshooting</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Ramprasad KM]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt32b449a412d139b6/6a7f05fbc2cc09008c24922b/header.png" length="0" type="image/png"/>
    <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Investigate Kubernetes infrastructure issues with PromQL in Elasticsearch & Kibana]]></title>
    <description><![CDATA[Walkthrough of a Kubernetes fleet-wide CPU investigation in Elastic Observability, from cluster to namespace to the noisy pod, using PromQL in Elasticsearch and Kibana.]]></description>
    <content:encoded><![CDATA[<p><a href="https://www.elastic.co/observability-labs/blog/elasticsearch-supports-promql">Elasticsearch now supports PromQL natively</a>, and <a href="https://www.elastic.co/observability-labs/blog/promql-queries-run-in-kibana">you can run PromQL queries in Kibana</a> through the <code>PROMQL</code> source command in ES|QL.
That means you can use PromQL to query your Kubernetes metrics stored in Elasticsearch. You can run those queries directly in Discover, Dashboards or alerting rules.</p>
<p>When <strong>cluster CPU spikes</strong> and you need to find <strong>which workload</strong> is responsible, narrow from <strong>fleet</strong> to <strong>namespace</strong> to <strong>pod</strong>, one step at a time.</p>
<h2 id="whatyouneed">What you need</h2>
<ul>
<li>An <strong>Observability</strong> <a href="https://www.elastic.co/docs/solutions/observability/get-started">serverless project</a> or a self-managed or Elastic Cloud Hosted stack at <strong>version 9.4 or later</strong>, where <strong>PromQL</strong> is available as a <strong>preview</strong> query language for metrics.</li>
<li><strong>Kubernetes</strong> metrics flowing into Elasticsearch. For this exercise we have considered <strong>OpenTelemetry</strong> data.</li>
<li>One or more clusters with workloads running so <code>group by</code> queries have something to compare.</li>
</ul>
<h2 id="thescenario">The scenario</h2>
<p>You manage a fleet of Kubernetes clusters:</p>
<p>| Cluster | Region | Role |
|---------|--------|------|
| <code>prod-us-east-1</code> | US East | Production: services, ML training |
| <code>prod-eu-west-1</code> | EU West | Production: regional web tier, cache |
| <code>staging-us-east-1</code> | US East | Staging: QA, integration tests |
| <code>dev-sandbox</code> | US East | Developer sandbox |</p>
<p>The production cluster in US East runs a mix of services and ML training jobs across several namespaces.</p>
<p>An <strong>alert</strong> fires: <strong>cluster-wide CPU is elevated</strong>, but only one team is complaining about slower response times.</p>
<p>You are triaging <strong>which cluster</strong>, then <strong>which namespace</strong>, then <strong>which pod</strong>.</p>
<p>You are not after a full root-cause proof in one query, but enough to <strong>name the suspect</strong> and hand off.</p>
<h2 id="yourdata">Your data</h2>
<p>The OpenTelemetry Collector's <strong>Kubelet Stats Receiver</strong> populates data streams like <code>metrics-kubeletstatsreceiver.otel-default</code>.
Metrics follow the <code>k8s.*</code> naming convention (for example <code>k8s.pod.cpu.usage</code>) and labels like <code>k8s.cluster.name</code> or <code>k8s.namespace.name</code> let you slice by cluster, namespace, or pod.</p>
<p>To verify the data is there, open <strong>Discover</strong>, switch to ES|QL mode, run <strong><code>TS metrics-*</code></strong>, and scope the query with <strong><code>WHERE data_stream.dataset == "kubeletstatsreceiver.otel"</code></strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt78109b18cefd6655/6a7f19dbe3a219121f99f8b2/discover-ts-metrics-k8s.png" alt="Discover: kubernetes metrics from OpenTelemetry" /></p>
<h2 id="investigationfindthenoisyneighbor">Investigation: find the noisy neighbor</h2>
<h3 id="step1whichclusterishot">Step 1: Which cluster is hot?</h3>
<p>When you manage multiple clusters, start at the fleet level.</p>
<pre><code>PROMQL sum by (k8s.cluster.name) (k8s.pod.cpu.usage)
</code></pre>
<p>This groups total pod CPU by cluster.</p>
<p><code>prod-us-east-1</code> immediately stands out: total pod CPU is <strong>an order of magnitude higher</strong> than the other clusters.</p>
<p>The EU production cluster, staging, and dev-sandbox are all quiet.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1ccaed4498c9d723/6a7f19de96b5a62c2c87b873/promql-fleet-cpu-by-cluster.png" alt="Fleet-level PromQL chart showing prod-us-east-1 as the outlier" /></p>
<p>Now you know <strong>where</strong> the problem is, time to zoom in.</p>
<h3 id="step2overallcpuinthehotcluster">Step 2: Overall CPU in the hot cluster</h3>
<p>Filter to <code>prod-us-east-1</code> and look at total CPU:</p>
<pre><code>PROMQL sum(k8s.pod.cpu.usage{k8s.cluster.name="prod-us-east-1"})
</code></pre>
<p>This gives you the <strong>cluster-wide pod CPU footprint</strong> over time.</p>
<p>If the total is climbing or spiking, something changed, but you don't yet know <strong>what</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt14eca383e549700a/6a7f19e24c4bfb7d30ccd8ec/promql-hot-cluster.png" alt="Overall CPU in prod-us-east-1 showing a clear spike" /></p>
<h3 id="step3breakdownbynamespace">Step 3: Break down by namespace</h3>
<p>The fastest way to isolate <strong>which team</strong> is responsible: group by namespace.</p>
<pre><code>PROMQL sum by (k8s.namespace.name) (k8s.pod.cpu.usage{k8s.cluster.name="prod-us-east-1"})
</code></pre>
<p>Set the <strong>time picker</strong> in Kibana to cover your incident window.</p>
<p><code>ml-training</code> dominates at <strong>~2.0 cores</strong> while every other namespace stays well below <strong>0.2 cores</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte0eef1cef1efae0c/6a7f19e5448e4e068c5c0b56/promql-group-by-noisy-neighbor.png" alt="Grouped PromQL chart showing ml-training as the dominant series" /></p>
<h3 id="step4drilldowntothepod">Step 4: Drill down to the pod</h3>
<p>Now that you know the namespace, identify the specific pod:</p>
<pre><code>PROMQL sum by (k8s.pod.name) (k8s.pod.cpu.usage{k8s.cluster.name="prod-us-east-1", k8s.namespace.name="ml-training"})
</code></pre>
<p>That ranks pods in the namespace by total CPU.</p>
<p>The chart should make the outlier obvious.</p>
<p>Pod <code>model-train-v2-run-47-d9j67</code> is consuming the full <strong>2.0 cores</strong>.
It is a training job saturating its allocation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc9b5a73325ae96ab/6a7f19e85967e564495dd6b5/promql-drilldown-pod.png" alt="Pod drill-down showing model-train-v2-run-47-d9j67 as the CPU consumer" /></p>
<h3 id="step5checkresourceutilizationratios">Step 5: Check resource utilization ratios</h3>
<p>Raw CPU cores tell you <strong>how much</strong>.
Utilization ratios tell you <strong>how close to limits</strong>.</p>
<p>A pod hitting 100% of its CPU limit is being throttled, and it is both the noisy neighbor <strong>and</strong> a victim of its own limits.</p>
<pre><code>PROMQL sum by (k8s.namespace.name) (k8s.container.cpu_limit_utilization{k8s.cluster.name="prod-us-east-1"})
</code></pre>
<p><code>ml-training</code> shows <strong>~100% CPU limit utilization</strong> (pegged at the 2-core limit), while the other namespaces stay under 20%.</p>
<p>This confirms the training job is <strong>saturating its allocation</strong> and likely causing scheduling pressure on the shared node.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64374adda3576ad3/6a7f19ebea068d317bf0a2bb/promql-cpu-utilization.png" alt="CPU limit utilization by namespace — ml-training pegged near 100%" /></p>
<h2 id="whathappensnext">What happens next</h2>
<p>The PromQL query <strong>named the suspect</strong>: the training job <code>model-train-v2-run-47</code> in <code>ml-training</code>.</p>
<p>From here:</p>
<ul>
<li><strong>Logs</strong>: Filter by the pod name in Discover to see what the training job was doing and whether it logged errors or warnings.</li>
<li><strong>Kube events</strong>: Check for OOMKilled, throttling, or eviction events in the same time window.</li>
<li><strong>Resource policies</strong>: Review whether the training job's requests and limits match its actual usage. A large gap between request and limit lets a pod burst past what the scheduler planned for. Consider <code>ResourceQuota</code> or <code>LimitRange</code> on the namespace.</li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/promql-investigate-kubernetes-infrastructure</link>
    <guid isPermaLink="false">promql-investigate-kubernetes-infrastructure</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Miguel Sánchez Gómez]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3651d463b7cb4316/6a7f19eebdcff04042c4329b/cover.png" length="0" type="image/png"/>
    <pubDate>Thu, 07 May 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Kubernetes Observability from alert to root cause: Dashboards, Alerts, and Anomaly Detection with Elastic]]></title>
    <description><![CDATA[Kubernetes observability with Elastic includes dashboards, alert rules, and ML anomaly detection for alerts with root-cause context.]]></description>
    <content:encoded><![CDATA[<p>Kubernetes observability with Elastic is built for the operator who gets paged at 3 AM. That operator is often in a terminal, a chat tool, or an IDE. They need an answer that is grounded in what is happening in the cluster right now.</p>
<p>The new <a href="https://www.elastic.co/docs/reference/integrations/kubernetes">Elastic Kubernetes integration</a> is built for that operator. It includes  dashboards with drilldowns, alert rule templates, and ML anomaly detection jobs. Additionally Elastic also offers Agentic Investigations, that drives investigations automatically. </p>
<p>This blog will cover the foundational observability components (dashboards, drilldowns, alert templates, etc), while a part 2 covering the agentic investigations will cover workflows, agent skills, and MCP tools and views</p>
<p>The new Kubernetes integration content in this post is generally available across Elastic Cloud Hosted, Serverless, and self-managed deployments.</p>
<hr />
<h2 id="dashboardsdesignedfordrilldownnotjustdisplay">Dashboards designed for drill-down, not just display</h2>
<p>The new Kubernetes dashboards are organized around a three-tier design: a cluster Overview that surfaces what needs attention at a glance, object summary pages for clusters, nodes, namespaces, workloads, and pods, and object detail pages that give you the full picture for any single entity.</p>
<p>Every layer connects to the next: click any entity in a summary table and choose: apply it as a filter on the current view, or open its dedicated detail page.</p>
<p>Here's what that looks like when something's actually wrong:</p>
<p><strong>Following a restart cascade from overview to container</strong></p>
<p><strong>Overview:</strong> The Overview surfaces what needs attention across your cluster.
You can see top pods by CPU, top namespaces by container restarts, and top nodes by memory utilization in one screen.
When the "container restarts" panel starts climbing, you know where to look.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta96de19280caf42b/6a7f0bc7227b1c58be598548/overview-dashboard.jpg" alt="Kubernetes observability with Elastic, cluster overview dashboard showing top pods by CPU and container restarts by namespace" /></p>
<p><strong>Namespaces Overview:</strong> Click into the flagged namespace with 1232 restarts and CPU limit utilization at 116%.
The detail view plots CPU and memory against requests and limits over time.
This shows both the size and duration of the overage.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb75870f34cf3b08f/6a7f0bca6c6eac5ef2f1409b/namespace-overview.jpg" alt="Kubernetes observability with Elastic, namespace overview showing multiple namespaces" /></p>
<p><strong>Namespace Details:</strong> We can get more info on the various pods in this namespace here.
Click the pod driving the restarts.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9ec4c28319b6e11/6a7f0bcd3ce8e203e4cf533b/namespace-details.jpg" alt="Kubernetes observability with Elastic, namespace detail view showing CPU limit utilization at 116% and container restart count" /></p>
<p><strong>Pod Details:</strong> The pod detail dashboard is organized into capacity, metrics, and containers sections.
Container restarts are flagged in red at the top of the page.
Most panels are metric-driven, and the dashboard also links to correlated pod logs in Discover.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt62fb8cfe62ca374a/6a7f0bd0bdcff07295c42db7/pod-details.jpg" alt="Kubernetes observability with Elastic, pod detail dashboard with container restart alerts, capacity metrics, and log drilldown links" /></p>
<p>It takes four clicks to move from the Cluster Overview to container logs that explain the failure.
These dashboards are starting points for your team.
You can copy and customize them with ESQL visualizations.</p>
<hr />
<h2 id="alertrulesthatfireondayone">Alert rules that fire on day one</h2>
<p>The integration ships with pre-built alerting rule templates for states that are wrong by definition.
No historical baseline or warmup period is required.
Enable them during setup and they work immediately.</p>
<p>These rules do not ask, "Is this abnormal for this service?"
They ask, "Is this a known bad state in Kubernetes?"
A pod in CrashLoopBackOff is always a problem.
A container killed by the kernel for exceeding its memory limit is always a problem.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt491bfc3539965be3/6a7f0bd49090b015b484e967/alert-list.png" alt="Kubernetes observability with Elastic, list of alerts with the CrashLoopBackOff alert rule selected" /></p>
<p>Like the Kubernetes dashboards, these alerts are built on ES|QL queries.
You can see that in the CrashLoopBackOff definition below.
If you are new to ES|QL, you can start with the <a href="https://www.elastic.co/docs/explore-analyze/query-filter/languages/esql">ES|QL docs</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9cb843f4cf381e4a/6a7f0bd72f00b2b067efeaf8/alert-detail.png" alt="Kubernetes observability with Elastic, ES|QL query that defines the CrashLoopBackOff alert rule" /></p>
<p>The alert templates cover:</p>
<ul>
<li><strong>CrashLoopBackOff detection</strong> - Fires when a pod's restart count exceeds a configurable threshold within a rolling window.
The default catches a real restart cycle without triggering on routine restarts during a rolling deployment.</li>
<li><strong>Container OOMKilled</strong> - Surfaces kernel-level container terminations due to memory limits.
These events are easy to miss in dashboards and often precede wider failures.
This rule fires on any occurrence.</li>
<li><strong>Deployment below desired replicas</strong> - Fires when a deployment runs fewer replicas than declared for longer than a grace period.
This catches scaling failures and partially failed rollouts.</li>
<li><strong>Pod stuck in Pending</strong> - Fires when a pod cannot be scheduled past a configurable time threshold.
This surfaces node capacity problems, missing resources, and affinity failures before availability drops.</li>
<li><strong>Node disk pressure</strong> - Fires immediately when the Kubernetes DiskPressure node condition is <code>True</code>.
A node condition is a direct state signal, not a statistical threshold.</li>
<li><strong>Persistent volume near capacity</strong> - Alerts when storage utilization crosses a configurable threshold before writes start failing.</li>
</ul>
<p>Each template is parameterized.
Adjust thresholds in the ES|QL query to match your environment.
Connect notifications to PagerDuty, Slack, or another destination in your runbook.</p>
<hr />
<h2 id="anomalydetectionjobswithmlbaselines">Anomaly detection jobs with ML baselines</h2>
<p>Alert rules catch what is definitively wrong.
ML anomaly detection catches patterns that often precede failures.
If you are new to this area, see the <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-ad-overview.html">Elastic anomaly detection overview</a>.</p>
<p>A pod that always runs at 85% memory utilization might be healthy.
A pod that grew from 40% to 85% over twelve hours is usually not healthy.
A static threshold often catches this only after an OOM kill.
The ML module should catch the trajectory earlier.</p>
<p>The integration ships with ML module configurations that learn workload baselines and flag meaningful deviations.
These jobs need 24 to 48 hours of data before results become useful.
Results become more reliable as jobs continue to run.</p>
<h3 id="theincludedmodules">The included modules</h3>
<p><strong>1. Pod memory growth anomalies</strong></p>
<ul>
<li><strong>What it learns:</strong> per-pod memory consumption pattern over time</li>
<li><strong>What it flags:</strong> Growth trajectories that are inconsistent with baseline behavior, such as a slow leak that never crosses the hard limit.</li>
<li><strong>Why ML (not alert rule):</strong> The alert rule catches the OOMKill after the fact.
The ML job catches the trajectory that leads there.</li>
</ul>
<p><strong>2. Network I/O anomalies</strong></p>
<ul>
<li><strong>What it learns:</strong> per-pod network transmit/receive byte rate patterns</li>
<li><strong>What it flags:</strong> Unusual spikes or drops relative to the pod baseline.
A spike can indicate a runaway process or unexpected load.
A drop can indicate a network partition that causes the pod to go idle.</li>
<li><strong>Why ML (not alert rule):</strong> Normal network traffic varies by time of day and workload type.
A batch job pod at high throughput during its normal window is expected.
The same throughput outside that window can be anomalous.</li>
</ul>
<p><strong>3. Pod restart frequency</strong></p>
<ul>
<li><strong>What it learns:</strong> Per-workload restart rate patterns during deployments, scaling events, and routine operations.</li>
<li><strong>What it flags:</strong> Restart patterns that are anomalous relative to each workload's own history.
This is distinct from the CrashLoopBackOff alert rule, which fires on a fixed threshold regardless of context.</li>
<li><strong>Why ML (not alert rule):</strong> A deployment that restarts twice during every rollout can be healthy.
The same deployment restarting twice on a Tuesday afternoon may be unhealthy.
The alert rule cannot distinguish these cases without workload history.</li>
</ul>
<p>Here's our Single Metric Viewer showing anomalies triggered against a specific pod, for the memory growth job:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d86eb72e08ac5a1/6a7f0bda77b03484193ff457/single-metric-viewer.png" alt="Kubernetes observability with Elastic, ML Single Metric Viewer showing pod memory growth anomaly detection for one pod" /></p>
<p>And here's the multi-series Anomaly Explorer view of the same job, showing detections firing across a variety of pods:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7a2fe1f9686b6a0e/6a7f0bdd4c4bfbd2c8ccd4cf/anomaly-explorer.png" alt="Kubernetes observability with Elastic, Anomaly Explorer showing pod memory anomaly detections across multiple pods" /></p>
<hr />
<h2 id="tryityourselftheotelastronomyshop">Try it yourself: the OTel Astronomy Shop</h2>
<p>If you do not have a Kubernetes cluster ready, you can use the OpenTelemetry Astronomy Shop demo environment.
It uses the same commands as Getting Started Step 2, Path A, but points to demo services.
Create the namespace and secret, then run the Helm install.
All 16 services, Kafka, and PostgreSQL start flowing into Elastic without instrumentation changes.</p>
<p>The demo ships with a built-in feature flag service, <code>flagd</code>, that lets you activate failure scenarios.
Enable <code>cartServiceFailure</code> and watch the checkout-service restart cascade unfold in real time.
The CrashLoopBackOff alert rule fires.
The ML modules begin establishing baselines.
If you have the investigation workflow enabled, it runs automatically when the alert fires.</p>
<hr />
<h2 id="gettingstarted">Getting started</h2>
<p><strong>Step 1 - Install the Kubernetes integration.</strong>
Dashboards are available immediately.
No additional configuration is required.</p>
<p><strong>Step 2 - Deploy data collection.</strong>
There are two supported paths, both based on Helm.
Choose the one that fits your deployment model.</p>
<p><strong>Path A - OpenTelemetry (EDOT collector):</strong>
This path uses the <code>opentelemetry-kube-stack</code> Helm chart with the Elastic Distribution of OpenTelemetry (EDOT) collector.
Create a namespace and a secret with your endpoint and API key, then install:</p>
<pre><code>kubectl create namespace opentelemetry-operator-system

kubectl create secret generic elastic-secret-otel \
  --namespace opentelemetry-operator-system \
  --from-literal=elastic_otlp_endpoint='https://&lt;your-endpoint&gt;.elastic.cloud:443' \
  --from-literal=elastic_api_key='&lt;your-api-key&gt;'

helm upgrade --install opentelemetry-kube-stack open-telemetry/opentelemetry-kube-stack \
  --namespace opentelemetry-operator-system \
  --values 'https://raw.githubusercontent.com/elastic/elastic-agent/refs/tags/v9.3.2/deploy/helm/edot-collector/kube-stack/managed_otlp/values.yaml' \
  --version '0.12.4'
</code></pre>
<p><strong>Path B - Elastic Agent (standalone):</strong>
This path uses the <code>elastic/elastic-agent</code> Helm chart.
The default manifest includes resource limits that may not be appropriate for production.
Review the <a href="https://www.elastic.co/docs/reference/fleet/scaling-on-kubernetes">Scaling Elastic Agent on Kubernetes guide</a> before deploying.</p>
<pre><code>helm repo add elastic https://helm.elastic.co/ &amp;&amp; \
helm install elastic-agent elastic/elastic-agent \
  --version 9.3.2 \
  -n kube-system \
  --set outputs.default.url=https://&lt;your-endpoint&gt;.es.elastic.cloud:443 \
  --set outputs.default.type=ESPlainAuthAPI \
  --set outputs.default.api_key=$(echo "&lt;your-base64-api-key&gt;" | base64 -d) \
  --set kubernetes.enabled=true
</code></pre>
<p><strong>Step 3 - Enable the alert rule templates.</strong>
Go to Observability &gt; Alerts in Kibana.
The Kubernetes templates are in the rule library.
Enable the templates relevant to your environment, set thresholds, and connect your notification channel.</p>
<p><strong>Step 4 - Let the ML modules warm up.</strong>
After 24 to 48 hours, anomaly detection modules establish baselines and begin surfacing pattern-based deviations.
Longer running jobs usually produce better baselines.
Find results in the ML Anomaly Explorer, linked from the Kubernetes dashboards.</p>
<p><strong>Steps 5, 6, and 7 - Agentic content</strong> will be covered in Part 2 (forthcoming), Kubernetes observability with Elastic: Agentic Investigations.</p>
<hr />
<h2 id="whatsnext">What's next</h2>
<p>The next step is the layer that runs investigation workflows when an alert fires.
That includes skills that encode investigation logic, tools that expose facts like ML state and topology, and MCP apps that render outputs in places like Claude Desktop or VS Code.
These technical preview capabilities are available today and will be covered in Part 2 (forthcoming), Kubernetes observability with Elastic: Agentic Investigations.</p>
<p>If you are running Kubernetes on Elastic today, tell us which investigation steps you repeat manually on every incident.
Tell us which remediations you would trust a workflow to propose.
You can <a href="https://discuss.elastic.co/c/observability">join the Elastic Community Discussion here</a>.</p>
<hr />
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion.</em>
<em>Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kubernetes-dashboards-alerts-anomaly-detection</link>
    <guid isPermaLink="false">kubernetes-dashboards-alerts-anomaly-detection</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[AI]]></category>
    <dc:creator><![CDATA[Jesse Miller]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt582db3c8608d473c/6a7f0be03cab1c86700e47dc/header.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 21 Apr 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Composing OpenTelemetry Reference Architectures]]></title>
    <description><![CDATA[A conceptual framework for reasoning about OpenTelemetry Collection architectures — edge, processing, and resilience layers that compose into the right pipeline for your environment.]]></description>
    <content:encoded><![CDATA[<p>Most OpenTelemetry tutorials end at the same place: an application instrumented with the SDK, exporting traces to a single collector, forwarding to a backend. It works. Then production happens.</p>
<p>Traffic grows. Teams want metrics derived from traces. The backend goes down for maintenance and you lose an hour of telemetry. A compliance requirement means PII must be stripped before data leaves the cluster. Suddenly, that single collector isn't enough — and the question becomes: what should the architecture actually look like?</p>
<p>The OpenTelemetry Collector is designed to be composed. It can run in multiple deployment modes, be chained into pipelines, and scaled independently at each stage. But the documentation describes individual components, not how to think about assembling them. That thinking is what this article is about.</p>
<p>What follows is a conceptual framework for reasoning about collector architectures — not a set of rigid templates. The building blocks described here are reference points. In practice, they combine, overlap, and adapt to your constraints. A tail sampling tier might also need Kafka-backed resilience. A gateway might absorb the role of a sampling tier at low volumes. The goal is to understand the concepts well enough to compose the right architecture for your situation, not to pick a pre-built one off a shelf.</p>
<h2 id="threeconceptuallayers">Three conceptual layers</h2>
<p>It helps to think about collector architectures in three layers: <strong>edge</strong>, <strong>processing</strong>, and <strong>resilience</strong>. These aren't physical tiers that must exist as separate deployments — they're categories of concern. A single collector can address multiple layers. A complex deployment might have several components within one layer. The layers are a thinking tool, not a deployment diagram.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb0c3d663572bdc9a/6a7f18eceab5be375920aad7/three-layers.png" alt="The three conceptual layers: Edge, Processing, and Resilience" /></p>
<h3 id="edgehowtelemetryentersthepipeline">Edge: how telemetry enters the pipeline</h3>
<p>The edge layer is about the first hop — how telemetry gets from your applications and infrastructure into the pipeline. At this stage, the collector gathers data in two fundamentally different ways. <strong>Pull-based receivers</strong> like <code>filelog</code> and <code>hostmetrics</code> actively reach out to collect data — tailing log files on disk or scraping system-level metrics from the host. <strong>Push-based receivers</strong> like <code>otlp</code> listen for data sent to them — applications instrumented with OpenTelemetry SDKs export traces, metrics, and logs directly to the collector's OTLP endpoint. A single edge collector typically runs both: pull receivers for infrastructure telemetry the application doesn't know about, and push receivers for application telemetry the SDK produces. There are several common deployment patterns, and the right one depends on your environment and what you need to collect.</p>
<p><strong>DaemonSet Agent</strong> — One OpenTelemetry Collector per Kubernetes node, deployed as a DaemonSet. Applications export to the agent running on the same node (typically via status.hostIP:4317 using the Kubernetes Downward API). The agent also tails container log files from disk via the filelog receiver and scrapes host-level metrics via the hostmetrics receiver. This is the most common Kubernetes pattern because it handles both application and infrastructure telemetry with a single deployment, and applications only need to know about localhost.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt898169e98cea105d/6a7f18effc63abe98364d054/daemonset-agent.png" alt="DaemonSet Agent pattern: Application with OTel SDK exporting over OTLP to a per-node DaemonSet collector" /></p>
<p><strong>Sidecar Agent</strong> — One OpenTelemetry Collector per pod, deployed as a sidecar container. Each service gets its own collector with a custom configuration. This is required on managed container platforms like AWS Fargate or Azure Container Apps where DaemonSets aren't available, and it's useful when services have different processing requirements. When running alongside a DaemonSet, the sidecar handles application telemetry while the DaemonSet independently collects node-level telemetry — applications don't send to both.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbf752fb09630a0a5/6a7f18f2c2cc091599249994/sidecar-agent.png" alt="Sidecar Agent pattern: Application with OTel SDK exporting over OTLP to a per-pod sidecar collector" /></p>
<p><strong>Host Agent</strong> — A standalone OpenTelemetry Collector running as a systemd service on bare-metal or VM hosts. It serves the same role as the DaemonSet agent but outside Kubernetes: collecting host metrics, tailing log files, and receiving OTLP from local applications.</p>
<p><strong>Direct SDK Export</strong> — Applications export directly to the next stage (gateway or backend) with no local collector. This is the simplest option but only works when you don't need infrastructure collection. For log collection, the recommended pattern is still to write to stdout and use a collector with the <code>filelog</code> receiver — even if the SDK is exporting traces and metrics directly.</p>
<p>These patterns aren't mutually exclusive. A Kubernetes cluster might run DaemonSet agents for infrastructure collection alongside sidecars for services that need custom processing. A VM environment might use host agents for some services and direct SDK export for others. The edge layer is about matching the collection pattern to the workload, not picking one pattern for everything.</p>
<h3 id="processingcentralpolicysamplingandtransformation">Processing: central policy, sampling, and transformation</h3>
<p>Not every architecture needs a processing layer. If your edge collectors can export directly to your backend and you don't need centralized policy, you can skip it to favour simplicity. But several scenarios push you toward central processing — and the way you address them can range from a single gateway to a multi-stage pipeline.</p>
<p><strong>Centralized policy (Gateway)</strong> — A pool of OpenTelemetry Collectors that sits between edge collectors and the backend. This is where you enforce consistent filtering, transformation, and PII redaction across all services. It's also where you manage backend credentials — edge collectors export to the gateway over OTLP, and only the gateway holds the API keys. Credential isolation is often the primary reason teams add a gateway.</p>
<p>Replica count scales with data volume. At low volumes (under 1K events/sec), 2 replicas co-located with workloads is sufficient. At medium volumes, 3–5 replicas on a dedicated node pool. At high volumes, 5–20+ replicas, potentially in a separate cluster. This is a general rule of thumb, and you should adapt it to your specific needs as loads might vary significantly between payload types.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2c5fe1b38941f73d/6a7f18f533fa8a2253202b5c/gateway-pool.png" alt="Gateway pattern: Load Balancer distributing traffic to a Gateway Pool of OTel Collectors" /></p>
<p><strong>Tail-based sampling</strong> — Sampling decisions that consider the complete trace (e.g., "keep all traces with errors, sample 10% of successful traces") require that all spans of a trace reach the same collector instance. This is achieved with the <code>loadbalancingexporter</code> using <code>routing_key: traceID</code>, which consistently routes spans from the same trace to the same downstream collector.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e5c9ce1457269a3/6a7f18f8e88c654d6f00bada/tail-sampling.png" alt="Tail sampling pattern: LB Exporter routing to Sampling Collectors with tail_sampling" /></p>
<p>There's a critical subtlety here: if you're deriving span metrics (RED metrics) from traces using the <code>spanmetrics</code> connector, the derivation must happen <strong>before</strong> sampling. Otherwise, your metrics only reflect the sampled subset, not the true traffic. The correct pattern is a two-step pipeline within the sampling stage:</p>
<ol>
<li>Receive traces, derive spanmetrics from 100% of traffic, forward via a <code>forward</code> connector.</li>
<li>Apply <code>tail_sampling</code> to the forwarded traces, export only kept traces.</li>
<li>A separate metrics pipeline exports the derived RED metrics.</li>
</ol>
<p>This ensures accurate metrics regardless of your sampling rate.</p>
<p><strong>The key point about processing</strong> is that these capabilities — gateway policy, tail sampling, span metrics derivation — are not separate products or fixed modules. They're configurations of the same OpenTelemetry Collector. At low volumes, a single gateway deployment might handle policy enforcement, sampling, and metrics derivation all at once. At high volumes, you might split them into dedicated stages for independent scaling. The architecture adapts to your scale, not the other way around.</p>
<h3 id="resiliencewhathappenswhenthebackendisdown">Resilience: what happens when the backend is down</h3>
<p>The resilience layer determines how much data you're willing to lose during backend outages or collector restarts. This isn't a separate tier you bolt on — it's a property you apply to any stage of the pipeline.</p>
<p><strong>In-Memory Queues</strong> — The default. The collector's <code>sending_queue</code> retries failed exports with exponential backoff. If the collector process crashes or restarts, queued data is lost. This is acceptable for development and for workloads where some data loss during incidents is tolerable.</p>
<p><strong>Persistent Queues (WAL)</strong> — The <code>file_storage</code> extension writes queued data to disk before export. If the collector crashes, it resumes from where it left off after restart. In Kubernetes, this requires a PersistentVolumeClaim. This is the right choice for most production workloads — it survives collector restarts and brief backend outages without the operational complexity of an external message bus.</p>
<p><strong>Kafka Buffer</strong> — An external Kafka cluster sits between collectors and the backend. Producer collectors write to Kafka topics; consumer collectors read from Kafka and export to the backend. This provides the strongest durability guarantee — Kafka can buffer hours of telemetry during extended outages and enables replay. But it adds significant operational complexity.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbff2af063797b1e9/6a7f18fa448e4eacd65c0b38/kafka-buffer.png" alt="Kafka buffer pattern: Collector Pool producing to Kafka, consumed by another Collector Pool" /></p>
<p>The important thing to understand is that resilience is orthogonal to the other layers. You can add persistent queues to an edge agent, a gateway, or a sampling tier. You can put Kafka in front of a gateway, in front of a sampling tier, or in front of the backend. A tail sampling deployment that needs to survive extended outages might use Kafka-backed ingestion — combining what might look like two separate "modules" into a single stage. The building blocks compose freely based on what you need to protect against.</p>
<h2 id="wheretostartwithyourarchitecture">Where to start with your architecture</h2>
<p>The Agent + Gateway two-tier pattern is the de facto production standard, used by the vast majority of organizations running OpenTelemetry at scale. DaemonSet agents on every node handle local collection — pulling infrastructure telemetry via <code>filelog</code> and <code>hostmetrics</code>, receiving application telemetry via OTLP — while a centralized gateway pool enforces policy, manages credentials, and exports to the backend. Persistent queues (WAL) on the gateway protect against backend outages without external dependencies.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4b123115a47e99f1/6a7f18fd73d9bd051c29df2f/where-to-start.png" alt="A Kubernetes architecture with DaemonSet agents, a processing tier with tail sampling and gateway pool, exporting over OTLP to an observability backend" /></p>
<p>Every other configuration either simplifies this pattern or extends it. Smaller environments might drop the gateway and export directly from agents. Larger ones might add a tail sampling tier with traceID-based load balancing, a Kafka buffer for extended resilience, or span metrics derivation before sampling. The building blocks described in the previous sections — edge, processing, resilience — are the modules you add or remove from this foundation.</p>
<p>The key is to start with the two-tier pattern and evolve incrementally:</p>
<ul>
<li>Need credential isolation or centralized PII redaction? You already have the gateway.</li>
<li>Need tail-based sampling? Add a load-balancing exporter and a sampling tier between agents and gateway.</li>
<li>Need hours of buffer during extended outages? Insert Kafka between agents and the processing tier.</li>
<li>Running on Fargate or Azure Container Apps? Swap DaemonSet agents for sidecars — the rest of the pipeline stays the same.</li>
</ul>
<p>Start here. Add modules as your needs grow. The architecture adapts to your scale, not the other way around.</p>
<h2 id="decisionpointsthatshapewhereyouneedtotakeyourarchitecture">Decision points that shape where you need to take your architecture</h2>
<p>When designing a collector architecture, these are the questions that determine which patterns you need:</p>
<p>| Question | Impact |
|----------|--------|
| Do I need infrastructure telemetry (host metrics, disk logs)? | Determines whether you need a local collector or can use direct SDK export |
| Am I on a managed container platform (Fargate, ACA)? | Forces sidecar pattern instead of DaemonSet |
| Do I need centralized filtering, PII redaction, or credential isolation? | Adds a gateway stage |
| Do I need tail-based sampling? | Adds a sampling stage with load-balancing exporter and traceID routing |
| Do I want span-derived metrics (RED metrics)? | Requires spanmetrics before sampling in a two-step pipeline |
| How much data loss is acceptable during outages? | Determines in-memory queues vs. persistent queues vs. Kafka — applied to whichever stage needs protection |
| What is my expected data volume? | Determines whether capabilities can be co-located in a single deployment or need dedicated stages |</p>
<p>The answers to these questions don't map to a single "correct" architecture. They constrain the design space, and within those constraints, you make trade-offs between simplicity and capability.</p>
<h2 id="exploringthesepatternsinteractively">Exploring these patterns interactively</h2>
<p>If you'd rather explore how these building blocks compose than assemble them by hand, <a href="https://mlunadia.github.io/otel-blueprints/">OpenTelemetry Blueprints</a> is an open-source tool that generates reference architectures from your requirements.</p>
<p>Toggle your environment, signals, volume, resilience, and processing needs — and get a composed diagram with animated data flow, interactive tooltips, and reference collector configurations you can open directly in <a href="https://www.otelbin.io">OTelBin</a> for validation.</p>
<p><a href="https://mlunadia.github.io/otel-blueprints/"><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5253df89f15ea6d5/6a7f19003ce8e27730cf5789/architecture.png" alt="Screenshot of a composed architecture diagram showing a Kubernetes cluster with DaemonSet agent, processing tier, and observability backend" /></a></p>
<p>The generated configurations export via OTLP, so they work with any OTLP-compatible backend — including <a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic Observability</a>, which natively accepts and stores OTLP traces, metrics, and logs.</p>
<p>The architectures Blueprints generates are reference compositions — starting points for understanding how the building blocks fit together, not turnkey deployments. Every architecture should be adapted to your organisation's scale, security, networking, and compliance requirements. The patterns might combine or overlap differently in your environment than in anyone else's, and that's the point.</p>
<h2 id="getstarted">Get started</h2>
<p>The architectures described here export over OTLP, so they work with any compatible backend. If you don't have one yet, the fastest way to see your telemetry flowing end-to-end is with Elastic Observability — it natively ingests OTLP traces, metrics, and logs with no additional configuration.</p>
<ol>
<li><a href="https://cloud.elastic.co/serverless-registration?onboarding_token=observability">Start a free trial</a> on Elastic Cloud Serverless — no credit card required.</li>
<li>Point your collector's OTLP exporter at the managed OTLP endpoint.</li>
<li>Explore your traces, metrics, and logs in Kibana within minutes.</li>
</ol>
<p>Check out these resources to go further:</p>
<ul>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/motlp">Elastic's managed OTLP endpoint documentation</a></li>
<li><a href="https://www.elastic.co/docs/reference/opentelemetry/edot-collector">EDOT Collector — Elastic's distribution of the OpenTelemetry Collector</a></li>
<li><a href="https://mlunadia.github.io/otel-blueprints/">OpenTelemetry Blueprints — generate reference architectures interactively</a></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-collector-reference-architectures</link>
    <guid isPermaLink="false">opentelemetry-collector-reference-architectures</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Miguel Luna]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d7f731ee0a1652e/6a7f19032f00b219dfefef09/opentelemetry-collector-reference-architectures.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 31 Mar 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to Troubleshoot Kubernetes Pod Restarts & OOMKilled Events with Agent Builder]]></title>
    <description><![CDATA[Learn how to immediately troubleshoot Kubernetes pod restarts and OOMKilled events with Elastic Agent Builder. We’ll show how to detect, analyze, and remediate failures.]]></description>
    <content:encoded><![CDATA[<h2 id="initialsummary">Initial Summary</h2>
<ul>
<li>Detect Kubernetes pod restarts and OOMKill events using Elastic Agent Builder</li>
<li>Analyze CPU and memory pressure using ES|QL over Kubernetes metrics</li>
<li>Generate troubleshooting summaries and remediation guidance</li>
</ul>
<p>This article explains how to use <a href="https://www.elastic.co/search-labs/blog/elastic-ai-agent-builder-context-engineering-introduction">Elastic Agent Builder</a> to automatically detect, analyze, and remediate Kubernetes pod failures caused by resource pressure (CPU and memory), with a focus on pods experiencing frequent restarts and OOMKilled events. Elastic Agent Builder lets you quickly create precise agents that utilize all your data with powerful tools (such as ES|QL queries), chat interfaces, and custom agents.</p>
<h2 id="introductionwhatistheelasticagentbuilder">Introduction: What is the Elastic Agent Builder?</h2>
<p>Elastic has an AI Agent embedded that you can use to get more insights from all of the logs, metrics and traces that you’ve ingested. While that’s great, you can take it one step further and streamline the process by creating tools that the agent can use.</p>
<p>Giving the agent tools means it spends less time ‘thinking’ and quickly gets to assessing what’s important to you. For example, if I have a Kubernetes environment that needs monitoring, and I want to keep an eye on pod restarts and memory and CPU usage without hanging out at the terminal, I can have Elastic alert me if something goes wrong. </p>
<p>Having an alert is great, but how do I get the bigger picture, faster? You need to know what service is having (or creating) the issues, why, and how to fix it.</p>
<h2 id="assumptions">Assumptions</h2>
<p>This guide assumes:</p>
<ul>
<li>A running Kubernetes cluster</li>
<li>An Elastic Observability deployment</li>
<li>Kubernetes metrics indexed in Elastic</li>
</ul>
<h2 id="step1createanewelasticagent">Step 1: Create a New Elastic Agent</h2>
<p>In Elastic Observability, use the top search bar to search for Agents. Create a new agent.</p>
<p>This agent is going to be the Kubernetes Pod Troubleshooter agent, designed to help users troubleshoot pod restarts, OOMKill terminations and evaluate CPU or memory pressure. </p>
<p>The Kubernetes Pod Troubleshooter agent will:</p>
<ol>
<li>Identify pods that have restarted more than once</li>
<li>Filter for pods that are not in a running state</li>
<li>Retrieve the container termination reason (e.g., OOMKilled)</li>
<li>Analyze CPU and memory utilization for affected services</li>
<li>Flag resource utilization above 60% (warning) and 80% (critical)</li>
<li>Provide remediation recommendations</li>
</ol>
<p>The agent requires instructions to guide how the agent behaves when interacting with tools or responding to queries. This description can set tone, priorities or special behaviours. The instructions below tell the agent to execute the steps outlined above. </p>
<pre><code>You will help users troubleshoot problematic pods by searching the metrics for pods that have restarted more than once and the status is not running. Pods that have the highest number of restarts will be returned to the user.
Once the containers that are not running and have restarted multiple times are found you will use their container ID or image name to to look up the container status reason and reason for the last termination. You will return that reason to the user.
You will also begin basic troubleshooting steps, such as checking  for insufficient cluster resources (CPU or memory) from the metrics and tools available.
Any CPU or memory utilization percentages over 60%, and definitely over 80% should be flagged to the user with remediation steps.
</code></pre>
<p>Getting answers quickly is critical when troubleshooting high-value systems and environments. Using Tools ensures that the workflow is repeatable and that you can trust the results. You also get complete oversight of the process, as the Elastic Agent outlines every step and query that it took and you can explore the results in Discover.</p>
<p>You will create custom tools that the agent will run to complete the Kubernetes troubleshooting tasks that the custom instructions references such as: <code>look up the container status reason and reason for the last termination</code> and <code>checking&amp;nbsp; for insufficient cluster resources (CPU or memory).</code></p>
<h2 id="step2createtoolspodrestarts">Step 2: Create Tools - Pod Restarts</h2>
<p>The first tool takes the Kubernetes metrics and assesses if the pod has restarted and it has a last terminated reason, and if it has the agent will present that information to the user.</p>
<p>This <code>pod-restarts</code> tool uses a custom ES|QL query that interrogates the Kubernetes metrics data coming from OTel.</p>
<p>The ES|QL query:</p>
<ol>
<li>Filters for containers that have restarted and have a reason for termination; then</li>
<li>Calculates the number of restarts; then</li>
<li>Returns the number of restarts and termination reason per service.</li>
</ol>
<pre><code>FROM metrics-k8sclusterreceiver.otel-default
| WHERE metrics.k8s.container.restarts &gt; 0
| WHERE resource.attributes.k8s.container.status.last_terminated_reason IS NOT NULL
| STATS total_restarts = SUM(metrics.k8s.container.restarts),
        reasons = VALUES(resource.attributes.k8s.container.status.last_terminated_reason) 
  BY resource.attributes.service.name
| SORT total_restarts DESC
</code></pre>
<h2 id="step3createtoolsservicememory">Step 3: Create Tools - Service Memory</h2>
<p>The custom tools can take input variables, which increases speed and accuracy of the results.</p>
<p>Common reasons for pods not scheduling, or restarting often, is due to the cluster or nodes being under-resourced. The <code>pod-restarts</code> tool returns services that have many restarts and OOMKill termination reasons, which indicate memory pressure.</p>
<p>The <code>eval-pod-memory</code> tool is a custom ES|QL that:</p>
<ol>
<li>Filters for metrics data that match the service name returned from the <code>pod-restarts</code> tool within the last 12 hours; then</li>
<li>Converts memory usage, requests, limits and utilization into megabytes; then</li>
<li>Calculates the average of each of those metrics; then</li>
<li>Groups them into 1 minute groupings and sorts them.</li>
</ol>
<pre><code>FROM metrics-*
| WHERE resource.attributes.service.name == ?servicename
| WHERE @timestamp &gt;= NOW() - 12 hours
| EVAL
  memory_usage_mb = metrics.container.memory.usage / 1024 / 1024,
   memory_request_mb = metrics.k8s.container.memory_request / 1024 / 1024,
   memory_limit_mb = metrics.k8s.container.memory_limit / 1024 / 1024,
   memory_utilization_pct = metrics.k8s.container.memory_limit_utilization * 100
| STATS
   avg_memory_usage = AVG(memory_usage_mb),
   avg_memory_request = AVG(memory_request_mb),
   avg_memory_limit = AVG(memory_limit_mb),
   avg_memory_utilization = AVG(memory_utilization_pct)
   BY bucket = BUCKET(@timestamp, 1 minute)
| SORT bucket ASC
</code></pre>
<h2 id="step4createtoolsservicecpu">Step 4: Create Tools: Service CPU</h2>
<p>As CPU usage is another common reason for pods to fail scheduling or be stuck in endless restart loops, the next tool will evaluate CPU usage, requests and limits.</p>
<p>The <code>eval-pod-cpu</code> tool is a custom ES|QL that:</p>
<ol>
<li>Filters for metrics data that match the service name returned from the <code>pod-restarts</code> tool within the last 12 hours; then</li>
<li>Calculates the average for CPU usage, CPU request utilization and CPU limit utilization.</li>
</ol>
<pre><code>FROM metrics-kubeletstatsreceiver.otel-default
| WHERE k8s.container.name == ?servicename OR resource.attributes.k8s.container.name == ?servicename
| STATS
  avg_cpu_usage = AVG(container.cpu.usage),
  avg_cpu_request_utilization = AVG(k8s.container.cpu_request_utilization) * 100,
  avg_cpu_limit_utilization = AVG(k8s.container.cpu_limit_utilization) * 100
| LIMIT 100
</code></pre>
<h2 id="step5assigntoolstokubernetespodtroubleshooteragent">Step 5: Assign Tools to Kubernetes Pod Troubleshooter Agent</h2>
<p>Once all of the tools are built you need to assign them to the agent.</p>
<p>This image shows the Kubernetes Pod Troubleshooter agent with the three tools: <code>pod-restarts</code>, <code>eval-pod-cpu</code> and <code>eval-pod-memory</code> assigned to it and active.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48d2d513f519c351/6a7f1bc4ea068d5c4ef0a2eb/kubernetes-pod-troubleshooter.png" alt="kubernetes-pod-troubleshooter" /></p>
<h2 id="step6testthekubernetespodtroubleshooteragent">Step 6: Test the Kubernetes Pod Troubleshooter Agent</h2>
<p>To simulate memory pressure the Open Telemetry demo is running inside the cluster. Artificially lowering the memory requests and limits and increasing the service load will cause pods to restart.</p>
<p>To do this to the open telemetry demo in your cluster, follow these steps. </p>
<p>Reduce the cart service to one replica by scaling the deployment. Once that is complete, change the resources on the deployment by lowering the memory requests and limits as shown in this command:</p>
<pre><code>kubectl -n otel-demo scale deploy/cart --replicas=1
kubectl -n otel-demo set resources deploy/cart -c cart --requests=memory=50Mi --limits=memory=60Mi
</code></pre>
<p>The OpenTelemetry demo application comes with a load-generator. This is used to simulate requests to the demo site by modifying the users and spawn rate in the load generator deployment, as shown in this command:</p>
<pre><code>kubectl -n otel-demo set env deploy/load-generator LOCUST_USERS=800 LOCUST_SPAWN_RATE=200 LOCUST_BROWSER_TRAFFIC_ENABLED=false
</code></pre>
<p>If you list all of your pods in the cluster or namespace, you should begin to see restarts.</p>
<p>You can now chat with the Kubernetes Pod Troubleshooter agent and ask “Are any of my Kubernetes pods having issues?”.</p>
<p>The screenshot shows the final response from the Kubernetes Pod Troubleshooter agent. It provides a problem summary of its findings from each tool, showing which services were experiencing the most restarts and memory and CPU utilization. </p>
<p>The threshold interpretations were described in the initial agent instructions, where &gt;60% utilization is a warning (sustained pressure) and &gt;80% utilization is critical (high likelihood of restarts or throttling). This aligns with findings presented by the Kubernetes Pod Troubleshooter agent, where the services that had the highest restarts were all above 90% memory utilization. The agent needs clearly defined threshold values to correctly assess the returned memory and CPU utilization values. </p>
<p>Problem summary returned by the Kubernetes Pod Troubleshooter agent:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte59fc6eebe4bf00b/6a7f1bc7bd2198dbc27584d1/problem-summary-by-Kubernetes.png" alt="problem summary by Kubernetes" /></p>
<h2 id="conclusionandfinalthoughts">Conclusion and Final Thoughts</h2>
<p>Elastic Agent Builder enables fast, repeatable Kubernetes troubleshooting by combining ES|QL-driven analysis with constrained AI reasoning.</p>
<p>The creation of custom tools that use specific ES|QL queries combined with downstream queries that take input variables from the output of previous tools eliminates or reduces error propagation and hallucinations. In comparison to generic AI troubleshooting without purpose-built tools, you run the risk of it analyzing too many services (that aren’t relevant to the issue at hand). This will slow down the thinking process and generate longer responses, increasing the likelihood of error propagation and hallucinations. </p>
<p>With the Elastic Agent Builder, you can inspect the output of every tool if you need to, to explore and verify the outputs.</p>
<p>Having a succinct problem summary is a game-changer, bringing your attention straight to the most affected services.</p>
<p>Reasoning returned by the Kubernetes Pod Troubleshooter agent:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c346782edc71fd3/6a7f1bcaea068d015bf0a2ef/return-pod-troubleshooter-agent.png" alt="summary-returned-kubernetes-pod-troubleshooter" /></p>
<p>Not only that, but the agent can go one step further and offer recommendations for remediation based on what outputs the tools delivered.</p>
<p>Remediation recommendation returned by the Kubernetes Pod Troubleshooter agent:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc552e9e4a8ddd2dc/6a7f1bcd73d9bdaabe29df86/remediation-recommendation-kubernetes-pod-troubleshooter.png" alt="remediation-recommendation-kubernetes-pod-troubleshooter" /></p>
<p>Sign up for <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> and try this out with your Kubernetes clusters.</p>
<h2 id="frequentlyaskedquestions">Frequently Asked Questions</h2>
<p><strong>1. When to use the Elastic Agent Builder for Troubleshooting</strong></p>
<p>Use the Elastic Agent Builder for Troubleshooting that works best if:</p>
<ul>
<li><p>You need repeatable, auditable troubleshooting workflows</p></li>
<li><p>You want deterministic analysis instead of free-form AI responses</p></li>
<li><p>You’re investigating something that is reported in the logs or metrics (i.e. pod restarts, OOMKills, or resource pressure)</p></li>
<li><p>You want to reduce mean time to resolution (MTTR)</p></li>
</ul>
<p><strong>2. Do I need OpenTelemetry to use Elastic Agent Builder for Kubernetes troubleshooting?</strong> </p>
<p>No, you don’t need to use OpenTelemetry. You have two options:</p>
<ul>
<li><p>You can collect logs and metrics from Kubernetes using the Elastic Agent; or </p></li>
<li><p>You can collect logs, traces and metrics with the Elastic Distro for OTel (EDOT) Collector</p></li>
</ul>
<p>When following the steps above, this would change the field names that are used in the tools above. For example, <code>kubernetes.container.memory.usage.bytes</code> vs <code>metrics.container.memory.usage</code>.</p>
<p><strong>3. Can this agent be adapted for node-level failures?</strong> </p>
<p>Yes, Elastic has hundreds of <a href="https://www.elastic.co/docs/reference/fleet#integrations">integrations</a>, including AWS (for EKS), Azure (for AKS), Google Cloud (for GKE), as well as host operating system monitoring.</p>
<p>The queries shown above would be modified to use the correct field.</p>
<p><strong>4. Can these tools be reused in automation workflows?</strong> </p>
<p>Yes, <a href="https://www.elastic.co/search-labs/blog/elastic-workflows-automation">Elastic Workflows</a> can reuse the same scripted automations and AI agents you build in Elastic. An agent can handle the initial analysis and investigation (reducing manual effort), and the workflow can continue with structured steps, such as running Elasticsearch queries, transforming data, branching on conditions and calling external APIs or tools like Slack, Jira and PagerDuty. Workflows can also be exposed to Agent Builder as reusable tools, just like the tool created in this guide.</p>
<p>For more advanced automation from a similar scenario as described in this guide, learn how to <a href="https://www.elastic.co/observability-labs/blog/agentic-cicd-kubernetes-mcp-server">integrate AI agents into GitHub Actions to monitor K8s health and improve deployment reliability via Observability</a>.</p>
<p><strong>5. Can these tools be triggered by alerts?</strong> </p>
<p>Yes, alerts can trigger <a href="https://www.elastic.co/search-labs/blog/elastic-workflows-automation">Elastic Workflows</a>, and pass the alert context to the workflow. This workflow may be integrated with an Elastic Agent, as described above.</p>
<p>Additionally, Elastic Alerts allow you to publish investigation guides alongside alerts so an SRE has all of the information they need to begin investigating. Any troubleshooting or investigative agents can be linked to from the investigation guide, meaning the SRE doesn’t have to follow manual processes outlined in an investigation guide and instead let the agent handle the manual, repetitive investigations.</p>
<p><strong>6. How can I get started with Agent Builder?</strong></p>
<p>Sign up for <a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a>, a new fully managed, stateless architecture that auto-scales no matter your data, usage, and performance needs.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/troubleshoot-kubernetes-pod-restarts-oomkilled-elastic-agent-builder</link>
    <guid isPermaLink="false">troubleshoot-kubernetes-pod-restarts-oomkilled-elastic-agent-builder</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <dc:creator><![CDATA[Jen Luther Thomas]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9fd318a10c893b12/6a7f1bd09090b02a4984ee3d/cover.png" length="0" type="image/png"/>
    <pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Debugging Azure Networking for Elastic Cloud Serverless]]></title>
    <description><![CDATA[Learn how Elastic SREs uncovered and resolved unexpected packet loss in Azure Kubernetes Service (AKS), impacting Elastic Cloud Serverless performance.]]></description>
    <content:encoded><![CDATA[<h2 id="summaryoffindings">Summary of Findings</h2>
<p>Elastic's Site Reliability Engineering team (SRE) observed unstable throughput and packet loss in Elastic Cloud Serverless running on Azure Kubernetes Service (AKS). After investigation, we identified the primary contributing factors to be RX ring buffer overflows and kernel input queue saturation on SR-IOV interfaces. To address this, we increased RX buffer sizes and adjusted the netdev backlog, which significantly improved network stability.</p>
<h2 id="settingthescene">Setting the Scene</h2>
<p><a href="https://www.elastic.co/cloud/serverless">Elastic Cloud Serverless</a> is a fully managed solution that allows you to deploy and use Elastic for your use cases without managing the underlying infrastructure. Built on Kubernetes, it represents a shift in how you interact with Elasticsearch. Instead of managing clusters, nodes, data tiers, and scaling, you create serverless projects that are fully managed and automatically scaled by Elastic. This abstraction of infrastructure decisions allows you to focus solely on gaining value and insight from your data.</p>
<p>Elastic Cloud Serverless is generally available (GA) on AWS, GCP and currently in <a href="https://www.elastic.co/guide/en/serverless/current/regions.html">Technical Preview on Azure</a>. As part of preparing Elastic Cloud Serverless GA on Azure, we have been conducting extensive performance and scalability tests to ensure that our users get a consistent and reliable user experience.</p>
<p>In this post, we’ll take you behind the scenes of a deep technical investigation into a surprising performance issue that affected Serverless Elasticsearch in our Azure Kubernetes clusters. At first, the network seemed like the least likely place to look, especially with a high-speed 100 Gb/s interface on the host backing it. But as we dug deeper, with help from the Microsoft Azure team, that’s exactly where the problem led us.</p>
<h2 id="unexpectedresults">Unexpected Results!</h2>
<p>While the high-level architectures and system design patterns of the major cloud provider’s systems are often similar, the implementations are different, and these differences can have dramatic impacts on a system’s performance characteristics.</p>
<p>One of the most significant differences between the different cloud providers is that the underlying hypervisor software and server hardware of the Virtual Machines can vary significantly, even between instance families of the same provider.</p>
<p>There is no way to fully abstract the hardware away from an application like Elasticsearch. Fundamentally, its performance is dictated by the CPU, memory, disks, and network interfaces on the physical server. In preparation for the Elastic Cloud Serverless GA on Azure, our Elasticsearch Performance team kicked off large-scale load testing against Serverless Elasticsearch projects running on <a href="https://docs.azure.cn/en-us/aks/what-is-aks">Azure Kubernetes Service (AKS)</a>, using <a href="https://azure.microsoft.com/en-us/blog/azure-cobalt-100-based-virtual-machines-are-now-generally-available/">ARM-based VMs</a> (we’re big fans!). Throughout this process, we relied heavily on Elastic tools to analyse system behaviour, identify bottlenecks, and validate performance under load.</p>
<p>To perform these scale and load tests, the Elasticsearch Performance team use <a href="https://github.com/elastic/rally">Rally</a>, an open-source benchmarking tool designed to measure the performance of Elasticsearch clusters. The workload (or in Rally nomenclature, ‘Track’) used for these tests was the <a href="https://github.com/elastic/rally-tracks/tree/master/github_archive">GitHub Archive Track</a>. Rally collects and sends test telemetry using the <a href="https://www.elastic.co/docs/reference/elasticsearch/clients/python">official Python client</a> to a separate Elasticsearch cluster running <a href="https://www.elastic.co/observability">Elastic Observability</a>, which allows for monitoring and analysis during these scale and load tests in real time via <a href="https://www.elastic.co/docs/explore-analyze">Kibana</a>.</p>
<p>When we looked at the results, we observed that the indexing rate (the number of docs/s) for the Serverless projects was not only much lower than we had expected for the given hardware, but the throughput was also quite unstable. There were peaks and valleys, interspersed with frequent errors, whereas we were instead expecting a stable indexing rate for the duration of the test.</p>
<p>These tests are designed to push the system to its limits, and in doing so, they surfaced unexpected behavior in the form of unstable indexing throughput and intermittent errors. This was precisely the kind of problem we'd hoped to uncover prior to going GA — giving us the opportunity to work closely with Azure.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda5b934ddb81078b/6a7f04dd6c6eacac0cf13d9e/indexing-rate-before.png" alt="Indexing Rate with Packet Loss" />
<em>A Kibana visualisation of Rally telemetry, showing fluctuating Elasticsearch indexing rates alongside spikes in 5xx and 4xx HTTP error responses.</em></p>
<h2 id="debugging">Debugging!</h2>
<p>Debugging performance issues can feel a little bit like trying to find a <a href="https://www.youtube.com/watch?v=7AO4wz6gI3Q">‘Butterfly in a Hurricane’</a>, so it’s crucial that you take a methodological approach to analysing application and system performance.</p>
<p>Using methodologies helps you to be more consistent and thorough in your debugging, and avoids missing things. We started with the <a href="https://www.brendangregg.com/usemethod.html">Utilisation Saturation and Errors (USE) Method</a>, looking at both the client and server side to identify any obvious bottlenecks in the system. </p>
<p>Elastic's Site Reliability Engineers (SREs) maintain a suite of custom <a href="https://www.elastic.co/docs/solutions/observability/get-started/what-is-elastic-observability">Elastic Observability</a> dashboards designed to visualise data collected from various <a href="https://www.elastic.co/docs/extend/integrations/what-is-an-integration">Elastic Integrations</a>. These dashboards provide deep visibility into the health and performance of Elastic Cloud infrastructure and systems.</p>
<p>For this investigation, we leveraged a custom dashboard built using metrics and log data from the <a href="https://www.elastic.co/docs/reference/integrations/system">System</a> and <a href="https://www.elastic.co/docs/reference/integrations/linux">Linux</a> Integrations:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt254043f394cf02f3/6a7f04e01967ea412233038e/overview-dashboard.png" alt="Node Overview Dashboard" />
  <em>One of many Elastic Observability dashboards built and maintained by the SRE team.</em></p>
<p>Following the USE Method, these dashboards highlight resource utilisation, saturation, and errors across our systems. With their help, we quickly identified that the AKS nodes hosting the Elasticsearch pods under test were dropping thousands of packets per second.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt39b522be27af75cd/6a7f04e396b5a639a987b0e8/packet-loss-before.png" alt="Node Packet Loss Before Tuning" />
<em>A Kibana visualisation of <a href="https://www.elastic.co/docs/reference/integrations/system">Elastic Agent's System Integration</a>, showing the rate of packet drops per second for AKS nodes.</em></p>
<p>Dropping packets forces reliable protocols, such as TCP, to retransmit any missing packets. These retransmissions can introduce significant delays, which kills the throughput of any system where client requests are only triggered upon the previous request completion (known as a <a href="https://www.usenix.org/legacy/event/nsdi06/tech/full_papers/schroeder/schroeder.pdf">Closed System</a>).</p>
<p>To investigate further, we jumped onto one of the AKS nodes exhibiting the packet loss to check the basics. First off, we wanted to identify what type of packet drops or errors we’re seeing; is it for specific pods, or the host as a whole?</p>
<pre><code>root@aks-k8s-node-1:~# ip -s link show
2: eth0: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 7c:1e:52:be:ce:5e brd ff:ff:ff:ff:ff:ff
    RX:    bytes   packets errors dropped  missed   mcast
    373507935420 134292481      0       0       0      15
    TX:    bytes   packets errors dropped carrier collsns
    644247778936 303191014      0       0       0       0
3: enP42266s1: &lt;BROADCAST,MULTICAST,SLAVE,UP,LOWER_UP&gt; mtu 1500 qdisc mq master eth0 state UP mode DEFAULT group default qlen 1000
    link/ether 7c:1e:52:be:ce:5e brd ff:ff:ff:ff:ff:ff
    RX:    bytes   packets errors dropped  missed   mcast
    386782548951 307000571      0       0 5321081       0
    TX:    bytes   packets errors dropped carrier collsns
    655758630548 477594747      0       0       0       0
    altname enP42266p0s2
15: lxc0ca0ec41ecd2@if14: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether f6:f5:5e:c9:4e:fb brd ff:ff:ff:ff:ff:ff link-netns cni-3f90ab53-df66-cac5-bd19-9cea4a68c29b
    RX:    bytes   packets errors dropped  missed   mcast
    627954576078  54297550      0    1600       0       0
    TX:    bytes   packets errors dropped carrier collsns
    372155326349 133538064      0    3927       0       0
</code></pre>
<p>In this output you can see the <code>enP42266s1</code> interface is showing a significant number of packets in the <code>missed</code> column. That’s interesting, sure, but what does missed actually represent? And what is <code>enP42266s1</code>?</p>
<p>To understand, let’s look at roughly what happens when a packet arrives at the NIC:</p>
<ol>
<li>A packet arrives at the NIC from the network.</li>
<li>The NIC uses DMA (Direct Memory Access) to place the packet into a receive ring buffer allocated in memory by the kernel, mapped for use by the NIC. Since our NICs supports multiple hardware queues, each queue has its own dedicated ring buffer, IRQ, and NAPI context.</li>
<li>The NIC raises a hardware interrupt (IRQ) to notify the CPU that a packet is ready.</li>
<li>The CPU runs the NIC driver’s IRQ handler. The driver schedules a NAPI (New API) poll to defer packet processing to a softirq context. A mechanism in the Linux kernel that defers work to be processed outside of the hard IRQ context, for better batching and CPU efficiency, enabling improved scalability.</li>
<li>The NAPI poll function is executed in a softirq context (<code>NET_RX_SOFTIRQ</code>) and retrieves packets from the ring buffer. This polling continues either until the driver’s packet budget is exhausted (<code>net.core.netdev_budget</code>) or the time limit is hit (<code>net.core.netdev_budget_usecs</code>).</li>
<li>Each packet is wrapped in an <code>sk_buff</code> (socket buffer) structure, which includes metadata such as protocol headers, timestamps, and interface identifiers.</li>
<li>If the networking stack is slower than the rate at which NAPI fetches packets, excess packets are queued in a per-CPU backlog queue (via <code>enqueue_to_backlog</code>). The maximum size of this backlog is controlled by the <code>net.core.netdev_max_backlog</code> sysctl.</li>
<li>Packets are then handed off to the kernel’s networking stack for routing, filtering, and protocol-specific processing (e.g. TCP, UDP).</li>
<li>Finally, packets reach the appropriate socket receive buffer, where they are available for consumption by the user-space application.</li>
</ol>
<p>Visualised, it looks something like this:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte9587f90d0e129ad/6a7f04e69090b014ff84e6a4/packet-flow.png" alt="Linux Packet Flow Diagram" />
<em>Image © 2018 Leandro Moreira. Used under the <a href="https://opensource.org/licenses/BSD-3-Clause">BSD 3-Clause License</a>. Source: <a href="https://github.com/leandromoreira/linux-network-performance-parameters">GitHub repository</a>.</em></p>
<p>The <code>missed</code> counter is incremented whenever the NIC tries to DMA a packet into a fully occupied <a href="https://en.wikipedia.org/wiki/Circular_buffer">ring buffer</a>. The NIC essentially "misses" the chance to deliver the packet to the VM’s memory. However, what’s most interesting is that this counter seldom increments for VMs. This is because Virtual NICs are usually implemented as software via the hypervisor, which typically has much more flexible memory management compared to the physical NICs and can reduce the chance of ring buffer overflow.</p>
<p>We mentioned earlier that we’re building Azure Elasticsearch Serverless on top of Azure’s AKS service, which is important to note because all of our AKS nodes use an Azure feature called <a href="https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-overview">Accelerated Networking</a>. In this setup, network traffic is delivered directly to the VM’s network interface, bypassing the hypervisor. This is enabled by <a href="https://learn.microsoft.com/en-us/windows-hardware/drivers/network/overview-of-single-root-i-o-virtualization--sr-iov-">single root I/O virtualization (SR-IOV)</a>, which offers much lower latency and higher throughput than traditional VM networking. Each node is physically connected to a 100 Gb/s network interface, although the SR-IOV Virtual Function (VF) exposed to the VM typically provides only a fraction of that total bandwidth.</p>
<p>Despite the VM only having a fraction of the 100 Gb/s bandwidth, microbursts are still very possible. These physical interfaces are so fast that they can transmit and receive multiple packets in just nanoseconds, far faster than most buffers or processing queues can absorb. At these timescales, even a short-lived burst of traffic can overwhelm the receiver, leading to dropped packets and unpredictable latency.</p>
<p>Direct access to the SR-IOV interface means that our VMs are responsible for handling the hardware interrupts triggered by the NIC in a timely manner, if there's any delay in handling the hardware interrupt (e.g. waiting to be scheduled onto CPU by the hypervisor) then network packets can be missed!</p>
<h2 id="firstlynicleveltuning">Firstly - NIC-level Tuning</h2>
<p>Since we'd confirmed that our VMs were using SR-IOV, we established that the <code>enP42266s1</code> and <code>eth0</code> interfaces <a href="https://learn.microsoft.com/en-us/azure/virtual-network/accelerated-networking-how-it-works">were a bonded pair and acted as a single interface</a>. Knowing this, then we reasoned that we should be able to adjust the ring buffer values directly using <code>ethtool</code>. </p>
<pre><code>root@aks-k8s-node-1:~# ethtool -g enP42266s1
Ring parameters for enP42266s1:
Pre-set maximums:
RX:        8192
RX Mini:    n/a
RX Jumbo:    n/a
TX:        8192
Current hardware settings:
RX:        1024
RX Mini:    n/a
RX Jumbo:    n/a
TX:        1024
</code></pre>
<p>In the output above, we were using only 1/8th of the available ring buffer descriptors. These values were set by the OS defaults, which generally aim to balance performance and resource usage. Set too low, they risk packet drops under load; set too high, they can lead to unnecessary memory consumption. We knew that the VMs were backed by a virtual function carved out of the directly attached 100 Gb/s network interface, which is fast enough to deliver microbursts that could easily overwhelm small buffers. To better absorb those short, high-intensity bursts of traffic, we increased the NIC’s RX ring buffer size from 1024 to 8192. Using a privileged DaemonSet, we rolled out the change across all of our AKS nodes by installing <a href="https://en.wikipedia.org/wiki/Udev">a <code>udev</code> rule</a> to automatically increase the buffer size:</p>
<pre><code># Match Mellanox ConnectX network cards and run ethtool to update the ring buffer settings
ENV{INTERFACE}=="en*", ENV{ID_NET_DRIVER}=="mlx5_core", RUN+="/sbin/ethtool -G %k rx ${CONFIG_AZURE_MLX_RING_BUFFER_SIZE} tx ${CONFIG_AZURE_MLX_RING_BUFFER_SIZE}"
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt02e4acebbd3f43a7/6a7f04e93cab1c5eb70e44dd/packet-loss-after.png" alt="AKS Node Packet Loss after RX ring buffer change" />
<em>A Kibana visualisation of <a href="https://www.elastic.co/docs/reference/integrations/system">Elastic Agent's System Integration</a>, showing packet loss reduced by ~99% after increasing the NIC's RX ring buffer values.</em></p>
<p>As soon as the change had been applied to all AKS nodes we stopped ‘missing’ RX packets! Fantastic! As a result of this simple change we observed a significant improvement in our indexing throughput and stability. </p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt77d9ee02f03f3c15/6a7f04ece02fac548a5d621f/indexing-rate-after.png" alt="Indexing rate after RX ring buffer change" />
<em>A Kibana visualisation of Rally telemetry, showing stable and improved Elasticsearch indexing rates after increasing the RX ring buffer size.</em></p>
<p>Job done, right? Not quite..</p>
<h2 id="furtherimprovementskernelleveltuning">Further improvements - Kernel-level Tuning</h2>
<p>Eagle eyed readers may have noticed two things:</p>
<ol>
<li>In the previous screenshot, despite adjusting the physical RX ring buffer values, we still observed a small number of <code>dropped</code> packets on the TX side.</li>
<li>In the original <code>ip link -s show</code> output, one of the ‘logical’ interfaces used by the Elasticsearch pod was showing <code>dropped</code> packets on both the TX and RX sides.</li>
</ol>
<pre><code>15: lxc0ca0ec41ecd2@if14: &lt;BROADCAST,MULTICAST,UP,LOWER_UP&gt; mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether f6:f5:5e:c9:4e:fb brd ff:ff:ff:ff:ff:ff link-netns cni-3f90ab53-df66-cac5-bd19-9cea4a68c29b
    RX:    bytes   packets errors dropped  missed   mcast
    627954576078  54297550      0    1600       0       0
    TX:    bytes   packets errors dropped carrier collsns
    372155326349 133538064      0    3927       0       0
</code></pre>
<p>So, we continued to dig. We’d eliminated ~99% of the packet loss, and the remaining loss rate wasn’t as significant as what we’d started with, but we still wanted to understand why it was occurring even after adjusting the RX ring buffer size of the NIC. </p>
<p>So what does <code>dropped</code> represent, and what is this <code>lxc0ca0ec41ecd2</code> interface? <code>dropped</code> is similar to <code>missed</code>, but only occurs when packets are deliberately dropped by the kernel or network interface. Crucially though, it doesn’t tell you why a packet was dropped. As for the <code>lxc0ca0ec41ecd2</code> interface, we use the <a href="https://learn.microsoft.com/en-us/azure/aks/azure-cni-powered-by-cilium">Azure CNI Powered by Cilium</a> to provide the network functionality to our AKS clusters. Any pod spun up on an AKS node gets a ‘logical’ interface, which is a virtual ethernet (<code>veth</code>) pair that connects the pod’s network namespace with the host’s network namespace. It was here that we were dropping packets.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d0128045c8d8985/6a7f04efc2e91492a901685b/aks-node-network-topology.png" alt="AKS Node Networking Diragram" /></p>
<p>In our experience, packet drops at this layer are unusual, so we started digging deeper into the cause of the drops. There are numerous ways you can debug why a packet is being dropped, but one of the easiest is <a href="https://perfwiki.github.io/main/">to use <code>perf</code></a> attach to the <code>skb:kfree_skb</code> tracepoint. The "socket buffer" (<code>skb</code>) is the primary data structure used to represent network packets in the Linux kernel. When a packet is dropped, its corresponding socket buffer is usually freed, triggering the <code>kfree_skb</code> tracepoint. Using <code>perf</code> to attach to this event allowed us to capture stack traces to analyze the cause of the drops.</p>
<pre><code># perf record -g -a -e skb:kfree_skb
</code></pre>
<p>We left this to run for ~10 minutes or so to capture as many drops as possible, and then ‘heavily inspired’ by <a href="https://gist.github.com/bobrik/0e57671c732d9b13ac49fed85a2b2290">this GitHub Gist by Ivan Babrou</a>, we converted the stack traces into an ‘easier’ to read <a href="https://github.com/brendangregg/FlameGraph">Flamegraphs</a>:</p>
<pre><code># perf script | sed -e 's/skb:kfree_skb:.*reason:\(.*\)/\n\tfffff \1 (unknown)/' -e 's/^\(\w\+\)\s\+/kernel /' &gt; stacks.txt
cat stacks.txt | stackcollapse-perf.pl --all | perl -pe 's/.*?;//' | sed -e 's/.*irq_exit_rcu_\[k\];/irq_exit_rcu_[k];/' | flamegraph.pl --colors=java --hash --title=aks-k8s-node-1 --width=1440 --minwidth=0.005 &gt; aks-k8s-node-1.svg
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt845a231a6ac4aef0/6a7f04f2bdcff058c9c42aad/aks-packet-loss-flamegraph.png" alt="AKS Node Packet Loss Flamegraph" />
<em>A Flamegraph showing the various stack trace ancestry of packet loss.</em></p>
<p>The flamegraph here shows how often different functions appeared in stack traces for packets drops. Each box represents a function call and wider boxes mean the function appears more frequently in the traces. The stack's ancestry builds upward from the bottom with earlier calls, to the top with later calls.</p>
<p>Firstly, we quickly discovered that unfortunately the <code>skb_drop_reason</code> enum <a href="https://github.com/torvalds/linux/commit/c504e5c2f9648a1e5c2be01e8c3f59d394192bd3">was only added in Kernel 5.17</a> (Azure’s Node Image at the time was using 5.15). This meant that there was no single human readable message that told us why the packets were being dropped, instead all we got was <code>NOT_SPECIFIED</code>. To work out why packets were being dropped we needed to do a little sleuthing through the stack traces to work out what code paths were being taken when a packet was dropped.</p>
<p>In the flamegraph above you can see that many of the stack traces include <code>veth</code> driver function calls (e.g. <code>veth_xmit</code>), and many end abruptly with a call to the <code>enqueue_to_backlog</code> function. When many stacks end at the same function (like <code>enqueue_to_backlog</code>) it suggests that function is a common point where packets are being dropped. If you go back to the earlier explanation of what happens when a packet arrives at the NIC, you’ll notice that in step 7 we explained:</p>
<blockquote>
  <p><em>7. If the networking stack is slower than the rate at which NAPI fetches packets, excess packets are queued in a per-CPU backlog queue (via <code>enqueue_to_backlog</code>). The maximum size of this backlog is controlled by the <code>net.core.netdev_max_backlog</code> sysctl.</em></p>
</blockquote>
<p>Using the same privileged DaemonSet method for the RX ring buffer adjustment, we set the value of the <code>net.core.netdev_max_backlog</code> adjustable kernel parameter from 1000 to 32768:</p>
<pre><code>/usr/sbin/sysctl -w net.core.netdev_max_backlog=32768
</code></pre>
<p>This value was based on the fact we knew the hosts were using a 100 Gb/s SR-IOV NIC, even if the VM was allowed only a fraction of the total bandwidth. We acknowledge that it’s worth revisiting this value in the future to see if it can be better optimised to not waste extraneous memory, but at the time “perfect was the enemy of good”.</p>
<p>We re-ran the load tests and compared the three sets of results we’d collected thus far.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt625534040dadddf8/6a7f04f5fc63ab696a64c8f0/indexing-rate-final.png" alt="Final Indexing Rate Results" />
<em>A Kibana visualisation of Rally results, comparing impact to median throughput after each configuration change.</em></p>
<p>| Tuning Step | Packet Loss | Median indexing throughput |
|-------------|-------------|-----------------------------|
| Baseline    | High        | ~18,000 docs/s                   |
| +RX Buffer  | ~99% drop ↓ | ~26,000 (+ ~40% from baseline)                    |
| +Backlog &amp; +RX Buffer     | Near zero   | ~29,000 (+ ~60% from baseline)                    |</p>
<p>Here you can see the P50 of throughput in docs/s over the course of the hours-long load tests. Compared to the baseline, we saw a roughly <strong>~40%</strong> increase in throughput by only adjusting the RX ring buffer values, and a <strong>~50-60%</strong> increase with both the RX ring buffer and backlog changes! Hooray!</p>
<p>A great result and one more step on our journey towards better Serverless Elasticsearch performance.</p>
<h2 id="workingwithazure">Working with Azure</h2>
<p>It’s great that we were able to quickly identify and mitigate the majority of our packet loss issues, but since we were using AKS with AKS node images, it made sense to engage with Azure to understand why the defaults weren’t working for our workload.</p>
<p>We walked Azure through our investigation, mitigations and results, and asked for some additional validation of our mitigations. Azure Engineering confirmed that the host NICs were not discarding packets, which confirmed that everything arriving at the host level was passed through to the hypervisor on the host. Further investigation confirmed that no loss or discards were occurring to Azure network fabric, or internal to the hypervisor – which shifted focus from the host to the guest OS and why the guest OS kernel was slow when reading packets off of the <code>enP*</code> SR-IOV interfaces. </p>
<p>Given the complexity of our load testing scenario — which involved configuring multiple systems and tools, including <a href="https://www.elastic.co/observability">Elastic Observability</a>, we also developed a simplified reproduction of the packet loss issue using <a href="https://github.com/esnet/iperf"><code>iperf3</code></a>. This simplified test was created specifically to share with Azure for targeted analysis, and added to the broader monitoring and analysis enabled by Elastic Observability and Rally.</p>
<p>With this reproduction Azure was able to confirm the increasing <code>missed</code> and <code>dropped</code> packet counters we had observed, and confirmed the increased RX ring buffer and <code>netdev_max_backlog</code> increase as the recommended mitigations.</p>
<h2 id="conclusion">Conclusion</h2>
<p>While cloud providers offer various abstractions to manage your resources, the underlying hardware ultimately determines your application's performance and stability. High-performance hardware often requires tuning at the operating system level, well beyond the default settings most environments ship with. In managed platforms like AKS, where Azure controls both the node images and infrastructure, it is easy to overlook the impact of low-level configurations such as network device ring buffer sizes or sysctls like <code>net.core.netdev_max_backlog</code>.</p>
<p>Our experience shows that even with the convenience of a managed Kubernetes service, performance issues can still emerge if these hardware parameters are not tuned appropriately. It was tempting to assume that high-speed 100 Gb/s network interfaces, directly attached to the VM using SR-IOV would eliminate any chance of network-related bottlenecks. In reality, that assumption didn’t hold up. </p>
<p>Engaging early with Azure was essential, as they provided deeper visibility into the underlying infrastructure and worked with us to tune low-level, performance-critical settings. Combined with thorough load and scale testing and robust observability using tools like Elastic Observability, this collaboration helped us detect and rectify the issue early in order to deliver a consistent, reliable, and high-performing experience for our users.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/debugging-aks-packet-loss</link>
    <guid isPermaLink="false">debugging-aks-packet-loss</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Brad Deam,Christos Argyropoulos]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt374b162aa72f83be/6a7f04f8ead8ec51eebaa4b1/debugging-aks-packet-loss.png" length="0" type="image/png"/>
    <pubDate>Thu, 05 Jun 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Dynamic workload discovery on Kubernetes now supported with EDOT Collector]]></title>
    <description><![CDATA[Discover how Elastic's OpenTelemetry Collector leverages Kubernetes pod annotations providing dynamic workload discovery and improves automated metric and log collection for Kubernetes clusters.]]></description>
    <content:encoded><![CDATA[<p>At Elastic, Kubernetes is one of the most significant observability use cases we focus on.
We want to provide the best onboarding experience and lifecycle management based on real-world GitOps best practices. </p>
<p>OpenTelemetry recently <a href="https://opentelemetry.io/blog/2025/otel-collector-k8s-discovery/">published a blog</a> on how to do <code>Autodiscovery based on Kubernetes Pods' annotations</code> with the OpenTelemetry Collector. </p>
<p>In this blog post, we will talk about how to use this Kubernetes-related feature of the OpenTelemetry Collector,
which is already available with the Elastic Distribution of the OpenTelemetry (EDOT) Collector.</p>
<p>In addition to this feature, at Elastic, we heavily invest in making OpenTelemetry the best, standardized ingest solution for Observability.
You might already have seen us focusing on:</p>
<ul>
<li><p><a href="https://www.elastic.co/blog/ecs-elastic-common-schema-otel-opentelemetry-announcement">Semantic Conventions standardization</a></p></li>
<li><p>significant <a href="https://www.elastic.co/observability-labs/blog/elastics-collaboration-opentelemetry-filelog-receiver">log collection improvements</a></p></li>
<li><p>various other topics around <a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-go-applications-opentelemetry">instrumentation</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-donation-proposal-to-contribute-profiling-agent-to-opentelemetry">profiling</a></p></li>
</ul>
<p>Let's walk you through a hands-on journey using the EDOT Collector covering various use cases you might encounter in the real world, highlighting the capabilities of this powerful feature.</p>
<h2 id="configuringedotcollector">Configuring EDOT Collector</h2>
<p>The Collector’s configuration is not our main focus here, since based on the nature of this feature it is minimal,
letting workloads define how they should be monitored.</p>
<p>To illustrate the point, here is the Collector configuration snippet that enables the feature for both logs and metrics:</p>
<pre><code>receivers:
    receiver_creator/metrics:
      watch_observers: [k8s_observer]
      discovery:
        enabled: true
      receivers:

    receiver_creator/logs:
      watch_observers: [k8s_observer]
      discovery:
        enabled: true
      receivers:
</code></pre>
<p>You can include the above in the EDOT’s Collector configuration, specifically the
<a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L339">receivers’ section</a>.</p>
<p>Since logs collection in our examples will happen from the discovery feature make sure that the static filelog receiver
<a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L348">configuration block</a> is removed
and its <a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L193"><code>preset</code></a>
is disabled (i.e. set to <code>false</code>) to avoid having log duplication.</p>
<p>Make sure that the receiver creator is properly added in the pipelines for
<a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L471">logs</a>
(in addition to removing the <code>filelog</code> receiver completely)
and <a href="https://github.com/elastic/elastic-agent/blob/v9.0.0-rc1/deploy/helm/edot-collector/kube-stack/values.yaml#L484">metrics</a>
respectively.</p>
<p>Ensure that <a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/v0.122.0/extension/observer/k8sobserver/README.md"><code>k8sobserver</code></a>
is enabled as part of the extensions:</p>
<pre><code>extensions:
  k8s_observer:
    observe_nodes: true
    observe_services: true
    observe_ingresses: true

// ...

service:
  extensions: [k8s_observer]
</code></pre>
<p>Last but not least, ensure the log files' volume is mounted properly:</p>
<pre><code>volumeMounts:
 - name: varlogpods
   mountPath: /var/log/pods
   readOnly: true

volumes:
  - name: varlogpods
    hostPath:
      path: /var/log/pods
</code></pre>
<p>Once the configuration is ready follow the <a href="https://www.elastic.co/docs/reference/opentelemetry/quickstart/">Kubernetes quickstart guides on how to deploy the EDOT Collector</a>.
Make sure to replace the <code>values.yaml</code> file linked in the quickstart guide with the file that includes the above-described modifications.</p>
<h3 id="collectingmetricsfrommovingtargetsbasedontheirannotations">Collecting Metrics from Moving Targets Based on Their Annotations</h3>
<p>In this example, we have a Deployment with a Pod spec that consists of two different containers.
One container runs a Redis server, while the other runs an NGINX server. Consequently, we want to provide
different hints for each of these target containers.</p>
<p>The annotation-based discovery feature supports this, allowing us to specify metrics annotations
per exposed container port.</p>
<p>Here is how the complete spec file looks:</p>
<pre><code>apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-conf
data:
  nginx.conf: |
    user  nginx;
    worker_processes  1;
    error_log  /dev/stderr warn;
    pid        /var/run/nginx.pid;
    events {
      worker_connections  1024;
    }
    http {
      include       /etc/nginx/mime.types;
      default_type  application/octet-stream;

      log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                        '$status $body_bytes_sent "$http_referer" '
                        '"$http_user_agent" "$http_x_forwarded_for"';
      access_log  /dev/stdout main;
      server {
          listen 80;
          server_name localhost;

          location /nginx_status {
              stub_status on;
          }
      }
      include /etc/nginx/conf.d/*;
    }
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
      annotations:
        # redis container port hints
        io.opentelemetry.discovery.metrics.6379/enabled: "true"
        io.opentelemetry.discovery.metrics.6379/scraper: redis
        io.opentelemetry.discovery.metrics.6379/config: |
          collection_interval: "20s"
          timeout: "10s"

        # nginx container port hints
        io.opentelemetry.discovery.metrics.80/enabled: "true"
        io.opentelemetry.discovery.metrics.80/scraper: nginx
        io.opentelemetry.discovery.metrics.80/config: |
          endpoint: "http://`endpoint`/nginx_status"
          collection_interval: "30s"
          timeout: "20s"
    spec:
      volumes:
      - name: nginx-conf
        configMap:
          name: nginx-conf
          items:
            - key: nginx.conf
              path: nginx.conf
      containers:
        - name: webserver
          image: nginx:latest
          ports:
            - containerPort: 80
              name: webserver
          volumeMounts:
            - mountPath: /etc/nginx/nginx.conf
              readOnly: true
              subPath: nginx.conf
              name: nginx-conf
        - image: redis
          imagePullPolicy: IfNotPresent
          name: redis
          ports:
            - name: redis
              containerPort: 6379
              protocol: TCP
</code></pre>
<p>When this workload is deployed, the Collector will automatically discover it and identify the specific annotations.
After this, two different receivers will be started, each one responsible for each of the target containers.</p>
<h3 id="collectinglogsfrommultipletargetcontainers">Collecting Logs from Multiple Target Containers</h3>
<p>The annotation-based discovery feature also supports log collection based on the provided annotations.
In the example below, we again have a Deployment with a Pod consisting of two different containers,
where we want to apply different log collection configurations.
We can specify annotations that are scoped to individual container names:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: busybox-logs-deployment
  labels:
    app: busybox
spec:
  replicas: 1
  selector:
    matchLabels:
      app: busybox
  template:
    metadata:
      labels:
        app: busybox
      annotations:
        io.opentelemetry.discovery.logs.lazybox/enabled: "true"
        io.opentelemetry.discovery.logs.lazybox/config: |
          operators:
            - id: container-parser
              type: container
            - id: some
              type: add
              field: attributes.tag
              value: hints-lazybox
        io.opentelemetry.discovery.logs.busybox/enabled: "true"
        io.opentelemetry.discovery.logs.busybox/config: |
          operators:
            - id: container-parser
              type: container
            - id: some
              type: add
              field: attributes.tag
              value: hints-busybox
    spec:
      containers:
        - name: busybox
          image: busybox
          args:
            - /bin/sh
            - -c
            - while true; do echo "otel logs from busybox at $(date +%H:%M:%S)" &amp;&amp; sleep 5s; done
        - name: lazybox
          image: busybox
          args:
            - /bin/sh
            - -c
            - while true; do echo "otel logs from lazybox at $(date +%H:%M:%S)" &amp;&amp; sleep 25s; done
</code></pre>
<p>The above configuration enables two different filelog receiver instances, each applying a unique parsing configuration.
This is handy when we know how to parse specific technology logs, such as Apache server access logs.</p>
<h3 id="combiningbothmetricsandlogscollection">Combining Both Metrics and Logs Collection</h3>
<p>In our third example, we illustrate how to define both metrics and log annotations on the same workload.
This allows us to collect both signals from the discovered workload.
Below is a Deployment with a Pod consisting of a Redis server and a BusyBox container that performs dummy log writing.
We can target annotations to the port and container levels to collect metrics from the Redis server using
the Redis receiver, and logs from the BusyBox using the filelog receiver. Here’s how:</p>
<pre><code>apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis-deployment
  labels:
    app: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
      annotations:
        io.opentelemetry.discovery.metrics.6379/enabled: "true"
        io.opentelemetry.discovery.metrics.6379/scraper: redis
        io.opentelemetry.discovery.metrics.6379/config: |
          collection_interval: "20s"
          timeout: "10s"

        io.opentelemetry.discovery.logs.busybox/enabled: "true"
        io.opentelemetry.discovery.logs.busybox/config: |
          operators:
            - id: container-parser
              type: container
            - id: some
              type: add
              field: attributes.tag
              value: hints
    spec:
      containers:
        - image: redis
          imagePullPolicy: IfNotPresent
          name: redis
          ports:
            - name: redis
              containerPort: 6379
              protocol: TCP
        - name: busybox
          image: busybox
          args:
            - /bin/sh
            - -c
            - while true; do echo "otel logs at $(date +%H:%M:%S)" &amp;&amp; sleep 15s; done
</code></pre>
<h3 id="exploreandanalysedatacomingfromdynamictargetsinelastic">Explore and analyse data coming from dynamic targets in Elastic</h3>
<p>Once the target Pods are discovered and the Collector has started collecting telemetry data from them,
we can then explore this data in Elastic. In Discover we can search for Redis and NGINX metrics as well as
logs collected from the Busybox container. Here is how it looks like:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9de2760a872abd6/6a85cc4118249c3b8a18f7df/discoverlogs.png" alt="Logs Discovery" />
<img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltba39c0a6fbf3af42/6a85cc459d2b718795f939ae/discovermetrics.png" alt="Metrics Discovery" /></p>
<h2 id="summary">Summary</h2>
<p>The examples above showcase how users of our OpenTelemetry Collector can take advantage of this new feature
— one we played a major role in developing.</p>
<p>For this, we leveraged our years of experience with similar features already supported in
<a href="https://www.elastic.co/guide/en/beats/metricbeat/current/configuration-autodiscover-hints.html">Metricbeat</a>,
<a href="https://www.elastic.co/guide/en/beats/filebeat/current/configuration-autodiscover-hints.html">Filebeat</a>, and
<a href="https://www.elastic.co/guide/en/fleet/current/hints-annotations-autodiscovery.html">Elastic-Agent</a>.
This makes us extremely happy and confident, as it closes the feature gap between Elastic's specific
monitoring agents and the OpenTelemetry Collector — making it even better.</p>
<p>Interested in learning more? Visit the
<a href="https://github.com/open-telemetry/opentelemetry-collector-contrib/blob/main/receiver/receivercreator/README.md#generate-receiver-configurations-from-provided-hints">documentation</a>
and give it a try by following our <a href="https://www.elastic.co/docs/reference/opentelemetry/quickstart/">EDOT quickstart guide</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/k8s-discovery-with-EDOT-collector</link>
    <guid isPermaLink="false">k8s-discovery-with-EDOT-collector</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Christos Markou,Alexander Wert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt65d5d28ff5f7fe2d/6a85cc489d2b71658bf939b2/k8s-discovery-new.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 01 Apr 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Tracing a RAG based Chatbot with Elastic Distributions of OpenTelemetry and Langtrace]]></title>
    <description><![CDATA[How to observe a OpenAI RAG based application using Elastic. Instrument the app, collect logs, traces, metrics, and understand how well the LLM is performing with Elastic Distributions of OpenTelemetry on Kubernetes with Langtrace.]]></description>
    <content:encoded><![CDATA[<p>Most AI-driven applications are currently focusing around increasing the value an end user, such as an SRE gets from AI. The main use case is the creation of various chatbots. These chatbots not only use large language models (LLMs), but are also using frameworks such as LangChain, and search to improve contextual information during a conversation (Retrieval Augmented Generation). Elastic’s sample <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a>, showcases how to use Elasticsearch with local data that has embeddings, enabling search to properly pull out the most contextual information during a query with a chatbot connected to an LLM of your choice. It's a great example of how to build out a RAG based application with Elasticsearch. However, what about monitoring the application?</p>
<p>Elastic provides the ability to ingest OpenTelemetry data with native OTel SDKs, the off the shelf OTel collector, or even Elastic’s Distributions of OpenTelemetry (EDOT). EDOT enables you to bring in logs, metrics and traces for your GenAI application and for K8s. However you will also generally need libraries to help trace specific components in your application. In tracing GenAI applications you can pick from a large set of libraries.</p>
<ul>
<li><p><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-openai-v2">OpenTelemetry OpenAI Instrumentation-v2</a> - allows tracing LLM requests and logging of messages made by the OpenAI Python API library. (note v2 is built by OpenTelemetry, the non v2 version is from a specific vendor and not OpenTelemetry)</p></li>
<li><p><a href="https://github.com/open-telemetry/opentelemetry-python-contrib/tree/main/instrumentation-genai/opentelemetry-instrumentation-vertexai">OpenTelemetry VertexAI Instrumentation</a> - allows tracing LLM requests and logging of messages made by the VertexAI Python API library</p></li>
<li><p><a href="https://docs.langtrace.ai/introduction">Langtrace</a> - commercially available library which supports all LLMs in one library, and all traces are also OTel native.</p></li>
<li><p>Elastic’s EDOT - which recently added tracing. See <a href="https://www.elastic.co/observability-labs/blog/openai-tracing-elastic-opentelemetry">blog</a>.</p></li>
</ul>
<p>As you can see OpenTelemetry is the defacto mechanism that is converging to collect and ingest. OpenTelemetry is growing its support for this but it is also early days.</p>
<p>In this blog, we will walk through how to, with minimal code, observe a RAG based chatbot application with tracing using Langtrace. We previously covered Langtrace in a <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">blog</a> to highlight tracing Langchain.</p>
<p>In this blog we used langtrace OpenAI, Amazon Bedrock, Cohere, and others in one library.</p>
<h2 id="prerequisites">Pre-requisites:</h2>
<p>In order to follow along, these few pre-requisites are needed</p>
<ul>
<li><p>An Elastic Cloud account — sign up now, and become familiar with Elastic’s OpenTelemetry configuration. With Serverless no version required. With regular cloud minimally 8.17</p></li>
<li><p>Git clone the <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a> and go through the <a href="https://www.elastic.co/search-labs/tutorials/chatbot-tutorial/welcome">tutorial</a> on how to bring it up and become more familiar.</p></li>
<li><p>An account on your favorite LLM (OpenAI, AzureOpen AI, etc), with API keys</p></li>
<li><p>Be familiar with EDOT to understand how we bring in logs, metrics, and traces from the application through the OTel Collector</p></li>
<li><p>Kubernetes cluster - I’ll be using Amazon EKS</p></li>
<li><p>Look at <a href="https://docs.langtrace.ai/introduction">Langtrace</a> documentation also.</p></li>
</ul>
<h2 id="applicationopentelemetryoutputinelastic">Application OpenTelemetry output in Elastic</h2>
<h3 id="chatbotragapp">Chatbot-rag-app</h3>
<p>The first item that you will need to get up and running is the ChatBotApp, and once up you should see the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt880964dd83511be5/6a7f0f443ce8e2feb5cf5471/Chatbotapp-general.png" alt="Chatbot app main page" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d932574c6143415/6a7f0f48ead8ecb92fbaa976/Chatbotapp-details.png" alt="Chatbot app working" /></p>
<p>As you select some of the questions you will set a response based on the index that was created in Elasticsearch when the app initializes. Additionally there will be queries that are made to LLMs.</p>
<h3 id="traceslogsandmetricsfromedotinelastic">Traces, logs, and metrics from EDOT in Elastic</h3>
<p>Once you have OTel Collector with EDOT configuration on your K8s cluster, and Elastic Cloud up and running you should see the following:</p>
<h4 id="logs">Logs:</h4>
<p>In Discover you will see logs from the Chatbotapp, and be able to analyze the application logs, any specific log patterns (saves you time in analysis), and view logs from K8s.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta108497f956043e0/6a7f0f4a1967ea4e31330847/Chatbotapp-logs.png" alt="Chatbot-logs" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt47cfac93de9cc224/6a7f0f4d5967e535e15dd3cd/Chatbotapp-log-patterns.png" alt="Chatbot-log-patterns" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltef3b11c21b429f54/6a7f0f5063e95922cc73dedd/Chatbotapp-logs-detailed.png" alt="Chatbot-log-details" /></p>
<h4 id="traces">Traces:</h4>
<p>In Elastic Observability APM, you can also see tha chatbot details, which include transactions, dependencies, logs, errors, etc.</p>
<p>When you look at traces, you will be able to see the chatbot interactions in the trace.</p>
<ol>
<li><p>You will see the end to end http call</p></li>
<li><p>Individual calls to elasticsearch</p></li>
<li><p>Specific calls such as invoke actions, and calls to the LLM</p></li>
</ol>
<p>You can also get individual details of the traces, and look at related logs, and metrics related to that trace,</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2a985aa53fe6e887/6a7f0f536693f8a83a66402b/Chatbotapp-service-traces.png" alt="CHatbot-traces" /></p>
<h4 id="metrics">Metrics:</h4>
<p>In addition to logs, and traces, any instrumented metrics will also get ingested into Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3757e1e587a76239/6a7f0f564c4bfb17ddccd60d/chatbot-reg-metrics.png" alt="Chatbot app metrics" /></p>
<h2 id="settingitallup">Setting it all up</h2>
<p>In order to properly set up the Chatbot-app on K8s with telemetry sent over to Elastic, a few things must be set up:</p>
<ol>
<li><p>Git clone the chatbot-rag-app, and modify one of the python files.</p></li>
<li><p>Next create a docker container that can be used in Kubernetes. The Docker build <a href="https://github.com/elastic/elasticsearch-labs/blob/main/example-apps/chatbot-rag-app/Dockerfile">here</a> in the Chatbot-app is good to use.</p></li>
<li><p>Collect all needed env variables. In this example we are using OpenAI, but the files can be modified for any of the LLMs. Hence you will have to get a few environmental variables loaded into the cluster. In the github repo there is a env.example for docker. You can pick and chose what is needed or not needed and adjust appropriately in the K8s file below.</p></li>
<li><p>Set up your K8s Cluster, and then install the OpenTelemetry collector with the appropriate yaml file and credentials. This will help collect K8s cluster logs and metrics also.</p></li>
<li><p>Utilize the two yaml files listed below to ensure you can run it on Kubernetes.</p></li>
</ol>
<ul>
<li><p>Init-index-job.yaml - Initiates the index in elasticsearch with the local corporate information</p></li>
<li><p>k8s-deployment-chatbot-rag-app.yaml - initializes the application frontend and backend.</p></li>
</ul>
<ol>
<li><p>Open the app on the load balancer URL against the chatbot-app service in K8s</p></li>
<li><p>Go to Elasticsearch and look at Discover for logs, go to APM and look for your chatbot-app and review the traces, and finally.</p></li>
</ol>
<h3 id="modifythecodefortracingwithlangtrace">Modify the code for tracing with Langtrace</h3>
<p>Once you curl the app and untar, go to the chatbot-rag-app directory:</p>
<pre><code>curl https://codeload.github.com/elastic/elasticsearch-labs/tar.gz/main | 
tar -xz --strip=2 elasticsearch-labs-main/example-apps/chatbot-rag-app
cd elasticsearch-labs-main/example-apps/chatbot-rag-app
</code></pre>
<p>Next open the <code>app.py</code> file in the <code>api</code> directory and add the following </p>
<pre><code>from opentelemetry.instrumentation.flask import FlaskInstrumentor

from langtrace_python_sdk import langtrace

langtrace.init(batch=False)

FlaskInstrumentor().instrument_app(app)
</code></pre>
<p>into the code:</p>
<pre><code>import os
import sys
from uuid import uuid4

from chat import ask_question
from flask import Flask, Response, jsonify, request
from flask_cors import CORS

from opentelemetry.instrumentation.flask import FlaskInstrumentor

from langtrace_python_sdk import langtrace

langtrace.init(batch=False)

app = Flask(__name__, static_folder="../frontend/build", static_url_path="/")
CORS(app)

FlaskInstrumentor().instrument_app(app)

@app.route("/")
</code></pre>
<p>See the items in <strong>BOLD</strong> which will add in the langtrace library, and the opentelemetry flask instrumentation. This combination will provide and end to end trace for the https call all the way down to the calls to Elasticsearch, and to OpenAI (or other LLMs).</p>
<h3 id="createthedockercontainer">Create the docker container</h3>
<p>Use the Dockerfile that is in the chatbot-rag-app directory as is and add the following line:</p>
<p><code>RUN pip3 install --no-cache-dir langtrace-python-sdk</code></p>
<p>into the Dockerfile:</p>
<pre><code>COPY requirements.txt ./requirements.txt
RUN pip3 install -r ./requirements.txt
RUN pip3 install --no-cache-dir langtrace-python-sdk
COPY api ./api
COPY data ./data

EXPOSE 4000
</code></pre>
<p>This enables the <code>langtrace-python-sdk</code> to be installed into the docker container so the langtrace libraries can be used properly.</p>
<h3 id="collectingtheproperenvvariables">Collecting the proper env variables:</h3>
<p>First collect the env variables from Elastic:</p>
<p>Envs for index initialization in Elastic:</p>
<pre><code>ELASTICSEARCH_URL=https://aws.us-west-2.aws.found.io
ELASTICSEARCH_USER=elastic
ELASTICSEARCH_PASSWORD=elastic

# The name of the Elasticsearch indexes
ES_INDEX=workplace-app-docs
ES_INDEX_CHAT_HISTORY=workplace-app-docs-chat-history
</code></pre>
<p>The <code>ELASTICSEARCH_URL</code> can be found in cloud.elastic.co when you bring up your instance.
The user and password, you will need to setup in Elastic. </p>
<p>Envs for sending the OTel instrumentation you will need the following:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT="https://123456789.apm.us-west-2.aws.cloud.es.io:443"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer xxxxx"
</code></pre>
<p>These credentials are found in Elastic under APM integration and under OpenTelemetry</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte5d0b7250dcc156d/6a7f0f5933fa8a1f732027f0/otel-credentials.png" alt="OTel credentials" /></p>
<p>Envs for LLMs</p>
<p>In this example we’re using OpenAI, hence only three variables are needed.</p>
<pre><code>LLM_TYPE=openai
OPENAI_API_KEY=XXXX
CHAT_MODEL=gpt-4o-mini
</code></pre>
<p>All these variables will be needed in the Kubernetes yamls in the next step</p>
<h3 id="setupk8sclusterandloadupotelcollectorwithedot">Setup K8s cluster and load up OTel Collector with EDOT</h3>
<p>This step is outlined in the following <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-otel-operator">Blog</a>. It’s a simple three step process.</p>
<p>This step will bring in all the K8s cluster logs and metrics and setup the OTel collector.</p>
<h3 id="setupsecretsinitializeindicesandstarttheapp">Setup secrets, initialize indices, and start the app</h3>
<p>Now that the cluster is up, and you have your environmental variables, you will need to</p>
<ol>
<li><p>Install and run the <code>k8s-deployments.yaml</code> with the variables</p></li>
<li><p>Initialize the index</p></li>
</ol>
<p>Essentially run the following:</p>
<pre><code>kubectl create -f k8s-deployment.yaml
kubectl create -f init-index-job.yaml
</code></pre>
<p>Here are the two yamls you should use. Also found <a href="https://github.com/elastic/observability-examples/tree/main/chatbot-rag-app-observability">here</a></p>
<p>k8s-deployment.yaml</p>
<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: genai-chatbot-langtrace-secrets
type: Opaque
stringData:
  OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer%20xxxx"
  OTEL_EXPORTER_OTLP_ENDPOINT: "https://1234567.apm.us-west-2.aws.cloud.es.io:443"
 ELASTICSEARCH_URL: "YOUR_ELASTIC_SEARCH_URL"
  ELASTICSEARCH_USER: "elastic"
  ELASTICSEARCH_PASSWORD: "elastic"
  OPENAI_API_KEY: "XXXXXXX"  

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: genai-chatbot-langtrace
spec:
  replicas: 2
  selector:
    matchLabels:
      app: genai-chatbot-langtrace
  template:
    metadata:
      labels:
        app: genai-chatbot-langtrace
    spec:
      containers:
      - name: genai-chatbot-langtrace
        image:65765.amazonaws.com/genai-chatbot-langtrace2:latest
        ports:
        - containerPort: 4000
        env:
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: OTEL_SDK_DISABLED
          value: "false"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "service.name=genai-chatbot-langtrace,service.version=0.0.1,deployment.environment=dev"
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: "http/protobuf"
        envFrom:
        - secretRef:
            name: genai-chatbot-langtrace-secrets
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

---
apiVersion: v1
kind: Service
metadata:
  name: genai-chatbot-langtrace-service
spec:
  selector:
    app: genai-chatbot-langtrace
  ports:
  - port: 80
    targetPort: 4000
  type: LoadBalancer
</code></pre>
<p>Init-index-job.yaml</p>
<pre><code>apiVersion: batch/v1
kind: Job
metadata:
  name: init-elasticsearch-index-test
spec:
  template:
    spec:
      containers:
      - name: init-index
#update your image location for chatbot rag app
        image: your-image-location:latest
        workingDir: /app/api
        command: ["python3", "-m", "flask", "--app", "app", "create-index"]
        env:
        - name: FLASK_APP
          value: "app"
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: ES_INDEX
          value: "workplace-app-docs"
        - name: ES_INDEX_CHAT_HISTORY
          value: "workplace-app-docs-chat-history"
        - name: ELASTICSEARCH_URL
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_URL
        - name: ELASTICSEARCH_USER
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_USER
        - name: ELASTICSEARCH_PASSWORD
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_PASSWORD
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
      restartPolicy: Never
  backoffLimit: 4
</code></pre>
<h3 id="openappwithloadbalancerurl">Open App with LoadBalancer URL</h3>
<p>Run the kubectl get services command and get the URL for the chatbot app</p>
<pre><code>% kubectl get services
NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP                                                               PORT(S)                                                                     AGE
chatbot-langtrace-service            LoadBalancer   10.100.130.44    xxxxxxxxx-1515488226.us-west-2.elb.amazonaws.com   80:30748/TCP                                                                6d23h
</code></pre>
<p>Play with app and review telemetry in Elastic</p>
<p>Once you go to the URL, you should see all the screens we described earlier in the <a href="https://docs.google.com/document/d/1w_3VRDJV3CoLMjOj8Ktnng-6MuKgdzkhKs4CVBWkatc/edit?tab=t.0#bookmark=id.lrmf4nbl2twi">beginning of this blog</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>With Elastic's Chatbot-rag-app you have an example of how to build out a OpenAI driven RAG based chat application. However, you still need to understand how well it performs, whether its working properly, etc. Using OTel, Elastic’s EDOT and Langtrace gives you the ability to achieve this. Additionally, you will generally run this application on Kubernetes. Hopefully this blog provides the outline of how to achieve this.</p>
<p>Here are the other Tracing blogs:</p>
<p>App Observability with LLM (Tracing)- </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">Observing LangChain with Langtrace and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-openlit-tracing">Observing LangChain with OpenLit Tracing</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing">Instrumenting LangChain with OpenTelemetry</a> </p></li>
</ul>
<p>LLM Observability - </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elevate-llm-observability-with-gcp-vertex-ai-integration">Elevate LLM Observability with GCP Vertex AI Integration</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-aws-bedrock">LLM Observability on AWS Bedrock</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai">LLM Observability for Azure OpenAI</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai-v2">LLM Observability for Azure OpenAI v2</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/openai-tracing-langtrace-elastic</link>
    <guid isPermaLink="false">openai-tracing-langtrace-elastic</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9d3180a0fd833d64/6a7f0f5c73d9bd264e29dc29/edot-openai-tracing.png" length="0" type="image/png"/>
    <pubDate>Thu, 06 Feb 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Tracing, logs, and metrics for a RAG based Chatbot with Elastic Distributions of OpenTelemetry]]></title>
    <description><![CDATA[How to observe a OpenAI RAG based application using Elastic. Instrument the app, collect logs, traces, metrics, and understand how well the LLM is performing with Elastic Distributions of OpenTelemetry on Kubernetes and Docker.]]></description>
    <content:encoded><![CDATA[<p>As discussed in the following post, <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-openai">Elastic added instrumentation for OpenAI based applications in EDOT</a>. The main application most commonly using LLMs is known as a Chatbot. These chatbots not only use large language models (LLMs), but are also using frameworks such as LangChain, and search to improve contextual information during a conversation RAG (Retrieval Augmented Generation). Elastics's sample <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a>, showcases how to use Elasticsearch with local data that has embeddings, enabling search to properly pull out the most contextual information during a query with a chatbot connected to an LLM of your choice. It's a great example of how to build out a RAG based application with Elasticsearch.</p>
<p>This app is also now insturmented with EDOT, and you can visualize the Chatbot's traces to OpenAI, as well as relevant logs, and metrics from the application. By running the app as instructed in the github repo with Docker you can see these traces on a local stack. But how about running it against serverless, Elastic cloud or even with Kubernetes?</p>
<p>In this blog we will walk through how to set up Elastic's RAG Based Chatbot application with Elastic cloud and Kubernetes.</p>
<h2 id="prerequisites">Prerequisites:</h2>
<p>In order to follow along, these few pre-requisites are needed</p>
<ul>
<li><p>An Elastic Cloud account — sign up now, and become familiar with Elastic's OpenTelemetry configuration. With Serverless no version required. With regular cloud minimally 8.17</p></li>
<li><p>Git clone the <a href="https://github.com/elastic/elasticsearch-labs/tree/main/example-apps/chatbot-rag-app">RAG based Chatbot application</a> and go through the <a href="https://www.elastic.co/search-labs/tutorials/chatbot-tutorial/welcome">tutorial</a> on how to bring it up and become more familiar and how to bring up the application using Docker.</p></li>
<li><p>An account on OpenAI with API keys</p></li>
<li><p>Kubernetes cluster to run the RAG based Chatbot app</p></li>
<li><p>The instructions in this blog are also found in <a href="https://github.com/elastic/observability-examples/tree/main/chatbot-rag-app-observability">observability-examples</a> in github.</p></li>
</ul>
<h2 id="applicationopentelemetryoutputinelastic">Application OpenTelemetry output in Elastic</h2>
<h3 id="chatbotragapp">Chatbot-rag-app</h3>
<p>The first item that you will need to get up and running is the ChatBotApp, and once up you should see the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd100775bedb7fa2/6a7f0f2be3a2190cf999f57e/Chatbotapp-general.png" alt="Chatbot app main page" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68a2dbea6d8f3991/6a7f0f2e4c4bfb9fbaccd603/Chatbotapp-details.png" alt="Chatbot app working" /></p>
<p>As you select some of the questions you will set a response based on the index that was created in Elasticsearch when the app initializes. Additionally there will be queries that are made to LLMs.</p>
<h3 id="traceslogsandmetricsfromedotinelastic">Traces, logs, and metrics from EDOT in Elastic</h3>
<p>Once you have the application running on your K8s cluster or with Docker, and Elastic Cloud up and running you should see the following:</p>
<h4 id="logs">Logs:</h4>
<p>In Discover you will see logs from the Chatbotapp, and be able to analyze the application logs, any specific log patterns, which saves you time in analysis.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt29660a76fd00a49c/6a7f0f316c6eac6076f14207/chatbot-reg-logs.png" alt="Chatbot-logs" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdc9a84779ba0d561/6a7f0f34ea068d10eff09f64/chatbot-reg-logs-patterns.png" alt="Chatbot-log-patterns" /></p>
<h4 id="traces">Traces:</h4>
<p>In Elastic Observability APM, you can also see tha chatbot details, which include transactions, dependencies, logs, errors, etc.</p>
<p>When you look at traces, you will be able to see the chatbot interactions in the trace.</p>
<ol>
<li><p>You will see the end to end http call</p></li>
<li><p>Individual calls to elasticsearch</p></li>
<li><p>Specific calls such as invoke actions, and calls to the LLM</p></li>
</ol>
<p>You can also get individual details of the traces, and look at related logs, and metrics related to that trace,</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt810b06d98a833d78/6a7f0f376693f8036f664023/chatbot-reg-trace.png" alt="CHatbot-traces" /></p>
<h4 id="metrics">Metrics:</h4>
<p>In addition to logs, and traces, any instrumented metrics will also get ingested into Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16786031635f9d06/6a7f0f3a448e4eedc45c0803/chatbot-reg-metrics.png" alt="Chatbot app metrics" /></p>
<h2 id="settingitallupwithdocker">Setting it all up with Docker</h2>
<p>In order to properly set up the Chatbot-app on Docker with telemetry sent over to Elastic, a few things must be set up:</p>
<ol>
<li><p>Git clone the chatbot-rag-app</p></li>
<li><p>Modify the env file as noted in the github README with the following exception:</p></li>
</ol>
<p>Use your Elastic cloud's <code>OTEL_EXPORTER_OTLP_ENDPOINT</code> and <code>OTEL_EXPORTER_OTLP_HEADER</code> instead.</p>
<p>You can find these in the Elastic Cloud under <code>integrations-&gt;APM</code></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3720f7995587366b/6a7f0f3d3cab1c13600e494c/otel-credentials.png" alt="OTel credentials" /></p>
<p>Envs for sending the OTel instrumentation you will need the following:</p>
<pre><code>OTEL_EXPORTER_OTLP_ENDPOINT="https://123456789.apm.us-west-2.aws.cloud.es.io:443"
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer%20xxxxx"
</code></pre>
<p>Notice the <code>%20</code> in the headers. This will be needed to account for the space in credentials.</p>
<ol>
<li><p>Set the following to false - <code>OTEL_SDK_DISABLED=false</code></p></li>
<li><p>Set the envs for LLMs </p></li>
</ol>
<p>In this example we're using OpenAI, hence only three variables are needed.</p>
<pre><code>LLM_TYPE=openai
OPENAI_API_KEY=XXXX
CHAT_MODEL=gpt-4o-mini
</code></pre>
<ol>
<li>Run the docker container as noted </li>
</ol>
<pre><code>docker compose up --build --force-recreate
</code></pre>
<ol>
<li><p>Play with the app at <code>localhost:4000</code></p></li>
<li><p>Then log into Elastic cloud and see the output as shown previously.</p></li>
</ol>
<h2 id="runchatbotragapponkubernetes">Run chatbot-rag-app on Kubernetes</h2>
<p>In order to set this up, you can follow the following repo on Observability-examples which has the Kubernetes yaml files being used. These will also point to Elastic Cloud.</p>
<ol>
<li><p>Set up the Kubernetes Cluster (we're using EKS)</p></li>
<li><p>Get the appropriate ENV variables:</p></li>
</ol>
<ul>
<li><p>Find the <code>OTEL_EXPORTER_OTLP_ENDPOINT/HEADER</code> variables as noted in the pervious for Docker.</p></li>
<li><p>Get your OpenAI Key</p></li>
<li><p>Elasticsearch URL, and username and password.</p></li>
</ul>
<ol>
<li>Follow the instructions in the following <a href="https://github.com/elastic/observability-examples/tree/main/chatbot-rag-app-observability">github repo in observability examples</a> to run two Kubernetes yaml files.</li>
</ol>
<p>Essentially you need only replace the secret variables in k8s-deployment.yaml, and run</p>
<pre><code>kubectl create -f k8s-deployment.yaml
kubectl create -f init-index-job.yaml
</code></pre>
<p>The app needs to be running first, then we use the app to initialize Elasticsearch with indices for the app.</p>
<p><strong><em>Init-index-job.yaml</em></strong></p>
<pre><code>apiVersion: batch/v1
kind: Job
metadata:
  name: init-elasticsearch-index-test
spec:
  template:
    spec:
      containers:
      - name: init-index
        image: ghcr.io/elastic/elasticsearch-labs/chatbot-rag-app:latest
        workingDir: /app/api
        command: ["python3", "-m", "flask", "--app", "app", "create-index"]
        env:
        - name: FLASK_APP
          value: "app"
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: ES_INDEX
          value: "workplace-app-docs"
        - name: ES_INDEX_CHAT_HISTORY
          value: "workplace-app-docs-chat-history"
        - name: ELASTICSEARCH_URL
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_URL
        - name: ELASTICSEARCH_USER
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_USER
        - name: ELASTICSEARCH_PASSWORD
          valueFrom:
            secretKeyRef:
              name: chatbot-regular-secrets
              key: ELASTICSEARCH_PASSWORD
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
      restartPolicy: Never
  backoffLimit: 4
</code></pre>
<p><strong><em>k8s-deployment.yaml</em></strong></p>
<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: chatbot-regular-secrets
type: Opaque
stringData:
  ELASTICSEARCH_URL: "https://yourelasticcloud.es.us-west-2.aws.found.io"
  ELASTICSEARCH_USER: "elastic"
  ELASTICSEARCH_PASSWORD: "elastic"
  OTEL_EXPORTER_OTLP_HEADERS: "Authorization=Bearer%20xxxx"
  OTEL_EXPORTER_OTLP_ENDPOINT: "https://12345.apm.us-west-2.aws.cloud.es.io:443"
  OPENAI_API_KEY: "YYYYYYYY"

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: chatbot-regular
spec:
  replicas: 2
  selector:
    matchLabels:
      app: chatbot-regular
  template:
    metadata:
      labels:
        app: chatbot-regular
    spec:
      containers:
      - name: chatbot-regular
        image: ghcr.io/elastic/elasticsearch-labs/chatbot-rag-app:latest
        ports:
        - containerPort: 4000
        env:
        - name: LLM_TYPE
          value: "openai"
        - name: CHAT_MODEL
          value: "gpt-4o-mini"
        - name: OTEL_RESOURCE_ATTRIBUTES
          value: "service.name=chatbot-regular,service.version=0.0.1,deployment.environment=dev"
        - name: OTEL_SDK_DISABLED
          value: "false"
        - name: OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT
          value: "true"
        - name: OTEL_EXPERIMENTAL_RESOURCE_DETECTORS
          value: "process_runtime,os,otel,telemetry_distro"
        - name: OTEL_EXPORTER_OTLP_PROTOCOL
          value: "http/protobuf"
        - name: OTEL_METRIC_EXPORT_INTERVAL
          value: "3000"
        - name: OTEL_BSP_SCHEDULE_DELAY
          value: "3000"
        envFrom:
        - secretRef:
            name: chatbot-regular-secrets
        resources:
          requests:
            memory: "512Mi"
            cpu: "250m"
          limits:
            memory: "1Gi"
            cpu: "500m"

---
apiVersion: v1
kind: Service
metadata:
  name: chatbot-regular-service
spec:
  selector:
    app: chatbot-regular
  ports:
  - port: 80
    targetPort: 4000
  type: LoadBalancer
</code></pre>
<p><strong>Open App with LoadBalancer URL</strong></p>
<p>Run the kubectl get services command and get the URL for the chatbot app</p>
<pre><code>% kubectl get services
NAME                                 TYPE           CLUSTER-IP    EXTERNAL-IP                                                               PORT(S)                                                                     AGE
chatbot-regular-service            LoadBalancer   10.100.130.44    xxxxxxxxx-1515488226.us-west-2.elb.amazonaws.com   80:30748/TCP                                                                6d23h
</code></pre>
<ol>
<li><p>Play with app and review telemetry in Elastic</p></li>
<li><p>Once you go to the URL, you should see all the screens we described earlier in the beginning of this blog.</p></li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>With Elastic's Chatbot-rag-app you have an example of how to build out a OpenAI driven RAG based chat application. However, you still need to understand how well it performs, whether its working properly, etc. Using OTel and Elastic’s EDOT gives you the ability to achieve this. Additionally, you will generally run this application on Kubernetes. Hopefully this blog provides the outline of how to achieve this.
Here are the other Tracing blogs:</p>
<p>App Observability with LLM (Tracing)- </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing-langtrace">Observing LangChain with Langtrace and OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-openlit-tracing">Observing LangChain with OpenLit Tracing</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-langchain-tracing">Instrumenting LangChain with OpenTelemetry</a> </p></li>
</ul>
<p>LLM Observability - </p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elevate-llm-observability-with-gcp-vertex-ai-integration">Elevate LLM Observability with GCP Vertex AI Integration</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-aws-bedrock">LLM Observability on AWS Bedrock</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai">LLM Observability for Azure OpenAI</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/llm-observability-azure-openai-v2">LLM Observability for Azure OpenAI v2</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/openai-tracing-elastic-opentelemetry</link>
    <guid isPermaLink="false">openai-tracing-elastic-opentelemetry</guid>
    <category><![CDATA[LLM Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt12854c40fcaa0e97/6a7f0f406c6eac23bbf1420f/edot-openai-tracing.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 24 Jan 2025 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Unlock possibilities with native OpenTelemetry: prioritize reliability, not proprietary limitations]]></title>
    <description><![CDATA[Elastic now supports Elastic Distributions of OpenTelemetry (EDOT) deployment and management on Kubernetes, using OTel Operator. SREs can now access out-of the-box configurations and dashboards designed to streamline collector deployment, application auto-instrumentation and lifecycle management with Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>OpenTelemetry (OTel) is emerging as the standard for data ingestion since it delivers a vendor-agnostic way to ingest data across all telemetry signals. Elastic Observability is leading the OTel evolution with the following announcements:</p>
<ul>
<li><p><strong>Native OTel Integrity:</strong> Elastic is now 100% OTel-native, retaining OTel data natively without requiring data translation This eliminates the need for SREs to handle tedious schema conversions and develop customized views. All Elastic Observability capabilities—such as entity discovery, entity-centric insights, APM, infrastructure monitoring, and AI-driven issue analysis— now seamlessly work with  native OTel data.</p></li>
<li><p><strong>Powerful end to end OTel based Kubernetes observability with</strong> <a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry"><strong>Elastic Distributions of OpenTelemetry (EDOT)</strong></a><strong>:</strong> Elastic now supports EDOT deployment and management on Kubernetes via the OTel Operator, enabling streamlined EDOT collector deployment, application auto-instrumentation, and lifecycle management. With out-of-the-box OTel-based Kubernetes integration and dashboards, SREs gain instant, real-time visibility into cluster and application metrics, logs, and traces—with no manual configuration needed.</p></li>
</ul>
<p>For organizations, it signals our commitment to open standards, streamlined data collection, and delivering insights from native OpenTelemetry data. Bring the power of Elastic Observability to your Kubernetes and OpenTelemetry deployments for maximum visibility and performance. </p>
<h2 id="fullynativeotelarchitecturewithindepthdataanalysis">Fully native OTel architecture with in-depth data analysis</h2>
<p>Elastic’s OpenTelemetry-first architecture is 100% OTel-native, fully retaining the OTel data model, including OTel Semantic Conventions and Resource attributes, so your observability data remains in OpenTelemetry standards. OTel data in Elastic is also backward compatible with the Elastic Common Schema (ECS).</p>
<p>SREs now gain a holistic view of resources, as Elastic accurately identifies entities through OTel resource attributes. For example, in a Kubernetes environment, Elastic identifies containers, hosts, and services and connects these entities to logs, metrics, and traces.</p>
<p>Once OTel data is in Elastic’s scalable vector datastore, Elastic’s capabilities such as the AI Assistant, zero-config machine learning-based anomaly detection, pattern analysis, and latency correlation empower SREs to quickly analyze and pinpoint potential issues in production environments.</p>
<h2 id="kubernetesinsightswithelasticdistributionsofopentelemetryedot">Kubernetes insights with Elastic Distributions of OpenTelemetry (EDOT)</h2>
<p>EDOT reduces manual effort through automated onboarding and pre-configured dashboards. With EDOT and OpenTelemetry, Elastic makes Kubernetes monitoring straightforward and accessible for organizations of any size.</p>
<p>EDOT paired with Elasticsearch,  enables storage for all signal types—logs, metrics, traces, and soon profiling—while maintaining essential resource attributes and semantic conventions.</p>
<p>Elastic’s OpenTelemetry-native solution enables customers to quickly extract insights from their data rather than manage complex infrastructure to ingest data. Elastic automates the deployment and configuration of observability components to deliver a user experience focused on ease and scalability, making it well-suited for large-scale environments and diverse industry needs.</p>
<p>Let’s take a look at how Elastic’s EDOT enables visibility into Kubernetes environments.</p>
<h3 id="1simple3stepotelingestwithlifecyclemanagementandautoinstrumentationnbsp">1. Simple 3-step OTel ingest with lifecycle management and auto-instrumentation </h3>
<p>Elastic leverages the upstream OpenTelemetry Operator to automate its EDOT lifecycle management—including deployment, scaling, and updates—allowing customers to focus on visibility into their Kubernetes infrastructure and applications instead of their observability infrastructure for data collection.</p>
<p>The Operator integrates with the EDOT Collector and language SDKs to provide a consistent, vendor-agnostic experience. For instance, when customers deploy a new application, they don’t need to manually configure instrumentation for various languages; the OpenTelemetry Operator manages this through auto-instrumentation, as supported by the upstream OpenTelemetry project.</p>
<p>This integration simplifies observability by ensuring consistent application instrumentation across the Kubernetes environment. Elastic’s collaboration with the upstream OpenTelemetry project strengthens this automation, enabling users to benefit from the latest updates and improvements in the OpenTelemetry ecosystem. By relying on open source tools like the OpenTelemetry Operator, Elastic ensures that its solutions stay aligned with the latest advancements in the OpenTelemetry project, reinforcing its commitment to open standards and community-driven development.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt896dd27233a341a0/6a7f08c142a117bb2f95bd14/unified-otel-based-k8s-experience.png" alt="Unified OTel-based Kubernetes Experience" /></p>
<p>The diagram above shows how the operator can deploy multiple OTel collectors, helping SREs deploy individual EDOT Collectors for specific applications and infrastructure. This configuration improves availability for OTel ingest and the telemetry is sent directly to Elasticsearch servers via OTLP.</p>
<p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-otel-operator">Check out our recent blog on how to set this up</a>.</p>
<h3 id="2outoftheboxotelbasedkubernetesintegrationwithdashboards">2. Out-of-the-box OTel-based Kubernetes integration with dashboards</h3>
<p>Elastic delivers an OTel-based Kubernetes configuration for the OTel collector by packaging all necessary receivers, processors, and configurations for Kubernetes observability. This enables users to automatically collect, process, and analyze Kubernetes metrics, logs, and traces without the need to configure each component individually.</p>
<p>The OpenTelemetry Kubernetes Collector components provide essential building blocks, including receivers like the Kubernetes Receiver for cluster metrics, Kubeletstats Receiver for detailed node and container metrics, along with processors for data transformation and enrichment. By packaging these components, Elastic offers a turnkey solution that simplifies Kubernetes observability and eliminates the need for users to set up and configure individual collectors or processors.</p>
<p>This pre-packaged approach, which includes <a href="https://github.com/elastic/integrations/tree/main/packages/kubernetes_otel">OTel-native Kibana assets</a> such as dashboards, allows users to focus on analyzing their observability data rather than managing configuration details. Elastic’s Unified OpenTelemetry Experience ensures that users can harness OpenTelemetry’s full potential without needing deep expertise. Whether you’re monitoring resource usage, container health, or API server metrics, users gain comprehensive observability through EDOT.</p>
<p>For more details on OpenTelemetry Kubernetes Collector components, visit<a href="https://opentelemetry.io/docs/kubernetes/collector/components/"> OpenTelemetry Collector Components</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc2f0763430cc1ad8/6a7f08c41967ea53bd330587/otel-based-k8s-dashboard.png" alt="OTel-based Kubernetes Dashboard" /></p>
<h3 id="3streamlinedingestarchitecturewithoteldataandelasticsearch">3. Streamlined ingest architecture with OTel data and Elasticsearch</h3>
<p>Elastic’s ingest architecture minimizes infrastructure overhead by enabling users to forward trace data directly into Elasticsearch with the EDOT Collector, removing the need for the Elastic APM server. This approach:</p>
<ul>
<li><p>Reduces the costs and complexity associated with maintaining additional infrastructure, allowing users to deploy, scale, and manage their observability solutions with fewer resources.</p></li>
<li><p>Allows all OTel data, metrics, logs, and traces to be ingested and stored in Elastic’s singular vector database store enabling further analysis with Elastic’s AI-driven capabilities.</p></li>
</ul>
<p>SREs can now reduce operational burdens while also gaining high performance analytics and observability insights provided by Elastic.</p>
<h2 id="elasticsongoingcommitmenttoopensourceandopentelemetry">Elastic’s ongoing commitment to open source and OpenTelemetry</h2>
<p>With <a href="https://www.elastic.co/blog/elasticsearch-is-open-source-again">Elasticsearch fully open source once again</a> under the AGPL license,  this change reinforces our deep commitment to open standards and the open source community. This aligns with Elastic’s OpenTelemetry-first approach to observability, where Elastic Distributions of OpenTelemetry (EDOT) streamline OTel ingestion and schema auto-detection, providing real-time insights for Kubernetes and application telemetry.</p>
<p>As users increasingly adopt OTel as their schema and data collection architecture for observability, Elastic’s Distribution of OpenTelemetry (EDOT), currently in tech preview, enhances standard OpenTelemetry capabilities and improves troubleshooting while also serving as a commercially supported OTel distribution. EDOT, together with Elastic’s recent contributions of the Elastic Profiling Agent and Elastic Common Schema (ECS) to OpenTelemetry, reinforces Elastic’s commitment to establishing OpenTelemetry as the industry standard.</p>
<p>Customers can now embrace open standards and enjoy the advantages of an open, extensible platform that integrates seamlessly with their environment. End result?  Reduced costs, greater visibility, and vendor independence.</p>
<h2 id="gettinghandsonwithelasticobservabilityandedot">Getting hands-on with Elastic Observability and EDOT</h2>
<p>Ready to try out the OTel Operator with EDOT collector and SDKs to see how Elastic utilizes ingested OTel data in APM, Discover, Analysis, and out-of-the-box dashboards? </p>
<ul>
<li><p><a href="https://cloud.elastic.co/">Get an account on Elastic Cloud</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distributions-opentelemetry">Learn about Elastic Distributions of OpenTelemetry Overview</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/opentelemetry-demo-with-the-elastic-distributions-of-opentelemetry">Utilize the OpenTelemetry Demo with EDOT</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/infrastructure-monitoring-with-opentelemetry-in-elastic-observability">Understand how you can monitor Kubernetes with EDOT</a></p></li>
<li><p><a href="https://github.com/elastic/opentelemetry">Utilize the EDOT Operator </a>and the <a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-collector">EDOT OTel collector</a></p></li>
</ul>
<p>If you have your own application and want to configure EDOT the application with auto-instrumentation, read the following blogs on Go, Java, PHP, Python</p>
<ul>
<li><p><a href="https://www.elastic.co/observability-labs/blog/auto-instrumentation-go-applications-opentelemetry">Auto-Instrumenting Go Applications with OpenTelemetry</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-distribution-opentelemetry-java-agent">Elastic Distribution OpenTelemetry Java Agent</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-php">Elastic OpenTelemetry Distribution for PHP</a></p></li>
<li><p><a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-distribution-python">Elastic OpenTelemetry Distribution for Python</a></p></li>
</ul>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-native-kubernetes-observability</link>
    <guid isPermaLink="false">elastic-opentelemetry-native-kubernetes-observability</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <dc:creator><![CDATA[Bahubali Shetti,Miguel Luna]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6f5ac53ca0bdff9f/6a7f08c7ead8ec5b79baa6c5/Kubecon-main-blog.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 12 Nov 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using a custom agent with the OpenTelemetry Operator for Kubernetes]]></title>
    <description><![CDATA[]]></description>
    <content:encoded><![CDATA[<p>This is the second part of a two part series. The first part is available at <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">Zero config OpenTelemetry auto-instrumentation for Kubernetes Java applications</a>. In that first part I walk through setting up and installing the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a>, and configuring that for auto-instrumentation of a Java application using the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>. </p>
<p>In this second part, I show how to install <em>any</em> Java agent via the OpenTelemetry operator, using the Elastic Java agents as examples.</p>
<h2 id="installationandconfigurationrecap">Installation and configuration recap</h2>
<p>Part 1 of this series, <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">Zero config OpenTelemetry auto-instrumentation for Kubernetes Java applications</a>, details the installation and configuration of the OpenTelemetry operator and an Instrumentation resource. Here is an outline of the steps as a reminder:</p>
<ol>
<li>Install cert-manager, eg <code>kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml</code></li>
<li>Install the operator, eg <code>kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml</code></li>
<li>Create an Instrumentation resource</li>
<li>Add an annotation to either the deployment or the namespace</li>
<li>Deploy the application as normal</li>
</ol>
<p>In that first part, steps 3, 4 &amp; 5 were implemented for the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>. In this blog I’ll implement them for other agents, using the Elastic APM agents as examples. I assume that steps 1 &amp; 2 outlined above have already been done, ie that the operator is now installed. I will continue using the <code>banana</code> namespace for the examples, so ensure that namespace exists (<code>kubectl create namespace banana</code>). As per part 1, if you use any of the example instrumentation definitions below, you’ll need to substitute <code>my.apm.server.url</code> and <code>my-apm-secret-token</code> with the values appropriate for your collector.</p>
<h2 id="usingtheelasticdistributionforopentelemetryjava">Using the Elastic Distribution for OpenTelemetry Java</h2>
<p>From version 0.4.0, the <a href="https://github.com/elastic/elastic-otel-java">Elastic Distribution for OpenTelemetry Java</a> includes the agent jar at the path <code>/javaagent.jar</code> in the docker image - which is essentially all that is needed for a docker image to be usable by the OpenTelemetry operator for auto-instrumentation. This means the Instrumentation resource is straightforward to define, and as it’s a distribution of the OpenTelemetry Java agent, all the OpenTelemetry environment can apply:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: elastic-otel
  namespace: banana
spec:
  exporter:
    endpoint: https://my.apm.server.url
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"
  java:
    image: docker.elastic.co/observability/elastic-otel-javaagent:1.10.0
    env:
      - name: OTEL_EXPORTER_OTLP_HEADERS
        value: "Authorization=Bearer my-apm-secret-token"
      - name: ELASTIC_OTEL_INFERRED_SPANS_ENABLED
        value: "true"
      - name: ELASTIC_OTEL_SPAN_STACK_TRACE_MIN_DURATION
        value: "50"
</code></pre>
<p>I’ve included environment for switching on several features in the agent, including</p>
<ol>
<li>ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED to switch on the inferred spans implementation feature described in <a href="https://www.elastic.co/observability-labs/blog/tracing-data-inferred-spans-opentelemetry">this blog</a></li>
<li>Span stack traces are automatically captured if the span takes more than ELASTIC_OTEL_SPAN_STACK_TRACE_MIN_DURATION (default would be 5ms)</li>
</ol>
<p>Adding in the annotation …</p>
<pre><code>metadata:
  annotations:
    instrumentation.opentelemetry.io/inject-java: "elastic-otel"
</code></pre>
<p>… to the pod yaml gets the application traced, and displayed in the Elastic APM UI, including the inferred child spans and stack traces</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1cf450efadd3a797/6a7f1c0296b5a66b7787b8c1/elastic-apm-ui-with-stack-trace.png" alt="Elastic APM UI showing methodB traced with stack traces and inferred spans" /></p>
<p>The additions from the features mentioned above are circled in red - inferred spans (for methodC and methodD) bottom left, and the stack trace top right. (Note that the pod included the <code>OTEL_INSTRUMENTATION_METHODS_INCLUDE</code> environment variable set to <code>"test.Testing[methodB]"</code> so that traces from methodB are shown; for pod configuration see the "Trying it" section in <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">part 1</a>)</p>
<h2 id="usingtheelasticapmjavaagent">Using the Elastic APM Java agent</h2>
<p>From version 1.50.0, the <a href="https://github.com/elastic/apm-agent-java">Elastic APM Java agent</a> includes the agent jar at the path /javaagent.jar in the docker image - which is essentially all that is needed for a docker image to be usable by the OpenTelemetry operator for auto-instrumentation. This means the Instrumentation resource is straightforward to define:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: elastic-apm
  namespace: banana
spec:
  java:
    image: docker.elastic.co/observability/apm-agent-java:1.55.4
    env:
      - name: ELASTIC_APM_SERVER_URL
        value: "https://my.apm.server.url"
      - name: ELASTIC_APM_SECRET_TOKEN
        value: "my-apm-secret-token"
      - name: ELASTIC_APM_LOG_LEVEL
        value: "INFO"
      - name: ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED
        value: "true"
      - name: ELASTIC_APM_LOG_SENDING
        value: "true"
</code></pre>
<p>I’ve included environment for switching on several features in the agent, including</p>
<ul>
<li>ELASTIC_APM_LOG_LEVEL set to the default value (INFO) which could easily be switched to DEBUG</li>
<li>ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED to switch on the inferred spans implementation equivalent to the feature described in <a href="https://www.elastic.co/observability-labs/blog/tracing-data-inferred-spans-opentelemetry">this blog</a></li>
<li>ELASTIC_APM_LOG_SENDING which switches on sending logs to the APM UI, the logs are automatically correlated with transactions (for all common logging frameworks)</li>
</ul>
<p>Adding in the annotation …</p>
<pre><code>metadata:
  annotations:
     instrumentation.opentelemetry.io/inject-java: "elastic-apm"
</code></pre>
<p>… to the pod yaml gets the application traced, and displayed in the Elastic APM UI, including the inferred child spans</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt425d331c27b53881/6a7f1c0596b5a6621887b8c5/elastic-apm-ui-with-inferred-spans.png" alt="Elastic APM UI showing methodB traced with inferred spans" /></p>
<p>(Note that the pod included the <code>ELASTIC_APM_TRACE_METHODS</code> environment variable set to <code>"test.Testing#methodB"</code> so that traces from methodB are shown; for pod configuration see the "Trying it" section in <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">part 1</a>)</p>
<h2 id="usinganextensionwiththeopentelemetryjavaagent">Using an extension with the OpenTelemetry Java agent</h2>
<p>Setting up an Instrumentation resource for the OpenTelemetry Java agent is straightforward and was done in <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents">part 1</a> of this two part series - and you can see from the above examples it’s just a matter of deciding on the docker image URL you want to use. However if you want to include an <em>extension</em> in your deployment, this is a little more complex, but also supported by the operator. Basically the extensions you want to include with the agent need to be in docker images - or you have to build an image which includes the extensions that are not already in images. Then you declare the images and the directories the extensions are in, in the Instrumentation resource. As an example, I’ll show an Instrumentation which uses version 2.5.0 of the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a> together with the <a href="https://github.com/elastic/elastic-otel-java/tree/main/inferred-spans">inferred spans extension</a> from the <a href="https://github.com/elastic/elastic-otel-java">Elastic OpenTelemetry Java distribution</a>. The distro image includes the extension at path <code>/extensions/elastic-otel-agentextension.jar</code>. The Instrumentation resource allows either directories or file paths to be specified, here I’ll list the directory:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: otel-plus-extension-instrumentation
  namespace: banana
spec:
  exporter:
    endpoint: https://my.apm.server.url
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.5.0
    extensions:
      - image: "docker.elastic.co/observability/elastic-otel-javaagent:1.10.0"
        dir: "/extensions"
    env:
      - name: OTEL_EXPORTER_OTLP_HEADERS
        value: "Authorization=Bearer my-apm-secret-token"
      - name: ELASTIC_OTEL_INFERRED_SPANS_ENABLED
        value: "true"
</code></pre>
<p>Note that you can have multiple <code>image … dir</code> pairs, ie include multiple extensions from different images. Note also if you are testing this specific configuration that the inferred spans extension included here will be contributed to the OpenTelemetry contrib repo at some point after this blog is published, after which the extension may no longer be present in a later version of the referred image (since it will be available from the <a href="https://github.com/open-telemetry/opentelemetry-java-contrib/">contrib repo</a> instead).</p>
<h2 id="nextsteps">Next steps</h2>
<p>Here I’ve shown how to use any agent with the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a>, and configure that for your system. In particular the examples have showcased how to use the Elastic Java agents to auto-instrument Java applications running in your Kubernetes clusters, along with how to enable features, using Instrumentation resources. And you can set it up for either zero config for deployments, or for just one annotation which is generally a more flexible mechanism (you can have multiple Instrumentation resource definitions, and the deployment can select the appropriate one for its application).</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-elastic-agents</link>
    <guid isPermaLink="false">using-the-otel-operator-for-injecting-elastic-agents</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Jack Shirazi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55dfb115f9341105/6a7f1c08ea068d4de1f0a2f9/blog-header-720x420.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Zero config OpenTelemetry auto-instrumentation for Kubernetes Java applications]]></title>
    <description><![CDATA[Walking through how to install and enable the OpenTelemetry Operator for Kubernetes to auto-instrument Java applications, with no configuration changes needed for deployments]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a> has a number of <a href="https://opentelemetry.io/docs/languages/java/automatic/#setup">ways to install</a> the agent into a Java application. If you are running your Java applications in Kubernetes pods, there is a separate mechanism (which under the hood uses JAVA_TOOL_OPTIONS and other environment variables) to auto-instrument Java applications. This auto-instrumentation can be achieved with zero configuration of the applications and pods!</p>
<p>The mechanism to achieve zero-config auto-instrumentation of Java applications in Kubernetes is via the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a>. This operator has many capabilities and the full documentation (and of course source) is available in the project itself. In this blog, I'll walk through installing, setting up and running zero-config auto-instrumentation of Java applications in Kubernetes using the OpenTelemetry Operator.</p>
<h2 id="installingtheopentelemetryoperatoraidinstallingtheopentelemetryoperatora">Installing the OpenTelemetry Operator<a id="installing-the-opentelemetry-operator"></a></h2>
<p>At the time of writing this blog, the OpenTelemetry Operator needs the certification manager to be installed, after which the operator can be installed. Installing from the web is straightforward. First install the <code>cert-manager</code> (the version to be installed will be specified in the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a> documentation):</p>
<pre><code>kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.14.4/cert-manager.yaml
</code></pre>
<p>Then when the cert managers are ready (<code>kubectl get pods -n cert-manager</code>)  …</p>
<pre><code>NAMESPACE&amp;nbsp; &amp;nbsp; &amp;nbsp; NAME &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; READY
cert-manager &amp;nbsp; cert-manager-67c98b89c8-rnr5s&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1/1
cert-manager &amp;nbsp; cert-manager-cainjector-5c5695d979-q9hxz &amp;nbsp; &amp;nbsp; 1/1
cert-manager &amp;nbsp; cert-manager-webhook-7f9f8648b9-8gxgs&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1/1
</code></pre>
<p>… you can install the OpenTelemetry Operator:</p>
<pre><code>kubectl apply -f https://github.com/open-telemetry/opentelemetry-operator/releases/latest/download/opentelemetry-operator.yaml
</code></pre>
<p>You can, of course, use a specific version of the operator instead of the <code>latest</code>. But here I’ve used the <code>latest</code> version.</p>
<h2 id="aninstrumentationresourceaidaninstrumentationresourcea">An Instrumentation resource<a id="an-instrumentation-resource"></a></h2>
<p>Now you need to add just one further Kubernetes resource to enable auto-instrumentation: an <code>Instrumentation</code> resource. I am going to use the <code>banana</code> namespace for my examples, so I have first created that namespace (<code>kubectl create namespace banana</code>). The auto-instrumentation is specified and configured by these Instrumentation resources. Here is a basic one which will allow every Java pod in the <code>banana</code> namespace to be auto-instrumented with version 2.5.0 of the <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>:</p>
<pre><code>apiVersion: opentelemetry.io/v1alpha1
kind: Instrumentation
metadata:
  name: banana-instr
  namespace: banana
spec:
  exporter:
    endpoint: "https://my.endpoint"
  propagators:
    - tracecontext
    - baggage
    - b3
  sampler:
    type: parentbased_traceidratio
    argument: "1.0"
  java:
    image: ghcr.io/open-telemetry/opentelemetry-operator/autoinstrumentation-java:2.5.0
    env:
      - name: OTEL_EXPORTER_OTLP_HEADERS
        value: "Authorization=Bearer MyAuth"
</code></pre>
<p>Creating this resource (eg with <code>kubectl apply -f banana-instr.yaml</code>, assuming the above yaml was saved in file <code>banana-instr.yaml</code>) makes the <code>banana-instr</code> Instrumentation resource available for use. (Note you will need to change <code>my.endpoint</code> and <code>MyAuth</code> to values appropriate for your collector.) You can use this instrumentation immediately by adding an annotation to any deployment in the <code>banana</code> namespace:</p>
<pre><code>metadata:
&amp;nbsp;&amp;nbsp;annotations:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;instrumentation.opentelemetry.io/inject-java: "true"
</code></pre>
<p>The <code>banana-instr</code> Instrumentation resource is not yet set to be applied by <em>default</em> to all pods in the banana namespace. Currently it's zero-config as far as the <em>application</em> is concerned, but it requires an annotation added to a <em>pod or deployment</em>. To make it fully zero-config for <em>all pods</em> in the <code>banana</code> namespace, we need to add that annotation to the namespace itself, ie editing the namespace (<code>kubectl edit namespace banana</code>) so it would then have contents similar to</p>
<pre><code>apiVersion: v1
kind: Namespace
metadata:
&amp;nbsp;&amp;nbsp;name: banana
&amp;nbsp;&amp;nbsp;annotations:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;instrumentation.opentelemetry.io/inject-java: "banana-instr"
...
</code></pre>
<p>Now we have a namespace that is going to auto-instrument <em>every</em> Java application deployed in the <code>banana</code> namespace with the 2.5.0 <a href="https://github.com/open-telemetry/opentelemetry-java-instrumentation/">OpenTelemetry Java agent</a>!</p>
<h2 id="tryingitaidtryingita">Trying it<a id="trying-it"></a></h2>
<p>There is a simple example Java application at <a href="http://docker.elastic.co/demos/apm/k8s-webhook-test">docker.elastic.co/demos/apm/k8s-webhook-test</a> which just repeatedly calls the chain <code>main-&gt;methodA-&gt;methodB-&gt;methodC-&gt;methodD</code> with some sleeps in the calls. Running this (<code>kubectl apply -f banana-app.yaml</code>) using a very basic pod definition:</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: banana-app
  namespace: banana
  labels:
    app: banana-app
spec:
  containers:
    - image: docker.elastic.co/demos/apm/k8s-webhook-test
      imagePullPolicy: Always
      name: banana-app
      env: 
      - name: OTEL_INSTRUMENTATION_METHODS_INCLUDE
        value: "test.Testing[methodB]"
</code></pre>
<p>results in the app being auto-instrumented with no configuration changes! The resulting app shows up in any APM UI, such as Elastic APM</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5339364ee45875ef/6a7f1c0d3ce8e2a2e5cf57d0/elastic-apm-ui-transaction.png" alt="Elastic APM UI showing methodB traced" /></p>
<p>As you can see, for this example I also added this env var to the pod yaml, <code>OTEL_INSTRUMENTATION_METHODS_INCLUDE="test.Testing[methodB]"</code> so that there were traces showing from methodB.</p>
<h2 id="thetechnologybehindtheautoinstrumentationaidthetechnologybehindtheautoinstrumentationa">The technology behind the auto-instrumentation<a id="the-technology-behind-the-auto-instrumentation"></a></h2>
<p>To use the auto-instrumentation there is no specific need to understand the underlying mechanisms, but for those of you interested, here’s a quick outline. </p>
<ol>
<li>The <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a> installs a <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">mutating webhook</a>, a standard Kubernetes component.</li>
<li>When deploying, Kubernetes first sends all definitions to the mutating webhook.</li>
<li>If the mutating webhook sees that the conditions for auto-instrumentation should be applied (ie </li>
<li>there is an Instrumentation resource for that namespace and</li>
<li>the correct annotation for that Instrumentation is applied to the definition in some way, either from the definition itself or from the namespace),</li>
<li>then the mutating webhook “mutates” the definition to include the environment defined by the Instrumentation resource.</li>
<li>The environment includes the explicit values defined in the env, as well as some implicit OpenTelemetry values (see the <a href="https://github.com/open-telemetry/opentelemetry-operator/">OpenTelemetry Operator for Kubernetes</a> documentation for full details).</li>
<li>And most importantly, the operator</li>
<li>pulls the image defined in the Instrumentation resource,</li>
<li>extracts the file at the path <code>/javaagent.jar</code> from that image (using shell command <code>cp</code>)</li>
<li>inserts it into the pod at path <code>/otel-auto-instrumentation-java/javaagent.jar</code></li>
<li>and adds the environment variable <code>JAVA_TOOL_OPTIONS=-javaagent:/otel-auto-instrumentation-java/javaagent.jar</code>.</li>
<li>The JVM automatically picks up that JAVA_TOOL_OPTIONS environment variable on startup and applies it to the JVM command-line.</li>
</ol>
<h2 id="nextstepsaidnextstepsa">Next steps<a id="next-steps"></a></h2>
<p>This walkthrough can be repeated in any Kubernetes cluster to demonstrate and experiment with auto-instrumentation (you will need to create the banana namespace first). In part 2 of this two part series, <a href="https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-elastic-agents">Using a custom agent with the OpenTelemetry Operator for Kubernetes</a>, I show how to install any Java agent via the OpenTelemetry operator, using the Elastic Java agents as examples.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/using-the-otel-operator-for-injecting-java-agents</link>
    <guid isPermaLink="false">using-the-otel-operator-for-injecting-java-agents</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Jack Shirazi]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt53c963d5c03388fb/6a7f1c101967ea3597330ba4/blog-header.png" length="0" type="image/png"/>
    <pubDate>Thu, 11 Jul 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Build better Service Level Objectives (SLOs) from logs and metrics]]></title>
    <description><![CDATA[To help manage operations and business metrics, Elastic Observability's SLO (Service Level Objectives) feature was introduced in 8.12. This blog reviews this feature and how you can use it with Elastic's AI Assistant to meet SLOs.]]></description>
    <content:encoded><![CDATA[<p>In today's digital landscape, applications are at the heart of both our personal and professional lives. We've grown accustomed to these applications being perpetually available and responsive. This expectation places a significant burden on the shoulders of developers and operations teams.</p>
<p>Site reliability engineers (SREs) face the challenging task of sifting through vast quantities of data, not just from the applications themselves but also from the underlying infrastructure. In addition to data analysis, they are responsible for ensuring the effective use and development of operational tools. The growing volume of data, the daily resolution of issues, and the continuous evolution of tools and processes can detract from the focus on business performance.</p>
<p>Elastic Observability offers a solution to this challenge. It enables SREs to integrate and examine all telemetry data (logs, metrics, traces, and profiling) in conjunction with business metrics. This comprehensive approach to data analysis fosters operational excellence, boosts productivity, and yields critical insights, all of which are integral to maintaining high-performing applications in a demanding digital environment.</p>
<p>To help manage operations and business metrics, Elastic Observability's SLO (Service Level Objectives) feature was introduced in <a href="https://www.elastic.co/guide/en/observability/8.12/slo.html">8.12</a>. This feature enables setting measurable performance targets for services, such as <a href="https://sre.google/sre-book/monitoring-distributed-systems/">availability, latency, traffic, errors, and saturation or define your own</a>. Key components include:</p>
<ul>
<li><p>Defining and monitoring SLIs (Service Level Indicators)</p></li>
<li><p>Monitoring error budgets indicating permissible performance shortfalls</p></li>
<li><p>Alerting on burn rates showing error budget consumption</p></li>
</ul>
<p>Users can monitor SLOs in real-time with dashboards, track historical performance, and receive alerts for potential issues. Additionally, SLO dashboard panels offer customized visualizations.</p>
<p>Service Level Objectives (SLOs) are generally available for our Platinum and Enterprise subscription customers.</p>
<div>
    
</div>
<p>In this blog, we will outline the following:</p>
<ul>
<li><p>What are SLOs? A Google SRE perspective</p></li>
<li><p>Several scenarios of defining and managing SLOs</p></li>
</ul>
<h2 id="servicelevelobjectiveoverview">Service Level Objective overview</h2>
<p>Service Level Objectives (SLOs) are a crucial component for Site Reliability Engineering (SRE), as detailed in <a href="https://sre.google/sre-book/table-of-contents/">Google's SRE Handbook</a>. They provide a framework for quantifying and managing the reliability of a service. The key elements of SLOs include:</p>
<ul>
<li><p><strong>Service Level Indicators (SLIs):</strong> These are carefully selected metrics, such as uptime, latency, throughput, error rates, or other important metrics, that represent the aspects of the service and are important from an operations or business perspective. Hence, an SLI is a measure of the service level provided (latency, uptime, etc.), and it is defined as a ratio of good over total events, with a range between 0% and 100%.</p></li>
<li><p><strong>Service Level Objective (SLO):</strong> An SLO is the target value for a service level measured as a percentage by an SLI. Above the threshold, the service is compliant. As an example, if we want to use service availability as an SLI, with the number of successful responses at 99.9%, then any time the number of failed responses is &gt; .1%, the SLO will be out of compliance.</p></li>
<li><p><strong>Error budget:</strong> This represents the threshold of acceptable errors, balancing the need for reliability with practical limits. It is defined as 100% minus the SLO quantity of errors that is tolerated.</p></li>
<li><p><strong>Burn rate:</strong> This concept relates to how quickly the service is consuming its error budget, which is the acceptable threshold for unreliability agreed upon by the service providers and its users.</p></li>
</ul>
<p>Understanding these concepts and effectively implementing them is essential for maintaining a balance between innovation and reliability in service delivery. For more detailed information, you can refer to <a href="https://sre.google/workbook/slo-document/">Google's SRE Handbook</a>.</p>
<p>One main thing to remember is that SLO monitoring is <em>not</em> incident monitoring. SLO monitoring is a proactive, strategic approach designed to ensure that services meet established performance standards and user expectations. It involves tracking Service Level Objectives, error budgets, and the overall reliability of a service over time. This predictive method helps in preventing issues that could impact users and aligns service performance with business objectives.</p>
<p>In contrast, incident monitoring is a reactive process focused on detecting, responding to, and mitigating service incidents as they occur. It aims to address unexpected disruptions or failures in real time, minimizing downtime and impact on service. This includes monitoring system health, errors, and response times during incidents, with a focus on rapid response to minimize disruption and preserve the service's reputation.</p>
<p>Elastic®’s SLO capability is based directly off the Google SRE Handbook. All the definitions and semantics are utilized as described in Google’s SRE handbook. Hence users can perform the following on SLOs in Elastic:</p>
<ul>
<li><p>Define an SLO on an SLI such as KQL (log based query), service availability, service latency, custom metric, histogram metric, or a timeslice metric. Additionally, set the appropriate threshold.</p></li>
<li><p>Utilize occurrence versus time slice based budgeting. Occurrences is the number of good events over the number of total events to compute the SLO. Timeslices break the overall time window into slammer slices of a defined duration and compute the number of good slices over the total slices to compute the SLO. Timeslice targets are more accurate and useful when calculating things like a service’s SLO when trying to meet agreed upon customer targets.</p></li>
<li><p>Manage all the SLOs in a singular location.</p></li>
<li><p>Trigger alerts from the defined SLO, whether the SLI is off, burn rate is used up, or the error rate is X.</p></li>
<li><p>Create unique service level dashboards with SLO information for a more comprehensive view of the service.</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta3810c425fa6d9ef/6a7f1a69b43770d02c4d70fc/1-slo-blog.png" alt="Create alerts" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f526ae1d0618b26/6a7f1a6ce02fac5abb5d69b3/2-slo-blog.png" alt="Create dashboards" /></p>
<p>SREs need to be able to manage business metrics.</p>
<h2 id="slosbasedonlogsnginxavailability">SLOs based on logs: NGINX availability</h2>
<p>Defining SLOs does not always mean metrics need to be used. Logs are a rich form of information, even when they have metrics embedded in them. Hence it’s useful to understand your business and operations status based on logs.</p>
<p>Elastic allows you to create an SLO based on specific fields in the log message, which don’t have to be metrics. A simple example is a simple multi-tier app that has a web server layer (nginx), a processing layer, and a database layer.</p>
<p>Let’s say that your processing layer is managing a significant number of requests. You want to ensure that the service is up properly. The best way is to ensure that all http.response.status_code are less than 500. Anything less ensures the service is up and any errors (like 404) are all user or client errors versus server errors.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte8b306f68814e9fa/6a7f1a6fe02fac7d295d69b7/3-slo-blog.png" alt="expanded document" /></p>
<p>If we use Discover in Elastic, we see that there are close to 2M log messages over a seven-day time frame.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f8638fe990d421f/6a7f1a72c2e9141e31016ff0/4-slo-blog.png" alt="17k" /></p>
<p>Additionally, the number of messages with http.response.status_code &gt; 500 is minimal, like 17K.</p>
<p>Rather than creating an alert, we can create an SLO with this query:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f476a5816f7c858/6a7f1a7533fa8a3787202b7e/5-slo-blog.png" alt="edit SLO" /></p>
<p>We chose to use occurrences as the budgeting method to keep things simple.</p>
<p>Once defined, we can see how well our SLO is performing over a seven-day time frame. We can see not only the SLO, but also the burn rate, the historical SLI, and error budget, and any specific alerts against the SLO.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d749a94c689ccb3/6a7f1a7877b034ab7d3ff907/6-slo-blog.png" alt="SLOs" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde894037a4de1f6d/6a7f1a7bea068d5abaf0a2cb/7-slo-blog.png" alt="nginx server availability " /></p>
<p>Not only do we get information about the violation, but we also get:</p>
<ul>
<li><p>Historical SLI (7 days)</p></li>
<li><p>Error budget burn down</p></li>
<li><p>Good vs. bad events (24 hours)</p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt23453245544e2b0d/6a7f1a7f5967e551ff5dd6cf/8-slo-blog.png" alt="Percentages" /></p>
<p>We can see how we’ve easily burned through our error budget.</p>
<p>Hence something must be going on with nginx. To investigate, all we need to do is utilize the <a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">AI Assistant</a>, and use its natural language interface to ask questions to help analyze the situation.</p>
<p>Let’s use Elastic’s AI Assistant to analyze the breakdown of http.response.status_code across all the logs from the past seven days. This helps us understand how many 50X errors we are getting.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2e3dad6ad1a36d7f/6a7f1a8233fa8a6c82202b82/9-slo-blog.png" alt="count of http response status code" /></p>
<p>As we can see, the number of 502s is minimal compared to the number of overall messages, but it is affecting our SLO.</p>
<p>However, it seems like Nginx is having an issue. In order to reduce the issue, we also ask the AI Assistant how to work on this error. Specifically, we ask if there is an internal runbook the SRE team has created.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc46a6e3bc18f8d57/6a7f1a8542a117ce0295c305/10-slo-blog.png" alt="ai assistant thread" /></p>
<p>AI Assistant gets a runbook the team has added to its knowledge base. I can now analyze and try to resolve or reduce the issue with nginx.</p>
<p>While this is a simple example, there are an endless number of possibilities that can be defined based on KQL. Some other simple examples:</p>
<ul>
<li><p>99% of requests occur under 200ms</p></li>
<li><p>99% of log message are not errors</p></li>
</ul>
<h2 id="applicationslosopentelemetrydemocartservice">Application SLOs: OpenTelemetry demo cartservice</h2>
<p>A common application developers and SREs use to learn about OpenTelemetry and test out Observability features is the <a href="https://github.com/elastic/opentelemetry-demo">OpenTelemetry demo</a>.</p>
<p>This demo has <a href="https://opentelemetry.io/docs/demo/feature-flags/">feature flags</a> to simulate issues. With Elastic’s alerting and SLO capability, you can also determine how well the entire application is performing and how well your customer experience is holding up when these feature flags are used.</p>
<p><a href="https://www.elastic.co/blog/opentelemetry-observability">Elastic supports OpenTelemetry by taking OTLP directly with no need for an Elastic specific agent</a>. You can send in OpenTelemetry data directly from the application (through OTel libraries) and through the collector.</p>
<p>We’ve brought up the OpenTelemetry demo on a K8S cluster (AWS EKS) and turned on the cartservice feature flag. This inserts errors into the cartservice. We’ve also created two SLOs to monitor the cartservice’s availability and latency.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfbeda104042da07a/6a7f1a87ead8ec59b3baac54/11-slo-blog.png" alt="SLOs" /></p>
<p>We can see that the cartservice’s availability is violated. As we drill down, we see that there aren’t as many successful transactions, which is affecting the SLO.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbd22cf26b8ff2180/6a7f1a8a2f00b25cbbefef23/12-slo-blog.png" alt="cartservice-otel" /></p>
<p>As we drill into the service, we can see in Elastic APM that there is a higher than normal failure rate of about 5.5% for the emptyCart service.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c99cfb53987e5c7/6a7f1a8deab5bee9cd20ab00/13-slo-blog.png" alt="apm" /></p>
<p>We can investigate this further in APM, but that is a discussion for another blog. Stay tuned to see how we can use Elastic’s machine learning, AIOps, and AI Assistant to understand the issue.</p>
<h2 id="conclusion">Conclusion</h2>
<p>SLOs allow you to set clear, measurable targets for your service performance, based on factors like availability, response times, error rates, and other key metrics. Hopefully with the overview we’ve provided in this blog, you can see that:</p>
<ul>
<li><p>SLOs can be based on logs. In Elastic, you can use KQL to essentially find and filter on specific logs and log fields to monitor and trigger SLOs.</p></li>
<li><p>AI Assistant is a valuable, easy-to-use capability to analyze, troubleshoot, and even potentially resolve SLO issues.</p></li>
<li><p>APM Service based SLOs are easy to create and manage with integration to Elastic APM. We also use OTel telemetry to help monitor SLOs.</p></li>
</ul>
<p>For more information on SLOs in Elastic, check out <a href="https://www.elastic.co/guide/en/observability/current/slo.html">Elastic documentation</a> and the following resources:</p>
<ul>
<li><p><a href="https://www.elastic.co/guide/en/observability/8.12/slo.html">What’s new in Elastic Observability 8.12</a></p></li>
<li><p><a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Introducing the Elastic AI Assistant</a></p></li>
<li><p><a href="https://www.elastic.co/blog/opentelemetry-observability">Elastic OpenTelemetry support</a></p></li>
</ul>
<p>Ready to get started? Sign up for <a href="https://cloud.elastic.co/registration">Elastic Cloud</a> and try out the features and capabilities I’ve outlined above to get the most value and visibility out of your SLOs.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>
<p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/service-level-objectives-slos-logs-metrics</link>
    <guid isPermaLink="false">service-level-objectives-slos-logs-metrics</guid>
    <category><![CDATA[Incident Management]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt126c07eb43762792/6a7f1a91b4377020074d7104/139686_-_Elastic_-_Headers_-_V1_3.jpg" length="0" type="image/jpeg"/>
    <pubDate>Fri, 23 Feb 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to easily add application monitoring in Kubernetes pods]]></title>
    <description><![CDATA[This blog walks through installing the Elastic APM K8s Attacher and shows how to configure your system for both common and non-standard deployments of Elastic APM agents.]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://www.elastic.co/guide/en/apm/attacher/current/index.html">Elastic® APM K8s Attacher</a> allows auto-installation of Elastic APM application agents (e.g., the Elastic APM Java agent) into applications running in your Kubernetes clusters. The mechanism uses a <a href="https://kubernetes.io/docs/reference/access-authn-authz/admission-controllers/">mutating webhook</a>, which is a standard Kubernetes component, but you don’t need to know all the details to use the Attacher. Essentially, you can install the Attacher, add one annotation to any Kubernetes deployment that has an application you want monitored, and that’s it!</p>
<p>In this blog, we’ll walk through a full example from scratch using a Java application. Apart from the Java code and using a JVM for the application, everything else works the same for the other languages supported by the Attacher.</p>
<h2 id="prerequisites">Prerequisites</h2>
<p>This walkthrough assumes that the following are already installed on the system: JDK 17, Docker, Kubernetes, and Helm.</p>
<h2 id="theexampleapplication">The example application</h2>
<p>While the application (shown below) is a Java application, it would be easily implemented in any language, as it is just a simple loop that every 2 seconds calls the method chain methodA-&gt;methodB-&gt;methodC-&gt;methodD, with methodC sleeping for 10 milliseconds and methodD sleeping for 200 milliseconds. The choice of application is just to be able to clearly display in the Elastic APM UI that the application is being monitored.</p>
<p>The Java application in full is shown here:</p>
<pre><code>package test;

public class Testing implements Runnable {

  public static void main(String[] args) {
    new Thread(new Testing()).start();
  }

  public void run()
  {
    while(true) {
      try {Thread.sleep(2000);} catch (InterruptedException e) {}
      methodA();
    }
  }

  public void methodA() {methodB();}

  public void methodB() {methodC();}

  public void methodC() {
    System.out.println("methodC executed");
    try {Thread.sleep(10);} catch (InterruptedException e) {}
    methodD();
  }

  public void methodD() {
    System.out.println("methodD executed");
    try {Thread.sleep(200);} catch (InterruptedException e) {}
  }
}
</code></pre>
<p>We created a Docker image containing that simple Java application for you that can be pulled from the following Docker repository:</p>
<pre><code>docker.elastic.co/demos/apm/k8s-webhook-test
</code></pre>
<h2 id="deploythepod">Deploy the pod</h2>
<p>First we need a deployment config. We’ll call the config file webhook-test.yaml, and the contents are pretty minimal — just pull the image and run that as a pod &amp; container called webhook-test in the default namespace:</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: webhook-test
  labels:
    app: webhook-test
spec:
  containers:
    - image: docker.elastic.co/demos/apm/k8s-webhook-test
      imagePullPolicy: Always
      name: webhook-test
</code></pre>
<p>This can be deployed normally using kubectl:</p>
<pre><code>kubectl apply -f webhook-test.yaml
</code></pre>
<p>The result is exactly as expected:</p>
<pre><code>$ kubectl get pods
NAME           READY   STATUS    RESTARTS   AGE
webhook-test   1/1     Running   0          10s

$ kubectl logs webhook-test
methodC executed
methodD executed
methodC executed
methodD executed
</code></pre>
<p>So far, this is just setting up a standard Kubernetes application with no APM monitoring. Now we get to the interesting bit: adding in auto-instrumentation.</p>
<h2 id="installelasticapmk8sattacher">Install Elastic APM K8s Attacher</h2>
<p>The first step is to install the <a href="https://www.elastic.co/guide/en/apm/attacher/current/index.html">Elastic APM K8s Attacher</a>. This only needs to be done once for the cluster — once installed, it is always available. Before installation, we will define where the monitored data will go. As you will see later, we can decide or change this any time. For now, we’ll specify our own Elastic APM server, which is at https://myserver.somecloud:443 — we also have a secret token for authorization to that Elastic APM server, which has value MY_SECRET_TOKEN. (If you want to set up a quick test Elastic APM server, you can do so at <a href="https://cloud.elastic.co/">https://cloud.elastic.co/</a>).</p>
<p>There are two additional environment variables set for the application that are not generally needed but will help when we see the resulting UI content toward the end of the walkthrough (when the agent is auto-installed, these two variables tell the agent what name to give this application in the UI and what method to trace). Now we just need to define the custom yaml file to hold these. On installation, the custom yaml will be merged into the yaml for the Attacher:</p>
<pre><code>apm:
  secret_token: MY_SECRET_TOKEN
  namespaces:
    - default
webhookConfig:
  agents:
    java:
      environment:
        ELASTIC_APM_SERVER_URL: "https://myserver.somecloud:443"
        ELASTIC_APM_TRACE_METHODS: "test.Testing#methodB"
        ELASTIC_APM_SERVICE_NAME: "webhook-test"
</code></pre>
<p>That custom.yaml file is all we need to install the attacher (note we’ve only specified the default namespace for agent auto-installation for now — this can be easily changed, as you’ll see later). Next we’ll add the Elastic charts to helm — this only needs to be done once, then all Elastic charts are available to helm. This is the usual helm add repo command, specifically:</p>
<pre><code>helm repo add elastic https://helm.elastic.co
</code></pre>
<p>Now the Elastic charts are available for installation (helm search repo would show you all the available charts). We’re going to use “elastic-webhook” as the name to install into, resulting in the following installation command:</p>
<pre><code>helm install elastic-webhook elastic/apm-attacher --namespace=elastic-apm --create-namespace --values custom.yaml
</code></pre>
<p>And that’s it, we now have the Elastic APM K8s Attacher installed and set to send data to the APM server defined in the custom.yaml file! (You can confirm installation with a helm list -A if you need.)</p>
<h2 id="autoinstallthejavaagent">Auto-install the Java agent</h2>
<p>The Elastic APM K8s Attacher is installed, but it doesn’t auto-install the APM application agents into every pod — that could lead to problems! Instead the Attacher is deliberately limited to auto-install agents into deployments defined a) by the namespaces listed in the custom.yaml, and b) to those deployments in those namespaces that have a specific annotation “co.elastic.apm/attach.”</p>
<p>So for now, restarting the webhook-test pod we created above won’t have any different effect on the pod, as it isn’t yet set to be monitored. What we need to do is add the annotation. Specifically, we need to add the annotation using the default agent configuration that was installed with the Attacher called “java” for the Java agent (we’ll see later how that agent configuration is altered — the default configuration installs the latest agent version and leaves everything else default for that version). So adding that annotation in to webhook-test yaml gives us the new yaml file contents (the additional config is shown labelled (1)):</p>
<pre><code>apiVersion: v1
kind: Pod
metadata:
  name: webhook-test
  annotations: #(1)
    co.elastic.apm/attach: java #(1)
  labels:
    app: webhook-test
spec:
  containers:
    - image: docker.elastic.co/demos/apm/k8s-webhook-test
      imagePullPolicy: Always
      name: webhook-test
</code></pre>
<p>Applying this change gives us the application now monitored:</p>
<pre><code>$ kubectl delete -f webhook-test.yaml
pod "webhook-test" deleted
$ kubectl apply -f webhook-test.yaml
pod/webhook-test created
$ kubectl logs webhook-test
… StartupInfo - Starting Elastic APM 1.45.0 …
</code></pre>
<p>And since the agent is now feeding data to our APM server, we can now see it in the UI:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltca8638b16b1aa112/6a85cb3b2d64d52d74081d44/webhook-test-k8s-blog.png" alt="webhook-test" /></p>
<p>Note that the agent identifies Testing.methodB method as a trace root because of the ELASTIC_APM_TRACE_METHODS environment variable set to test.Testing#methodB in the custom.yaml — this tells the agent to specifically trace that method. The time taken by that method will be available in the UI for each invocation, but we don’t see the sub-methods . . . currently. In the next section, we’ll see how easy it is to customize the Attacher, and in doing so we’ll see more detail about the method chain being executed in the application.</p>
<h2 id="customizingtheagents">Customizing the agents</h2>
<p>In your systems, you’ll likely have development, testing, and production environments. You’ll want to specify the version of the agent to use rather than just pull the latest version whatever that is, you’ll want to have debug on for some applications or instances, and you’ll want to have specific options set to specific values. This sounds like a lot of effort, but the attacher lets you enable these kinds of changes in a very simple way. In this section, we’ll add a configuration that specifies all these changes and we can see just how easy it is to configure and enable it.</p>
<p>We start at the custom.yaml file we defined above. This is the file that gets merged into the Attacher. Adding a new configuration with all the items listed in the last paragraph is easy — though first we need to decide a name for our new configuration. We’ll call it “java-interesting” here. The new custom.yaml in full is (the first part is just the same as before, the new config is simply appended):</p>
<pre><code>apm:
  secret_token: MY_SECRET_TOKEN
  namespaces:
    - default
webhookConfig:
  agents:
    java:
      environment:
        ELASTIC_APM_SERVER_URL: "https://myserver.somecloud:443"
        ELASTIC_APM_TRACE_METHODS: "test.Testing#methodB"
        ELASTIC_APM_SERVICE_NAME: "webhook-test"
    java-interesting:
      image: docker.elastic.co/observability/apm-agent-java:1.55.4
      artifact: "/usr/agent/elastic-apm-agent.jar"
      environment:
        ELASTIC_APM_SERVER_URL: "https://myserver.somecloud:443"
        ELASTIC_APM_TRACE_METHODS: "test.Testing#methodB"
        ELASTIC_APM_SERVICE_NAME: "webhook-test"
        ELASTIC_APM_ENVIRONMENT: "testing"
        ELASTIC_APM_LOG_LEVEL: "debug"
        ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED: "true"
        JAVA_TOOL_OPTIONS: "-javaagent:/elastic/apm/agent/elastic-apm-agent.jar"
</code></pre>
<p>Breaking the additional config down, we have:</p>
<ul>
<li><p>The name of the new config java-interesting</p></li>
<li><p>The APM Java agent image docker.elastic.co/observability/apm-agent-java</p></li>
<li><p>With a specific version 1.43.0 instead of latest</p></li>
<li><p>We need to specify the agent jar location (the attacher puts it here)</p></li>
<li><p>artifact: "/usr/agent/elastic-apm-agent.jar"</p></li>
<li><p>And then the environment variables</p></li>
<li><p>ELASTIC_APM_SERVER_URL as before</p></li>
<li><p>ELASTIC_APM_ENVIRONMENT set to testing, useful when looking in the UI</p></li>
<li><p>ELASTIC_APM_LOG_LEVEL set to debug for more detailed agent output</p></li>
<li><p>ELASTIC_APM_PROFILING_INFERRED_SPANS_ENABLED turning this on (setting to true) will give us additional interesting information about the method chain being executed in the application</p></li>
<li><p>And lastly we need to set JAVA_TOOL_OPTIONS to the enable starting the agent "-javaagent:/elastic/apm/agent/elastic-apm-agent.jar" — this is fundamentally how the attacher auto-attaches the Java agent</p></li>
</ul>
<p>More configurations and details about configuration options are <a href="https://www.elastic.co/guide/en/apm/agent/java/current/configuration.html">here for the Java agent</a>, and <a href="https://www.elastic.co/guide/en/apm/agent/index.html">other language agents</a> are also available.</p>
<h2 id="theapplicationtracedwiththenewconfiguration">The application traced with the new configuration</h2>
<p>And finally we just need to upgrade the attacher with the changed custom.yaml:</p>
<pre><code>helm upgrade elastic-webhook elastic/apm-attacher --namespace=elastic-apm --create-namespace --values custom.yaml
</code></pre>
<p>This is the same command as the original install, but now using upgrade. That’s it — add config to the custom.yaml and upgrade the attacher, and it’s done! Simple.</p>
<p>Of course we still need to use the new config on an app. In this case, we’ll edit the existing webhook-test.yaml file, replacing java with java-interesting, so the annotation line is now:</p>
<pre><code>co.elastic.apm/attach: java-interesting
</code></pre>
<p>Applying the new pod config and restarting the pod, you can see the logs now hold debug output:</p>
<pre><code>$ kubectl delete -f webhook-test.yaml
pod "webhook-test" deleted
$ kubectl apply -f webhook-test.yaml
pod/webhook-test created
$ kubectl logs webhook-test
… StartupInfo - Starting Elastic APM 1.44.0 …
… DEBUG co.elastic.apm.agent. …
… DEBUG co.elastic.apm.agent. …
</code></pre>
<p>More interesting is the UI. Now that inferred spans is on, the full method chain is visible.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf182dab36de35372/6a85cb3d6826668f5b1eac03/trace-sample-k8s-blog.png" alt="trace sample" /></p>
<p>This gives the details for methodB (it takes 211 milliseconds because it calls methodC - 10ms - which calls methodD - 200ms). The times for methodC and methodD are inferred rather than recorded, (inferred rather than traced — if you needed accurate times you would instead add the methods to trace_methods and have them traced too).</p>
<h2 id="noteontheeckoperator">Note on the ECK operator</h2>
<p>The <a href="https://www.elastic.co/guide/en/cloud-on-k8s/master/k8s-overview.html">Elastic Cloud on Kubernetes operator</a> allows you to install and manage a number of other Elastic components on Kubernetes. At the time of publication of this blog, the <a href="https://www.elastic.co/guide/en/apm/attacher/current/index.html">Elastic APM K8s Attacher</a> is a separate component, and there is no conflict between these management mechanisms — they apply to different components and are independent of each other.</p>
<h2 id="tryityourself">Try it yourself!</h2>
<p>This walkthrough is easily repeated on your system, and you can make it more useful by replacing the example application with your own and the Docker registry with the one you use.</p>
<p><a href="https://www.elastic.co/observability/kubernetes-monitoring">Learn more about real-time monitoring with Kubernetes and Elastic Observability</a>.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/application-monitoring-kubernetes-pods</link>
    <guid isPermaLink="false">application-monitoring-kubernetes-pods</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[APM]]></category>
    <dc:creator><![CDATA[Jack Shirazi,Sylvain Juge,Alexander Wert]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbececa85f67fdcd1/6a85cb40eaf24581f5a49f65/139689_-_Blog_Header_Banner_V1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 17 Jan 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Collecting OpenShift container logs using Red Hat’s OpenShift Logging Operator]]></title>
    <description><![CDATA[Learn how to optimize OpenShift logs collected with Red Hat OpenShift Logging Operator, as well as format and route them efficiently in Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>This blog explores a possible approach to collecting and formatting OpenShift Container Platform logs and audit logs with Red Hat OpenShift Logging Operator. We recommend using Elastic® Agent for the best possible experience! We will also show how to format the logs to Elastic Common Schema (<a href="https://www.elastic.co/guide/en/ecs/current/index.html">ECS</a>) for the best experience viewing, searching, and visualizing your logs. All examples in this blog are based on OpenShift 4.14.</p>
<h2 id="whyuseopenshiftloggingoperator">Why use OpenShift Logging Operator?</h2>
<p>A lot of enterprise customers use OpenShift as their orchestrating solution. The advantages of this approach are:</p>
<ul>
<li><p>It is developed and supported by Red Hat</p></li>
<li><p>It can automatically update the OpenShift cluster along with the Operating system to make sure that they are and remain compatible</p></li>
<li><p>It can speed up developing life cycles with features like source to image</p></li>
<li><p>It uses enhanced security</p></li>
</ul>
<p>In our consulting experience, this latter aspect poses challenges and frictions with OpenShift administrators when we try to install an Elastic Agent to collect the logs of the pods. Indeed, Elastic Agent requires the files of the host to be mounted in the pod, and it also needs to be run in privileged mode. (Read more about the permissions required by Elastic Agent in the <a href="https://www.elastic.co/guide/en/fleet/current/running-on-kubernetes-standalone.html#_red_hat_openshift_configuration">official Elasticsearch® Documentation</a>). While the solution we explore in this post requires similar privileges under the hood, it is managed by the OpenShift Logging Operator, which is developed and supported by Red Hat.</p>
<h2 id="whichlogsarewegoingtocollect">Which logs are we going to collect?</h2>
<p>In OpenShift Container Platform, we distinguish <a href="https://docs.openshift.com/container-platform/4.14/logging/cluster-logging.html#logging-architecture-overview_cluster-logging">three broad categories of logs</a>: audit, application, and infrastructure logs:</p>
<ul>
<li><p><strong>Audit logs</strong> describe the list of activities that affected the system by users, administrators, and other components.</p></li>
<li><p><strong>Application logs</strong> are composed of the container logs of the pods running in non-reserved namespaces.</p></li>
<li><p><strong>Infrastructure logs</strong> are composed of container logs of the pods running in reserved namespaces like openshift*, kube*, and default along with journald messages from the nodes.</p></li>
</ul>
<p>In the following, we will consider only audit and application logs for the sake of simplicity. In this post, we will describe how to format audit and application Logs in the format expected by the Kubernetes integration to take the most out of Elastic Observability.</p>
<h2 id="gettingstarted">Getting started</h2>
<p>To collect the logs from OpenShift, we must perform some preparation steps in Elasticsearch and OpenShift.</p>
<h3 id="insideelasticsearch">Inside Elasticsearch</h3>
<p>We first <a href="https://www.elastic.co/guide/en/fleet/8.11/install-uninstall-integration-assets.html#install-integration-assets">install the Kubernetes integration assets</a>. We are mainly interested in the index templates and ingest pipelines for the logs-kubernetes.container_logs and logs-kubernetes.audit_logs.</p>
<p>To format the logs received from the ClusterLogForwarder in <a href="https://www.elastic.co/guide/en/ecs/current/index.html">ECS</a> format, we will define a pipeline to normalize the container logs. The field naming convention used by OpenShift is slightly different from that used by ECS. To get a list of exported fields from OpenShift, refer to <a href="https://docs.openshift.com/container-platform/4.14/logging/cluster-logging-exported-fields.html">Exported fields | Logging | OpenShift Container Platform 4.14</a>. To get a list of exported fields of the Kubernetes integration, you can refer to <a href="https://www.elastic.co/guide/en/beats/filebeat/current/exported-fields-kubernetes-processor.html">Kubernetes fields | Filebeat Reference [8.11] | Elastic</a> and <a href="https://www.elastic.co/guide/en/observability/current/logs-app-fields.html">Logs app fields | Elastic Observability [8.11]</a>. Further, specific fields like kubernetes.annotations must be normalized by replacing dots with underscores. This operation is usually done automatically by Elastic Agent.</p>
<pre><code>PUT _ingest/pipeline/openshift-2-ecs
{
  "processors": [
    {
      "rename": {
        "field": "kubernetes.pod_id",
        "target_field": "kubernetes.pod.uid",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.pod_ip",
        "target_field": "kubernetes.pod.ip",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.pod_name",
        "target_field": "kubernetes.pod.name",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.namespace_name",
        "target_field": "kubernetes.namespace",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.namespace_id",
        "target_field": "kubernetes.namespace_uid",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.container_id",
        "target_field": "container.id",
        "ignore_missing": true
      }
    },
    {
      "dissect": {
        "field": "container.id",
        "pattern": "%{container.runtime}://%{container.id}",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.container_image",
        "target_field": "container.image.name",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "kubernetes.container.image",
        "copy_from": "container.image.name",
        "ignore_failure": true
      }
    },
    {
      "set": {
        "copy_from": "kubernetes.container_name",
        "field": "container.name",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "kubernetes.container_name",
        "target_field": "kubernetes.container.name",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "kubernetes.node.name",
        "copy_from": "hostname",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "hostname",
        "target_field": "host.name",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "level",
        "target_field": "log.level",
        "ignore_missing": true
      }
    },
    {
      "rename": {
        "field": "file",
        "target_field": "log.file.path",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "copy_from": "openshift.cluster_id",
        "field": "orchestrator.cluster.name",
        "ignore_failure": true
      }
    },
    {
      "dissect": {
        "field": "kubernetes.pod_owner",
        "pattern": "%{_tmp.parent_type}/%{_tmp.parent_name}",
        "ignore_missing": true
      }
    },
    {
      "lowercase": {
        "field": "_tmp.parent_type",
        "ignore_missing": true
      }
    },
    {
      "set": {
        "field": "kubernetes.pod.{{_tmp.parent_type}}.name",
        "value": "{{_tmp.parent_name}}",
        "if": "ctx?._tmp?.parent_type != null",
        "ignore_failure": true
      }
    },
    {
      "remove": {
        "field": [
          "_tmp",
          "kubernetes.pod_owner"
          ],
          "ignore_missing": true
      }
    },
    {
      "script": {
        "description": "Normalize kubernetes annotations",
        "if": "ctx?.kubernetes?.annotations != null",
        "source": """
        def keys = new ArrayList(ctx.kubernetes.annotations.keySet());
        for(k in keys) {
          if (k.indexOf(".") &gt;= 0) {
            def sanitizedKey = k.replace(".", "_");
            ctx.kubernetes.annotations[sanitizedKey] = ctx.kubernetes.annotations[k];
            ctx.kubernetes.annotations.remove(k);
          }
        }
        """
      }
    },
    {
      "script": {
        "description": "Normalize kubernetes namespace_labels",
        "if": "ctx?.kubernetes?.namespace_labels != null",
        "source": """
        def keys = new ArrayList(ctx.kubernetes.namespace_labels.keySet());
        for(k in keys) {
          if (k.indexOf(".") &gt;= 0) {
            def sanitizedKey = k.replace(".", "_");
            ctx.kubernetes.namespace_labels[sanitizedKey] = ctx.kubernetes.namespace_labels[k];
            ctx.kubernetes.namespace_labels.remove(k);
          }
        }
        """
      }
    },
    {
      "script": {
        "description": "Normalize special Kubernetes Labels used in logs-kubernetes.container_logs to determine service.name and service.version",
        "if": "ctx?.kubernetes?.labels != null",
        "source": """
        def keys = new ArrayList(ctx.kubernetes.labels.keySet());
        for(k in keys) {
          if (k.startsWith("app_kubernetes_io_component_")) {
            def sanitizedKey = k.replace("app_kubernetes_io_component_", "app_kubernetes_io_component/");
            ctx.kubernetes.labels[sanitizedKey] = ctx.kubernetes.labels[k];
            ctx.kubernetes.labels.remove(k);
          }
        }
        """
      }
    }
    ]
}
</code></pre>
<p>Similarly, to handle the audit logs like the ones collected by Kubernetes, we define an ingest pipeline:</p>
<pre><code>PUT _ingest/pipeline/openshift-audit-2-ecs
{
  "processors": [
    {
      "script": {
        "source": """
        def audit = [:];
        def keyToRemove = [];
        for(k in ctx.keySet()) {
          if (k.indexOf('_') != 0 &amp;&amp; !['@timestamp', 'data_stream', 'openshift', 'event', 'hostname'].contains(k)) {
            audit[k] = ctx[k];
            keyToRemove.add(k);
          }
        }
        for(k in keyToRemove) {
          ctx.remove(k);
        }
        ctx.kubernetes=["audit":audit];
        """,
        "description": "Move all the 'kubernetes.audit' fields under 'kubernetes.audit' object"
      }
    },
    {
      "set": {
        "copy_from": "openshift.cluster_id",
        "field": "orchestrator.cluster.name",
        "ignore_failure": true
      }
    },
    {
      "set": {
        "field": "kubernetes.node.name",
        "copy_from": "hostname",
        "ignore_failure": true
      }
    },
    {
      "rename": {
        "field": "hostname",
        "target_field": "host.name",
        "ignore_missing": true
      }
    },
    {
      "script": {
        "if": "ctx?.kubernetes?.audit?.annotations != null",
        "source": """
          def keys = new ArrayList(ctx.kubernetes.audit.annotations.keySet());
          for(k in keys) {
            if (k.indexOf(".") &gt;= 0) {
              def sanitizedKey = k.replace(".", "_");
              ctx.kubernetes.audit.annotations[sanitizedKey] = ctx.kubernetes.audit.annotations[k];
              ctx.kubernetes.audit.annotations.remove(k);
            }
          }
          """,
        "description": "Normalize kubernetes audit annotations field as expected by the Integration"
      }
    }
  ]
}
</code></pre>
<p>The main objective of the pipeline is to mimic what Elastic Agent is doing: storing all audit fields under the kubernetes.audit object.</p>
<p>We are not going to use the conventional @custom pipeline approach because the fields must be normalized before invoking the logs-kubernetes.container_logs integration pipeline that uses fields like kubernetes.container.name and kubernetes.labels to determine the fields service.name and service.version. Read more about custom pipelines in <a href="https://www.elastic.co/guide/en/fleet/8.11/data-streams-pipeline-tutorial.html#data-streams-pipeline-one">Tutorial: Transform data with custom ingest pipelines | Fleet and Elastic Agent Guide [8.11]</a>.</p>
<p>The OpenShift Cluster Log Forwarder writes the data in the indices app-write and audit-write by default. It is possible to change this behavior, but it still tries to prepend the prefix “app” and the suffix “write”, so we opted to send the data to the default destination and use the reroute processor to send it to the right data streams. Read more about the Reroute Processor in our blog <a href="https://www.elastic.co/blog/simplifying-log-data-management-flexible-routing-elastic">Simplifying log data management: Harness the power of flexible routing with Elastic</a> and our documentation <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/reroute-processor.html">Reroute processor | Elasticsearch Guide [8.11] | Elastic</a>.</p>
<p>In this case, we want to redirect the container logs (app-write index) to logs-kubernetes.container_logs and the Audit logs (audit-write) to logs-kubernetes.audit_logs:</p>
<pre><code>PUT _ingest/pipeline/app-write-reroute-pipeline
{
  "processors": [
    {
      "pipeline": {
        "name": "openshift-2-ecs",
        "description": "Format the Openshift data in ECS"
      }
    },
    {
      "set": {
        "field": "event.dataset",
        "value": "kubernetes.container_logs"
      }
    },
    {
      "reroute": {
        "destination": "logs-kubernetes.container_logs-openshift"
      }
    }
  ]
}



PUT _ingest/pipeline/audit-write-reroute-pipeline
{
  "processors": [
    {
      "pipeline": {
        "name": "openshift-audit-2-ecs",
        "description": "Format the Openshift data in ECS"
      }
    },
    {
      "set": {
        "field": "event.dataset",
        "value": "kubernetes.audit_logs"
      }
    },
    {
      "reroute": {
        "destination": "logs-kubernetes.audit_logs-openshift"
      }
    }
  ]
}
</code></pre>
<p>Please note that given that app-write and audit-write do not follow the data stream naming convention, we are forced to add the destination field in the reroute processor. The reroute processor will also fill up the <a href="https://www.elastic.co/guide/en/ecs/8.11/ecs-data_stream.html">data_stream fields</a> for us. Note that this step is done automatically by Elastic Agent at source.</p>
<p>Further, we create the indices with the default pipelines we created to reroute the logs according to our needs.</p>
<pre><code>PUT app-write
{
  "settings": {
      "index.default_pipeline": "app-write-reroute-pipeline"
   }
}


PUT audit-write
{
  "settings": {
    "index.default_pipeline": "audit-write-reroute-pipeline"
  }
}
</code></pre>
<p>Basically, what we did can be summarized in this picture:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80ce5b83551cdde/6a7f0f616693f891e9664031/openshift-summary-blog.png" alt="openshift-summary-blog" /></p>
<p>Let us take the container logs. When the operator attempts to write in the app-write index, it will invoke the default_pipeline “app-write-reroute-pipeline” that formats the logs into ECS format and reroutes the logs to logs-kubernetes.container_logs-openshift datastreams. This calls the integration pipeline that invokes, if it exists, the logs-kubernetes.container_logs@custom pipeline. Finally, the logs-kubernetes_container_logs pipeline may reroute the logs to another data set and namespace utilizing the elastic.co/dataset and elastic.co/namespace annotations as described in the Kubernetes <a href="https://docs.elastic.co/integrations/kubernetes/container-logs#rerouting-based-on-pod-annotations">integration documentation</a>, which in turn can lead to the execution of an another integration pipeline.</p>
<h3 id="createauserforsendingthelogs">Create a user for sending the logs</h3>
<p>We are going to use basic authentication because, at the time of writing, it is the only supported authentication method for Elasticsearch in OpenShift logging. Thus, we need a role that allows the user to write and read the app-write, and audit-write logs (required by the OpenShift agent) and auto_configure access to logs-*-* to allow custom Kubernetes rerouting:</p>
<pre><code>PUT _security/role/YOURROLE
{
    "cluster": [
      "monitor"
    ],
    "indices": [
      {
        "names": [
          "logs-*-*"
        ],
        "privileges": [
          "auto_configure",
          "create_doc"
        ],
        "allow_restricted_indices": false
      },
      {
        "names": [
          "app-write",
          "audit-write",
        ],
        "privileges": [
          "create_doc",
          "read"
        ],
        "allow_restricted_indices": false
      }
    ],
    "applications": [],
    "run_as": [],
    "metadata": {},
    "transient_metadata": {
      "enabled": true
    }

}



PUT _security/user/YOUR_USERNAME
{
  "password": "YOUR_PASSWORD",
  "roles": ["YOURROLE"]
}
</code></pre>
<h3 id="onopenshift">On OpenShift</h3>
<p>On the OpenShift Cluster, we need to follow the <a href="https://docs.openshift.com/container-platform/4.14/logging/log_collection_forwarding/log-forwarding.html">official documentation</a> of Red Hat on how to install the Red Hat OpenShift Logging and configure Cluster Logging and the Cluster Log Forwarder.</p>
<p>We need to install the Red Hat OpenShift Logging Operator, which defines the ClusterLogging and ClusterLogForwarder Resources. Afterward, we can define the Cluster Logging resource:</p>
<pre><code>apiVersion: logging.openshift.io/v1
kind: ClusterLogging
metadata:
  name: instance
  namespace: openshift-logging
spec:
  collection:
    logs:
      type: vector
      vector: {}
</code></pre>
<p>The Cluster Log Forwarder is the resource responsible for defining a daemon set that will forward the logs to the remote Elasticsearch. Before creating it, we need to create in the same namespace as the ClusterLogForwarder a secret containing the Elasticsearch credentials for the user we created previously in the namespace, where the ClusterLogForwarder will be deployed:</p>
<pre><code>apiVersion: v1
kind: Secret
metadata:
  name: elasticsearch-password
  namespace: openshift-logging
type: Opaque
stringData:
  username: YOUR_USERNAME
  password: YOUR_PASSWORD
</code></pre>
<p>Finally, we create the ClusterLogForwarder resource:</p>
<pre><code>kind: ClusterLogForwarder
apiVersion: logging.openshift.io/v1
metadata:
  name: instance
  namespace: openshift-logging
spec:
  outputs:
    - name: remote-elasticsearch
      secret:
        name: elasticsearch-password
      type: elasticsearch
      url: "https://YOUR_ELASTICSEARCH_URL:443"
      elasticsearch:
        version: 8 # The default is version 6 with the _type field
  pipelines:
    - inputRefs:
        - application
        - audit
      name: enable-default-log-store
      outputRefs:
        - remote-elasticsearch
</code></pre>
<p>Note that we explicitly defined the version of Elasticsearch to be 8, otherwise the ClusterLogForwarder will send the _type field, which is not compatible with Elasticsearch 8 and that we collect only application and audit logs.</p>
<h2 id="result">Result</h2>
<p>Once the logs are collected and passed through all the pipelines, the result is very close to the out-of-the-box Kubernetes integration. There are important differences, like the lack of host and cloud metadata information that don’t seem to be collected (at least without an additional configuration). We can view the Kubernetes container logs in the logs explorer:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt71059bb9bce109a8/6a7f0f64c2cc09a7662496be/openshift-summary-blog-graphs.png" alt="openshift-summary-blog-graphs" /></p>
<p>In this post, we described how you can use the OpenShift Logging Operator to collect the logs of containers and audit logs. We still recommend leveraging Elastic Agent to collect all your logs. It is the best user experience you can get. No need to maintain or transform the logs yourself to ECS formatting. Additionally, Elastic Agent uses API keys as the authentication method and collects metadata like cloud information that allow you in the long run to do <a href="https://www.elastic.co/blog/optimize-cloud-resources-cost-apm-metadata-elastic-observability">more</a>.</p>
<p><a href="https://www.elastic.co/observability/log-monitoring">Learn more about log monitoring with the Elastic Stack</a>.</p>
<p><em>Have feedback on this blog?</em> <a href="https://github.com/herrBez/elastic-blog-openshift-logging/issues"><em>Share it here</em></a><em>.</em></p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/openshift-container-logs-red-hat-logging-operator</link>
    <guid isPermaLink="false">openshift-container-logs-red-hat-logging-operator</guid>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Mirko Bez,David Ricordel,Philipp Kahr]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt72834ffb0a4604c9/6a7f0f6773d9bdff3c29dc3b/139687_-_Blog_Header_Banner_V1.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 16 Jan 2024 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Enhancing SRE troubleshooting with the AI Assistant for Observability and your organization's runbooks]]></title>
    <description><![CDATA[Empower your SRE team with this guide to enriching Elastic's AI Assistant Knowledge Base with your organization's internal observability information for enhanced alert remediation and incident management.]]></description>
    <content:encoded><![CDATA[<p>The <a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Observability AI Assistant</a> helps users explore and analyze observability data using a natural language interface, by leveraging automatic function calling to request, analyze, and visualize your data to transform it into actionable observability. The Assistant can also set up a Knowledge Base, powered by <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">Elastic Learned Sparse EncodeR</a> (ELSER) to provide additional context and recommendations from private data, alongside the large language models (LLMs) using RAG (Retrieval Augmented Generation). Elastic’s Stack — as a vector database with out-of-the-box semantic search and connectors to LLM integrations and the Observability solution — is the perfect toolkit to extract the maximum value of combining your company's unique observability knowledge with generative AI.</p>
<h2 id="enhancedtroubleshootingforsres">Enhanced troubleshooting for SREs</h2>
<p>Site reliability engineers (SRE) in large organizations often face challenges in locating necessary information for troubleshooting alerts, monitoring systems, or deriving insights due to scattered and potentially outdated resources. This issue is particularly significant for less experienced SREs who may require assistance even with the presence of a runbook. Recurring incidents pose another problem, as the on-call individual may lack knowledge about previous resolutions and subsequent steps. Mature SRE teams often invest considerable time in system improvements to minimize "fire-fighting," utilizing extensive automation and documentation to support on-call personnel.</p>
<p>Elastic® addresses these challenges by combining generative AI models with relevant search results from your internal data using RAG. The <a href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html">Observability AI Assistant's internal Knowledge Base</a>, powered by our semantic search retrieval model <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a>, can recall information at any point during a conversation, providing RAG responses based on internal knowledge.</p>
<p>This Knowledge Base can be enriched with your organization's information, such as runbooks, GitHub issues, internal documentation, and Slack messages, allowing the AI Assistant to provide specific assistance. The Assistant can also document and store specific information from an ongoing conversation with an SRE while troubleshooting issues, effectively creating runbooks for future reference. Furthermore, the Assistant can generate summaries of incidents, system status, runbooks, post-mortems, or public announcements.</p>
<p>This ability to retrieve, summarize, and present contextually relevant information is a game-changer for SRE teams, transforming the work from chasing documents and data to an intuitive, contextually sensitive user experience.The Knowledge Base (see <a href="https://www.elastic.co/guide/en/observability/current/obs-ai-assistant.html#obs-ai-requirements">requirements</a>) serves as a central repository of Observability knowledge, breaking documentation silos and integrating tribal knowledge, making this information accessible to SREs enhanced with the power of LLMs.</p>
<p>Your LLM provider may collect query telemetry when using the AI Assistant. If your data is confidential or has sensitive details, we recommend you verify the data treatment policy of the LLM connector you provided to the AI Assistant.</p>
<p>In this blog post, we will cover different ways to enrich your Knowledge Base (KB) with internal information. We will focus on a specific alert, indicating that there was an increase in logs with “502 Bad Gateway” errors that has surpassed the alert’s threshold.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt432418d2871ff281/6a7f1b0873d9bd18b729df69/elastic-blog-1.png" alt="1 - threshold breached" /></p>
<h2 id="howtotroubleshootanalertwiththeknowledgebase">How to troubleshoot an alert with the Knowledge Base</h2>
<p>Before the KB has been enriched with internal information, when the SRE asks the AI Assistant about how to troubleshoot an alert, the response from the LLM will be based on the data it learned during training; however, the LLM is not able to answer questions related to private, recent, or emerging knowledge. In this case, when asking for the steps to troubleshoot the alert, the response will be based on generic information.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2577ea1ce6b71b1/6a7f1b0b05b7b519c318bd51/elastic-blog-2.png" alt="2 - troubleshooting steps" /></p>
<p>However, once the KB has been enriched with your runbooks, when your team receives a new alert on “502 Bad Gateway” Errors, they can use AI Assistant to access the internal knowledge to troubleshoot it, using semantic search to find the appropriate runbook in the Knowledge Base.</p>
<p>In this blog, we will cover different ways to add internal information on how to troubleshoot an alert to the Knowledge Base:</p>
<ol>
<li><p>Ask the assistant to remember the content of an existing runbook.</p></li>
<li><p>Ask the Assistant to summarize and store in the Knowledge Base the steps taken during a conversation and store it as a runbook.</p></li>
<li><p>Import your runbooks from GitHub or another external source to the Knowledge Base using our Connector and APIs.</p></li>
</ol>
<p>After the runbooks have been added to the KB, the AI Assistant is now able to recall the internal and specific information in the runbooks. By leveraging the retrieved information, the LLM could provide more accurate and relevant recommendations for troubleshooting the alert. This could include suggesting potential causes for the alert, steps to resolve the issue, preventative measures for future incidents, or asking the assistant to help execute the steps mentioned in the runbook using functions. With more accurate and relevant information at hand, the SRE could potentially resolve the alert more quickly, reducing downtime and improving service reliability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte9f381e6f96debcb/6a7f1b0e73d9bd5ba529df6d/Screenshot_2023-11-10_at_9.52.38_AM.png" alt="3 - troubleshooting 502 Bad gateway" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7975cbd842b6cd8/6a7f1b11c2e91480da016ffa/elastic-blog-4.png" alt="4 - (5) test the backend directly" /></p>
<p>Your Knowledge Base documents will be stored in the indices <em>.kibana-observability-ai-assistant-kb-</em>*. Have in mind that LLMs have restrictions on the amount of information the model can read and write at once, called token limit. Imagine you're reading a book, but you can only remember a certain number of words at a time. Once you've reached that limit, you start to forget the earlier words you've read. That's similar to how a token limit works in an LLM.</p>
<p>To keep runbooks within the token limit for Retrieval Augmented Generation (RAG) models, ensure the information is concise and relevant. Use bullet points for clarity, avoid repetition, and use links for additional information. Regularly review and update the runbooks to remove outdated or irrelevant information. The goal is to provide clear, concise, and effective troubleshooting information without compromising the quality due to token limit constraints. LLMs are great for summarization, so you could ask the AI Assistant to help you make the runbooks more concise.</p>
<h2 id="asktheassistanttorememberthecontentofanexistingrunbook">Ask the assistant to remember the content of an existing runbook</h2>
<p>The easiest way to store a runbook into the Knowledge Base is to just ask the AI Assistant to do it! Open a new conversation and ask “Can you store this runbook in the KB for future reference?” followed by pasting the content of the runbook in plain text.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt88e751fe6aabf649/6a7f1b146c6eac1f20f145b5/elastic-blog-5.png" alt="5 - new conversation - let's work on this together" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcbdbc2440f80ddda/6a7f1b1696b5a6f0e687b89f/elastic-blog-6.png" alt="6 - new converastion" /></p>
<p>The AI Assistant will then store it in the Knowledge Base for you automatically, as simple as that.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt678daca55cfe0ff7/6a7f1b19fc63ab131a64d08e/elastic-blog-7.png" alt="7 - storing a runbook" /></p>
<h2 id="asktheassistanttosummarizeandstorethestepstakenduringaconversationintheknowledgebase">Ask the Assistant to summarize and store the steps taken during a conversation in the Knowledge Base</h2>
<p>You can also ask the AI Assistant to remember something while having a conversation — for example, after you have troubleshooted an alert using the AI Assistant, you could ask to "remember how to troubleshoot this alert for next time." The AI Assistant will create a summary of the steps taken to troubleshoot the alert and add it to the Knowledge Base, effectively creating runbooks for future reference. Next time you are faced with a similar situation, the AI Assistant will recall this information and use it to assist you.</p>
<p>In the following demo, the user asks the Assistant to remember the steps that have been followed to troubleshoot the root cause of an alert, and also to ping the Slack channel when this happens again. In a later conversation with the Assistant, the user asks what can be done about a similar problem, and the AI Assistant is able to remember the steps and also reminds the user to ping the Slack channel.</p>
<p>After receiving the alert, you can open the AI Assistant chat and test troubleshooting the alert. After investigating an alert, ask the AI Assistant to summarize the analysis and the steps taken to root cause. To remember them for the next time, we have a similar alert and add extra instruction like to warn the Slack channel.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt59ebedea4c01cbd8/6a7f1b1dbd21980b3c7584bf/elastic-blog-8.png" alt="8. -teal box" /></p>
<p>The Assistant will use the built-in functions to summarize the steps and store them into your Knowledge Base, so they can be recalled in future conversations.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ab3c30c33361206/6a7f1b20c2cc0977992499ca/Screenshot_2023-11-08_at_11.34.08_AM.png" alt="9 - Elastic assistant chat (CROP)" /></p>
<p>Open a new conversation, and ask what are the steps to take when troubleshooting a similar alert to the one we just investigated. The Assistant will be able to recall the information stored in the KB that is related to the specific alert, using semantic search based on <a href="https://www.elastic.co/guide/en/machine-learning/current/ml-nlp-elser.html">ELSER</a>, and provide a summary of the steps taken to troubleshoot it, including the last indication of informing the Slack channel.</p>
<div>
    
</div>
<h2 id="importyourrunbooksstoredingithubtotheknowledgebaseusingapisorourgithubconnector">Import your runbooks stored in GitHub to the Knowledge Base using APIs or our GitHub Connector</h2>
<p>You can also add proprietary data into the Knowledge Base programmatically by ingesting it (e.g., GitHub Issues, Markdown files, Jira tickets, text files) into Elastic.</p>
<p>If your organization has created runbooks that are stored in Markdown documents in GitHub, follow the steps in the next section of this blog post to index the runbook documents into your Knowledge Base.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt55e94e2bbca1d4c5/6a7f1b23ead8ec8c11baac5e/elastic-blog-10.png" alt="10 - github handling 502" /></p>
<p>The steps to ingest documents into the Knowledge Base are the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte50ccc306ed43ff6/6a7f1b26227b1cac36598a09/elastic-blog-11.png" alt="11 - using internal knowledge" /></p>
<h3 id="ingestyourorganizationsknowledgeintoelasticsearch">Ingest your organization’s knowledge into Elasticsearch</h3>
<p><strong>Option 1:</strong> <strong>Use the</strong> <a href="https://www.elastic.co/guide/en/enterprise-search/current/crawler.html"><strong>Elastic web crawler</strong></a> <strong>.</strong> Use the web crawler to programmatically discover, extract, and index searchable content from websites and knowledge bases. When you ingest data with the web crawler, a search-optimized <a href="https://www.elastic.co/blog/what-is-an-elasticsearch-index">Elasticsearch® index</a> is created to hold and sync webpage content.</p>
<p><strong>Option 2: Use Elasticsearch's</strong> <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html"><strong>Index API</strong></a> <strong>.</strong> <a href="https://www.elastic.co/guide/en/cloud/current/ec-ingest-guides.html">Watch tutorials</a> that demonstrate how you can use the Elasticsearch language clients to ingest data from an application.</p>
<p><strong>Option 3: Build your own connector.</strong> Follow the steps described in this blog: <a href="https://www.elastic.co/search-labs/how-to-create-customized-connectors-for-elasticsearch">How to create customized connectors for Elasticsearch</a>.</p>
<p><strong>Option 4: Use Elasticsearch</strong> <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-content-sources.html"><strong>Workplace Search connectors</strong></a> <strong>.</strong> For example, the <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-github-connector.html">GitHub connector</a> can automatically capture, sync, and index issues, Markdown files, pull requests, and repos.</p>
<ul>
<li>Follow the steps to <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-github-connector.html#github-configuration">configure the GitHub Connector in GitHub</a> to create an OAuth App from the GitHub platform.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt934623424f99c138/6a7f1b29bd21985ea47584c3/elastic-blog-12.png" alt="12 - elastic workplace search" /></p>
<ul>
<li>Now you can connect a GitHub instance to your organization. Head to your organization’s <strong>Search &gt; Workplace Search</strong> administrative dashboard, and locate the Sources tab.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt562317ca772f6b3d/6a7f1b2ceab5be27ce20ab08/Screenshot_2023-11-08_at_10.19.19_AM.png" alt="13 - screenshot" /></p>
<ul>
<li>Select <strong>GitHub</strong> (or GitHub Enterprise) in the Configured Sources list, and follow the GitHub authentication flow as presented. Upon the successful authentication flow, you will be redirected to Workplace Search and will be prompted to select the Organization you would like to synchronize.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c465cdefe93ce4d/6a7f1b2fde231504f2fd80af/elastic-blog-14.png" alt="14 - configure and connect" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt54d1b7727cb79ab9/6a7f1b32eab5be3b6220ab0c/elastic-blog-15.png" alt="15 - how to add github" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf86ab1779e93317c/6a7f1b35bdcff09009c432af/elastic-blog-16.png" alt="16 - github" /></p>
<ul>
<li>After configuring the connector and selecting the organization, the content should be synchronized and you will be able to see it in Sources. If you don’t need to index all the available content, you can specify the indexing rules via the API. This will help shorten indexing times and limit the size of the index. See <a href="https://www.elastic.co/guide/en/workplace-search/current/workplace-search-customizing-indexing-rules.html">Customizing indexing</a>.</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltded840b7b410baf3/6a7f1b37eab5be779e20ab10/elastic-blog-17.png" alt="17 - source overview" /></p>
<ul>
<li>The source has created an index in Elastic with the content (Issues, Markdown Files…) from your organization. You can find the index name by navigating to <strong>Stack Management &gt; Index Management</strong> , activating the <strong>Include hidden Indices</strong> button on the right, and searching for “GitHub.”</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt22ee12a22538f12f/6a7f1b3b05b7b517f518bd55/elastic-blog-18.png" alt="18 - index mgmt" /></p>
<ul>
<li>You can explore the documents you have indexed by creating a Data View and exploring it in Discover. Go to <strong>Stack Management &gt; Kibana &gt; Data Views &gt; Create data view</strong> and introduce the data view Name, Index pattern (make sure you activate “Allow hidden and system indices” in advanced options), and Timestamp field:</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt10bb270db2987d53/6a7f1b3eb437702e514d711e/elastic-blog-19.png" alt="19 - create data view" /></p>
<ul>
<li>You can now explore the documents in Discover using the data view:</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0626e95bf462405f/6a7f1b4142a11770e795c32b/elastic-blog-20.png" alt="20 - data view" /></p>
<h3 id="reindexyourinternalrunbooksintotheaiassistantsknowledgebaseindexusingitssemanticsearchpipeline">Reindex your internal runbooks into the AI Assistant’s Knowledge Base Index, using it's semantic search pipeline</h3>
<p>Your Knowledge Base documents are stored in the indices <em>.kibana-observability-ai-assistant-kb-*</em>. To add your internal runbooks imported from GitHub to the KB, you just need to reindex the documents from the index you created in the previous step to the KB’s index. To add the semantic search capabilities to the documents in the KB, the reindex should also use the ELSER pipeline preconfigured for the KB, <em>.kibana-observability-ai-assistant-kb-ingest-pipeline</em>.</p>
<p>By creating a Data View with the KB index, you can explore the content in Discover.</p>
<p>You execute the query below in <strong>Management &gt; Dev Tools</strong> , making sure to replace the following, both on “_source” and “inline”:</p>
<ul>
<li>InternalDocsIndex : name of the index where your internal docs are stored</li>
<li>text_field : name of the field with the text of your internal docs</li>
<li>timestamp : name of the field of the timestamp in your internal docs</li>
<li>public : (true or false) if true, makes a document available to all users in the defined <a href="https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html">Kibana Space</a> (if is defined) or in all spaces (if is not defined); if false, document will be restricted to the user indicated in</li>
<li>(optional) space : if defined, restricts the internal document to be available in a specific <a href="https://www.elastic.co/guide/en/kibana/current/xpack-spaces.html">Kibana Space</a></li>
<li>(optional) user.name : if defined, restricts the internal document to be available for a specific user</li>
<li>(optional) "query" filter to index only certain docs (see below)</li>
</ul>
<pre><code>POST _reindex
{
    "source": {
        "index": "&lt;InternalDocsIndex&gt;",
        "_source": [
            "&lt;text_field&gt;",
            "&lt;timestamp&gt;",
            "namespace",
            "is_correction",
            "public",
            "confidence"
        ]
    },
    "dest": {
        "index": ".kibana-observability-ai-assistant-kb-000001",
        "pipeline": ".kibana-observability-ai-assistant-kb-ingest-pipeline"
    },
    "script": {
        "inline": "ctx._source.text=ctx._source.remove(\"&lt;text_field&gt;\");ctx._source.namespace=\"&lt;space&gt;\";ctx._source.is_correction=false;ctx._source.public=&lt;public&gt;;ctx._source.confidence=\"high\";ctx._source['@timestamp']=ctx._source.remove(\"&lt;timestamp&gt;\");ctx._source['user.name'] = \"&lt;user.name&gt;\""
    }
}
</code></pre>
<p>You may want to specify the type of documents that you reindex in the KB — for example, you may only want to reindex Markdown documents (like Runbooks). You can add a “query” filter to the documents in the source. In the case of GitHub, runbooks are identified with the “type” field containing the string “file,” and you could add that to the reindex query like indicated below. To add also GitHub Issues, you can also include in the query “type” field containing the string “issues”:</p>
<pre><code>"source": {
        "index": "&lt;InternalDocsIndex&gt;",
        "_source": [
            "&lt;text_field&gt;",
            "&lt;timestamp&gt;",
            "namespace",
            "is_correction",
            "public",
            "confidence"
        ],
    "query": {
      "terms": {
        "type": ["file"]
      }
    }
</code></pre>
<p>Great! Now that the data is stored in your Knowledge Base, you can ask the Observability AI Assistant any questions about it:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta473e0043f5dbf04/6a7f1b442f00b25aabefef31/elastic-blog-21.png" alt="21 - new conversation" /></p>
<div>
    
</div>
<div>
    
</div>
<h2 id="conclusion">Conclusion</h2>
<p>In conclusion, leveraging internal Observability knowledge and adding it to the Elastic Knowledge Base can greatly enhance the capabilities of the AI Assistant. By manually inputting information or programmatically ingesting documents, SREs can create a central repository of knowledge accessible through the power of Elastic and LLMs. The AI Assistant can recall this information, assist with incidents, and provide tailored observability to specific contexts using Retrieval Augmented Generation. By following the steps outlined in this article, organizations can unlock the full potential of their Elastic AI Assistant.</p>
<p><a href="https://www.elastic.co/generative-ai/ai-assistant">Start enriching your Knowledge Base with the Elastic AI Assistant today</a> and empower your SRE team with the tools they need to excel. Follow the steps outlined in this article and take your incident management and alert remediation processes to the next level. Your journey toward a more efficient and effective SRE operation begins now.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>
<p><em>In this blog post, we may have used or referred to third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch, ESRE, Elasticsearch Relevance Engine and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/sre-troubleshooting-ai-assistant-observability-runbooks</link>
    <guid isPermaLink="false">sre-troubleshooting-ai-assistant-observability-runbooks</guid>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[AI]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Almudena Sanz Olivé,Katrin Freihofner,Tom Grabowski]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d0f6fc2d38fa05b/6a7f1b47bd21987d717584c9/11-hand.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 08 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Optimizing Observability with ES|QL: Streamlining SRE operations and issue resolution for Kubernetes and OTel]]></title>
    <description><![CDATA[ES|QL enhances operational efficiency, data analysis, and issue resolution for SREs. This blog covers the advantages of ES|QL in Elastic Observability and how it can apply to managing issues instrumented with OpenTelemetry and running on Kubernetes.]]></description>
    <content:encoded><![CDATA[<p>As an operations engineer (SRE, IT Operations, DevOps), managing technology and data sprawl is an ongoing challenge. Simply managing the large volumes of high dimensionality and high cardinality data is overwhelming.</p>
<p>As a single platform, Elastic® helps SREs unify and correlate limitless telemetry data, including metrics, logs, traces, and profiling, into a single datastore — Elasticsearch®. By then applying the power of Elastic’s advanced machine learning (ML), AIOps, AI Assistant, and analytics, you can break down silos and turn data into insights. As a full-stack observability solution, everything from infrastructure monitoring to log monitoring and application performance monitoring (APM) can be found in a single, unified experience.</p>
<p>In Elastic 8.11, a technical preview is now available of <a href="https://www.elastic.co/blog/esql-elasticsearch-piped-query-language">Elastic’s new piped query language, ES|QL (Elasticsearch Query Language)</a>, which transforms, enriches, and simplifies data investigations. Powered by a new query engine, ES|QL delivers advanced search capabilities with concurrent processing, improving speed and efficiency, irrespective of data source and structure. Accelerate resolution by creating aggregations and visualizations from one screen, delivering an iterative, uninterrupted workflow.</p>
<h2 id="advantagesofesqlforsres">Advantages of ES|QL for SREs</h2>
<p>SREs using Elastic Observability can leverage ES|QL to analyze logs, metrics, traces, and profiling data, enabling them to pinpoint performance bottlenecks and system issues with a single query. SREs gain the following advantages when managing high dimensionality and high cardinality data with ES|QL in Elastic Observability:</p>
<ul>
<li><strong>Improved operational efficiency:</strong> By using ES|QL, SREs can create more actionable notifications with aggregated values as thresholds from a single query, which can also be managed through the Elastic API and integrated into DevOps processes.</li>
<li><strong>Enhanced analysis with insights:</strong> ES|QL can process diverse observability data, including application, infrastructure, business data, and more, regardless of the source and structure. ES|QL can easily enrich the data with additional fields and context, allowing the creation of visualizations for dashboards or issue analysis with a single query.</li>
<li><strong>Reduced mean time to resolution:</strong> ES|QL, when combined with Elastic Observability's AIOps and AI Assistant, enhances detection accuracy by identifying trends, isolating incidents, and reducing false positives. This improvement in context facilitates troubleshooting and the quick pinpointing and resolution of issues.</li>
</ul>
<p>ES|QL in Elastic Observability not only enhances an SRE's ability to manage the customer experience, an organization's revenue, and SLOs more effectively but also facilitates collaboration with developers and DevOps by providing contextualized aggregated data.</p>
<p>In this blog, we will cover some of the key use cases SREs can leverage with ES|QL:</p>
<ul>
<li>ES|QL integrated with the Elastic AI Assistant, which uses public LLM and private data, enhances the analysis experience anywhere in Elastic Observability.</li>
<li>SREs can, in a single ES|QL query, break down, analyze, and visualize observability data from multiple sources and across any time frame.</li>
<li>Actionable alerts can be easily created from a single ES|QL query, enhancing operations.</li>
</ul>
<p>I will work through these use cases by showcasing how an SRE can solve a problem in an application instrumented with OpenTelemetry and running on Kubernetes. The OpenTelemetry (OTel) demo is on an Amazon EKS cluster, with Elastic Cloud 8.11 configured.</p>
<p>You can also check out our <a href="https://www.youtube.com/watch?v=vm0pBWI2l9c">Elastic Observability ES|QL Demo</a>, which walks through ES|QL functionality for Observability.</p>
<h2 id="esqlwithaiassistant">ES|QL with AI Assistant</h2>
<p>As an SRE, you are monitoring your OTel instrumented application with Elastic Observability, and while in Elastic APM, you notice some issues highlighted in the service map.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt67931374daecc7f2/6a85cdd8eaf2450312a49fab/elastic-blog-1-services.png" alt="1 - services" /></p>
<p>Using Elastic AI Assistant, you can easily ask for analysis, and in particular, we check on what the overall latency is across the application services.</p>
<pre><code>My APM data is in traces-apm*. What's the average latency per service over the last hour? Use ESQL, the data is mapped to ECS
</code></pre>
<div>
    
</div>
<p>The Elastic AI Assistant generates an ES|QL query, which we run in the AI Assistant to get a list of the average latencies across all the application services. We can easily see the top four are:</p>
<ul>
<li>load generator</li>
<li>front-end proxy</li>
<li>frontendservice</li>
<li>checkoutservice</li>
</ul>
<p>With a simple natural language query in the AI Assistant, it generated a single ES|QL query that helped list out the latencies across the services.</p>
<p>Noticing that there is an issue with several services, we decide to start with the frontend proxy. As we work through the details, we see significant failures, and through <strong>Elastic APM failure correlation</strong> , it becomes apparent that the frontend proxy is not properly completing its calls to downstream services.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt497215d42651cc18/6a85cddbd7b2e75e41fe853e/elastic-blog-2-failed-transaction.png" alt="2 - failed transaction" /></p>
<h2 id="esqlinsightfulandcontextualanalysisindiscover">ES|QL insightful and contextual analysis in Discover</h2>
<p>Knowing that the application is running on Kubernetes, we investigate if there are issues in Kubernetes. In particular, we want to see if there are any services having issues.</p>
<p>We use the following query in ES|QL in Elastic Discover:</p>
<pre><code>from metrics-* | where kubernetes.container.status.last_terminated_reason != "" and kubernetes.namespace == "default" | stats reason_count=count(kubernetes.container.status.last_terminated_reason) by kubernetes.container.name, kubernetes.container.status.last_terminated_reason | where reason_count &gt; 0
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt837d9acfc045bf02/6a85cddeeaf245c0cea49faf/elastic-blog-3-two-horizontal-bar-graphs.png" alt="3 - horizontal graph" /></p>
<p>ES|QL helps analyze 1,000s/10,000s of metric events from Kubernetes and highlights two services that are restarting due to OOMKilled.</p>
<p>The Elastic AI Assistant, when asked about OOMKilled, indicates that a container in a pod was killed due to an out-of-memory condition.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4e0b7f31ffb0e6f7/6a85cde1501a854b28fbb38d/elastic-blog-4-understanding-oomkilled.png" alt="4 - understanding oomkilled" /></p>
<p>We run another ES|QL query to understand the memory usage for emailservice and productcatalogservice.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf8feafaa0a4b2fa6/6a85cde4d7b2e78477fe8542/elastic-blog-5-split-bar-graphs.png" alt="5 - split bar graphs" /></p>
<p>ES|QL easily found the average memory usage fairly high.</p>
<p>We can now further investigate both of these services’ logs, metrics, and Kubernetes-related data. However, before we continue, we create an alert to track heavy memory usage.</p>
<h2 id="actionablealertswithesql">Actionable alerts with ES|QL</h2>
<p>Suspecting a specific issue, that might recur, we simply create an alert that brings in the ES|QL query we just ran that will track for any service that exceeds 50% in memory utilization.</p>
<p>We modify the last query to find any service with high memory usage:</p>
<pre><code>FROM metrics*
| WHERE @timestamp &gt;= NOW() - 1 hours
| STATS avg_memory_usage = AVG(kubernetes.pod.memory.usage.limit.pct) BY kubernetes.deployment.name | where avg_memory_usage &gt; .5
</code></pre>
<p>With that query, we create a simple alert. Notice how the ES|QL query is brought into the alert. We simply connect this to pager duty. But we can choose from multiple connectors like ServiceNow, Opsgenie, email, etc.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt16f1d7ecefc9df0f/6a85cde627c5cd53fd5f7450/elastic-blog-6-create-rule.png" alt="6 - create rule" /></p>
<p>With this alert, we can now easily monitor for any services that exceed 50% memory utilization in their pods.</p>
<h2 id="makethemostofyourdatawithesql">Make the most of your data with ES|QL</h2>
<p>In this post, we demonstrated the power ES|QL brings to analysis, operations, and reducing MTTR. In summary, the three use cases with ES|QL in Elastic Observability are as follows:</p>
<ul>
<li>ES|QL integrated with the Elastic AI Assistant, which uses public LLM and private data, enhances the analysis experience anywhere in Elastic Observability.</li>
<li>SREs can, in a single ES|QL query, break down, analyze, and visualize observability data from multiple sources and across any time frame.</li>
<li>Actionable alerts can be easily created from a single ES|QL query, enhancing operations.</li>
</ul>
<p>Elastic invites SREs and developers to experience this transformative language firsthand and unlock new horizons in their data tasks. Try it today at <a href="https://ela.st/free-trial">https://ela.st/free-trial</a> now in technical preview.</p>
<blockquote>
  <ul>
  <li><a href="https://www.elastic.co/demo-gallery/observability">Elastic Observability Tour</a></li>
  <li><a href="https://www.elastic.co/blog/log-management-observability-operations">The power of effective log management</a></li>
  <li><a href="https://www.elastic.co/blog/context-aware-insights-elastic-ai-assistant-observability">Transforming Observability with the AI Assistant</a></li>
  <li><a href="https://www.elastic.co/blog/esql-elasticsearch-piped-query-language">ES|QL announcement blog</a></li>
  </ul>
</blockquote>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-kubernetes-esql</link>
    <guid isPermaLink="false">opentelemetry-kubernetes-esql</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Logs Analytics]]></category>
    <category><![CDATA[Agentic Observability]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd21cab120ad20933/6a85cde980984ce0f666902e/ES_QL_blog-720x420-05.png" length="0" type="image/png"/>
    <pubDate>Wed, 01 Nov 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Ingesting and analyzing Prometheus metrics with Elastic Observability]]></title>
    <description><![CDATA[In this blog post, we will showcase the integration of Prometheus with Elastic, emphasizing how Elastic elevates metrics monitoring through extensive historical analytics, anomaly detection, and forecasting, all in a cost-effective manner.]]></description>
    <content:encoded><![CDATA[<p>In the world of monitoring and observability, <a href="https://prometheus.io/">Prometheus</a> has grown into the de-facto standard for monitoring in cloud-native environments because of its robust data collection mechanism, flexible querying capabilities, and integration with other tools for rich dashboarding and visualization.</p>
<p>Prometheus is primarily built for short-term metric storage, typically retaining data in-memory or on local disk storage, with a focus on real-time monitoring and alerting rather than historical analysis. While it offers valuable insights into current metric values and trends, it may pose economic challenges and fall short of the robust functionalities and capabilities necessary for in-depth historical analysis, long-term trend detection, and forecasting. This is particularly evident in large environments with a substantial number of targets or high data ingestion rates, where metric data accumulates rapidly.</p>
<p>Numerous organizations assess their unique needs and explore avenues to augment their Prometheus monitoring and observability capabilities. One effective approach is integrating Prometheus with Elastic®. In this blog post, we will showcase the integration of Prometheus with Elastic, emphasizing how Elastic elevates metrics monitoring through extensive historical analytics, anomaly detection, and forecasting, all in a cost-effective manner.</p>
<h2 id="integrateprometheuswithelasticseamlessly">Integrate Prometheus with Elastic seamlessly</h2>
<p>Organizations that have configured their cloud-native applications to expose metrics in Prometheus format can seamlessly transmit the metrics to Elastic by using <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-prometheus.html">Prometheus integration</a>. Elastic enables organizations to monitor their metrics in conjunction with all other data gathered through <a href="https://www.elastic.co/integrations/data-integrations">Elastic's extensive integrations</a>.</p>
<p>Go to Integrations and find the Prometheus integration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc9e5ad459d059c39/6a85cbf65c27902126f59b2d/elastic-blog-1-integrations.png" alt="1 - integrations" /></p>
<p>To gather metrics from Prometheus servers, the Elastic Agent is employed, with central management of Elastic agents handled through the <a href="https://www.elastic.co/guide/en/fleet/current/fleet-overview.html">Fleet server</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt626c6b844fca225a/6a85cbf9d7b2e70961fe84fc/elastic-blog-2-set-up-prometheus-integration.png" alt="2 - set up integration" /></p>
<p>After enrolling the Elastic Agent in the Fleet, users can choose from the following methods to ingest Prometheus metrics into Elastic.</p>
<h3 id="1prometheuscollectors">1. Prometheus collectors</h3>
<p><a href="https://docs.elastic.co/integrations/prometheus#prometheus-exporters-collectors">The Prometheus collectors</a> connect to the Prometheus server and pull metrics or scrape metrics from a Prometheus exporter.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6a29e0fd3ca28eb5/6a85cbfc1aa1e15ce6ff8d75/elastic-blog-3-prometheus-collectors.png" alt="3 - Prometheus collectors" /></p>
<h3 id="2prometheusqueries">2. Prometheus queries</h3>
<p><a href="https://docs.elastic.co/integrations/prometheus#prometheus-queries-promql">The Prometheus queries</a> execute specific Prometheus queries against <a href="https://prometheus.io/docs/prometheus/latest/querying/api/#expression-queries">Prometheus Query API</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blted8c85a2078f2702/6a85cbff501a8539cffbb353/elastic-blog-4-promtheus-queries.png" alt="4 - Prometheus queries" /></p>
<h3 id="3prometheusremotewrite">3. Prometheus remote-write</h3>
<p><a href="https://docs.elastic.co/integrations/prometheus#prometheus-server-remote-write">The Prometheus remote_write</a> can receive metrics from a Prometheus server that has configured the <a href="https://prometheus.io/docs/prometheus/latest/configuration/configuration/#remote_write">remote_write</a> setting.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7779a7711ae31fe5/6a85cc0193ffb91d45b9144d/elastic-blog-5-prometheus-remote-write.png" alt="5 - Prometheus remote-write" /></p>
<p>After your Prometheus metrics are ingested, you have the option to visualize your data graphically within the <a href="https://www.elastic.co/guide/en/observability/current/explore-metrics.html">Metrics Explorer</a> and further segment it based on labels, such as hosts, containers, and more.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5fa7bfd9b25d308b/6a85cc048c29445e83b8905b/elastic-blog-10-metrics-explorer.png" alt="10 - metrics explorer" /></p>
<p>You can also query your metrics data in <a href="https://www.elastic.co/guide/en/kibana/current/discover.html">Discover</a> and explore the fields of your individual documents within the details panel.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7be9f6c9584da7ad/6a85cc0793ffb927aeb91451/elastic-blog-7-expanded-doc.png" alt="7 - expanded document" /></p>
<h2 id="storinghistoricalmetricswithelasticsdatatieringmechanism">Storing historical metrics with Elastic’s data tiering mechanism</h2>
<p>By exporting Prometheus metrics to Elasticsearch, organizations can extend the retention period and gain the ability to analyze metrics historically. Elastic optimizes data storage and access based on the frequency of data usage and the performance requirements of different data sets. The goal is to efficiently manage and store data, ensuring that it remains accessible when needed while keeping storage costs in check.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt860919556d76beee/6a85cc0a18249c19f218f7d9/elastic-blog-8-hot-to-frozen.png" alt="8 - hot to frozen flow chart" /></p>
<p>After ingesting Prometheus metrics data, you have various retention options. You can set the duration for data to reside in the hot tier, which utilizes high IO hardware (SSD) and is more expensive. Alternatively, you can move the Prometheus metrics to the warm tier, employing cost-effective hardware like spinning disks (HDD) while maintaining consistent and efficient search performance. The cold tier mirrors the infrastructure of the warm tier for primary data but utilizes S3 for replica storage. Elastic automatically recovers replica indices from S3 in case of node or disk failure, ensuring search performance comparable to the warm tier while reducing disk cost.</p>
<p>The <a href="https://www.elastic.co/blog/introducing-elasticsearch-frozen-tier-searchbox-on-s3">frozen tier</a> allows direct searching of data stored in S3 or an object store, without the need for rehydration. The purpose is to further reduce storage costs for Prometheus metrics data that is less frequently accessed. By moving historical data into the frozen tier, organizations can optimize their storage infrastructure, ensuring that the recent, critical data remains in higher-performance tiers while less frequently accessed data is stored economically in the frozen tier. This way, organizations can perform historical analysis and trend detection, identify patterns and make informed decisions, and maintain compliance with regulatory standards in a cost-effective manner.</p>
<p>An alternative way to store your cloud-native metrics more efficiently is to use <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/tsds.html">Elastic Time Series Data Stream</a> (TSDS). TSDS can store your metrics data more efficiently with <a href="https://www.elastic.co/blog/70-percent-storage-savings-for-metrics-with-elastic-observability">~70% less disk space</a> than a regular data stream. The <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/downsampling.html">downsampling</a> functionality will further reduce the storage required by rolling up metrics within a fixed time interval into a single summary metric. This not only assists organizations in cutting down on storage expenses for metric data but also simplifies the metric infrastructure, making it easier for users to correlate metrics with logs and traces through a unified interface.</p>
<h2 id="advancedanalytics">Advanced analytics</h2>
<p>Besides <a href="https://www.elastic.co/guide/en/observability/current/explore-metrics.html">Metrics Explorer</a> and <a href="https://www.elastic.co/guide/en/kibana/current/discover.html">Discover</a>, Elasticsearch® provides more advanced analytics capabilities and empowers organizations to gain deeper, more valuable insights into their Prometheus metrics data.</p>
<p>Out of the box, Prometheus integration provides a default overview dashboard.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0cfc19a5570335b5/6a85cc0e8c29444c73b8905f/elastic-blog-9-advacned-analytics.png" alt="9 - adv analytics" /></p>
<p>From Metrics Explorer or Discover, users can also easily edit their Prometheus metrics visualization in <a href="https://www.elastic.co/kibana/kibana-lens">Elastic Lens</a> or create new visualizations from Lens.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte37f0d3ea041f9d0/6a85cc11bc5bb35bccf81b19/elastic-blog-6-metrics-explorer.png" alt="6 - metrics explorer" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda4b52e06c3c9635/6a85cc13342d69aa2521b103/elastic-blog-11-green-bars.png" alt="11 - green bars" /></p>
<p>Elastic Lens enables users to explore and visualize data intuitively through dynamic visualizations. This user-friendly interface eliminates the need for complex query languages, making data analysis accessible to a broader audience. Elasticsearch also offers other powerful visualization methods with <a href="https://www.elastic.co/guide/en/kibana/current/add-aggregation-based-visualization-panels.html">aggregations</a> and <a href="https://www.youtube.com/watch?v=I8NtctS33F0">filters</a>, enabling users to perform advanced analytics on their Prometheus metrics data, including short-term and historical data. To learn more, check out the <a href="https://www.elastic.co/videos/training-how-to-series-stack">how-to series: Kibana</a>.</p>
<h2 id="anomalydetectionandforecasting">Anomaly detection and forecasting</h2>
<p>When analyzing data, maintaining a constant watch on the screen is simply not feasible, especially when dealing with millions of time series of Prometheus metrics. Engineers frequently encounter the challenge of differentiating normal from abnormal data points, which involves analyzing historical data patterns — a process that can be exceedingly time consuming and often exceeds human capabilities. Thus, there is a pressing need for a more intelligent approach to detect anomalies efficiently.</p>
<p>Setting up alerts may seem like an obvious solution, but relying solely on rule-based alerts with static thresholds can be problematic. What's normal on a Wednesday at 9:00 a.m. might be entirely different from a Sunday at 2:00 a.m. This often leads to complex and hard-to-maintain rules or wide alert ranges that end up missing crucial issues. Moreover, as your business, infrastructure, users, and products evolve, these fixed rules don't keep up, resulting in lots of false positives or, even worse, important issues slipping through the cracks without detection. A more intelligent and adaptable approach is needed to ensure accurate and timely anomaly detection.</p>
<p>Elastic's machine learning anomaly detection excels in such scenarios. It automatically models the normal behavior of your Prometheus data, learning trends, and identifying anomalies, thereby reducing false positives and improving mean time to resolution (MTTR). With over 13 years of development experience in this field, Elastic has emerged as a trusted industry leader.</p>
<p>The key advantage of Elastic's machine learning anomaly detection lies in its unsupervised learning approach. By continuously observing real-time data, it acquires an understanding of the data's behavior over time. This includes grasping daily and weekly patterns, enabling it to establish a normalcy range of expected behavior. Behind the scenes, it constructs statistical models that allow accurate predictions, promptly identifying any unexpected variations. In cases where emerging data exhibits unusual trends, you can seamlessly integrate with alerting systems, operationalizing this valuable insight.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt602508375e682805/6a85cc165c2790e8d0f59b31/elastic-blog-12-LPO.png" alt="12 - LPO" /></p>
<p>Machine learning's ability to project into the future, forecasting data trends one day, a week, or even a month ahead, equips engineers not only with reporting capabilities but also with pattern recognition and failure prediction based on historical Prometheus data. This plays a crucial role in maintaining mission-critical workloads, offering organizations a proactive monitoring approach. By foreseeing and addressing issues before they escalate, organizations can avert downtime, cut costs, optimize resource utilization, and ensure uninterrupted availability of their vital applications and services.</p>
<p><a href="https://www.elastic.co/guide/en/machine-learning/current/ml-ad-run-jobs.html#ml-ad-create-job">Creating a machine learning job</a> for your Prometheus data is a straightforward task with a few simple steps. Simply specify the data index and set the desired time range in the single metric view. The machine learning job will then automatically process the historical data, building statistical models behind the scenes. These models will enable the system to predict trends and identify anomalies effectively, providing valuable and actionable insights for your monitoring needs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt300bc2ef939dbef1/6a85cc19078290c269321790/elastic-blog-13-creating-ML-job.png" alt="13 - create ML job" /></p>
<p>In essence, Elastic machine learning empowers us to harness the capabilities of data scientists and effectively apply them in monitoring Prometheus metrics. By seamlessly detecting anomalies and predicting potential issues in advance, Elastic machine learning bridges the gap and enables IT professionals to benefit from the insights derived from advanced data analysis. This practical and accessible approach to anomaly detection equips organizations with a proactive stance toward maintaining the reliability of their systems.</p>
<h2 id="tryitout">Try it out</h2>
<p><a href="https://www.elastic.co/cloud/cloud-trial-overview">Start a free trial</a> on Elastic Cloud and <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/metricbeat-module-prometheus.html">ingest your Prometheus metrics into Elastic</a>. Enhance your Prometheus monitoring with Elastic Observability. Stay ahead of potential issues with advanced AI/ML anomaly detection and prediction capabilities. Eliminate data silos, reduce costs, and enhance overall response efficiency.</p>
<p>Elevate your monitoring capabilities with Elastic today!</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/ingesting-analyzing-prometheus-metrics-observability</link>
    <guid isPermaLink="false">ingesting-analyzing-prometheus-metrics-observability</guid>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Jenny Morris]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt859b45f19c08a511/6a85cc1c331d7a3112c317b7/illustration-machine-learning-anomaly-v2.png" length="0" type="image/png"/>
    <pubDate>Mon, 09 Oct 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Native OpenTelemetry support in Elastic Observability]]></title>
    <description><![CDATA[Elastic offers native support for OpenTelemetry by allowing for direct ingest of OpenTelemetry traces, metrics, and logs without conversion, and applying any Elastic feature against OTel data without degradation in capabilities.]]></description>
    <content:encoded><![CDATA[<p>NOTE: Since writing this blog, new OTel data ingest configurations are now available in Elastic. See recent <a href="https://www.elastic.co/observability-labs/blog/elastic-opentelemetry-otel-operator">blog</a></p>
<p>OpenTelemetry is more than just becoming the open ingestion standard for observability. As one of the major Cloud Native Computing Foundation (CNCF) projects, with as many commits as Kubernetes, it is gaining support from major ISVs and cloud providers delivering support for the framework. Many global companies from finance, insurance, tech, and other industries are starting to standardize on OpenTelemetry. With OpenTelemetry, DevOps teams have a consistent approach to collecting and ingesting telemetry data providing a de-facto standard for observability.</p>
<p>Elastic<sup>®</sup> is strategically standardizing on OpenTelemetry for the main data collection architecture for observability and security. Additionally, Elastic is making a commitment to help OpenTelemetry become the best de facto data collection infrastructure for the observability ecosystem. Elastic is deepening its relationship with OpenTelemetry beyond the recent contribution of Elastic Common Schema (ECS) to OpenTelemetry (OTel).</p>
<p>Today, Elastic supports OpenTelemetry natively, since Elastic 7.14, by being able to directly ingest OpenTelemetry protocol (OTLP) based traces, metrics, and logs.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt68f6108956523f81/6a7f0e5ffc63ab7fae64cd0f/elastic-blog-1-otel-config-options.png" alt="otel configuration options" /></p>
<p>In this blog, we’ll review the current OpenTelemetry support provided by Elastic, which includes the following:</p>
<ul>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#ingesting-opentelemetry-into-elastic"><strong>Easy ingest of distributed tracing and metrics</strong></a> for applications configured with OpenTelemetry agents for Python, NodeJS, Java, Go, and .NET</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#opentelemetry-logs-in-elastic"><strong>OpenTelemetry logs instrumentation and ingest</strong></a> using various configurations</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#opentelemetry-is-elastics-preferred-schema"><strong>Open semantic conventions</strong></a> for logs and more through ECS, which is not part of OpenTelemetry</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#elastic-observability-apm-and-machine-learning-capabilities"><strong>Machine learning based AIOps capabilities</strong></a>, such as latency correlations, failure correlations, anomaly detection, log spike analysis, predictive pattern analysis, Elastic AI Assistant support, and more, all apply to native OTLP telemetry.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#elastic-allows-you-to-migrate-to-otel-on-your-schedule"><strong>Migrate applications to OpenTelemetry at your own speed</strong></a>. Elastic’s APM capabilities all work seamlessly even with a mix of services using OpenTelemetry and/or Elastic APM agents. You can even combine OpenTelemetry instrumentation with Elastic Agent.</li>
<li><a href="https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability#integrated-kubernetes-and-opentelemetry-views-in-elastic"><strong>Integrated views and analysis with Kubernetes clusters</strong></a>, which most OpenTelemetry applications are running on. Elastic can highlight specific pods and containers related to each service when analyzing issues for applications based on OpenTelemetry.</li>
</ul>
<h2 id="ingestingopentelemetryintoelastic">Ingesting OpenTelemetry into Elastic</h2>
<p>If you’re interested in seeing how simple it is to ingest OpenTelemetry traces and metrics into Elastic, follow the steps outlined in this blog.</p>
<p>Let’s outline what Elastic provides for ingesting OpenTelemetry data. Here are all your options:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta805e805b620c7c6/6a7f0e61c2cc0960a8249626/elastic-blog-2-flowchart.png" alt="flowchart" /></p>
<h3 id="usingtheopentelemetrycollector">Using the OpenTelemetry Collector</h3>
<p>When using the OpenTelemetry Collector, which is the most common configuration option, you simply have to add two key variables.</p>
<p>The instructions utilize a specific opentelemetry-collector configuration for Elastic. Essentially, the Elastic <a href="https://github.com/elastic/opentelemetry-demo/blob/main/kubernetes/elastic-helm/values.yaml">values.yaml</a> file specified in the elastic/opentelemetry-demo configure the opentelemetry-collector to point to the Elastic APM Server using two main values:</p>
<p>OTEL_EXPORTER_OTLP_ENDPOINT is Elastic’s APM Server<br />
OTEL_EXPORTER_OTLP_HEADERS Elastic Authorization</p>
<p>These two values can be found in the OpenTelemetry setup instructions under the APM integration instructions (Integrations-&gt;APM) in your Elastic Cloud.</p>
<h3 id="nativeopentelemetryagentsembeddedincode">Native OpenTelemetry agents embedded in code</h3>
<p>If you are thinking of using OpenTelemetry libraries in your code, you can simply point the service to Elastic’s APM server, because it supports native OLTP protocol. No special Elastic conversion is needed.</p>
<p>To demonstrate this effectively and provide some education on how to use OpenTelemetry, we have two applications you can use to learn from:</p>
<ul>
<li><a href="https://github.com/elastic/opentelemetry-demo">Elastic’s version of OpenTelemetry demo</a>: As with all the other observability vendors, we have our own forked version of the OpenTelemetry demo.</li>
<li><a href="https://github.com/elastic/workshops-instruqt/tree/main/Elastiflix">Elastiflix:</a> This demo application is an example to help you learn how to instrument on various languages and telemetry signals.</li>
</ul>
<p>Check out our blogs on using the Elastiflix application and instrumenting with OpenTelemetry:</p>
<ul>
<li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
<li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
<li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
<li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
<li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
</ul>
<p>We have created YouTube videos on these topics as well:</p>
<ul>
<li><a href="https://youtu.be/wMXMRsjFg-8?feature=shared">How to Manually Instrument Java with OpenTelemetry (Part 1)</a></li>
<li><a href="https://youtu.be/PX7s6RRLGaU?feature=shared">How to Manually Instrument Java with OpenTelemetry (Part 2)</a></li>
<li><a href="https://youtu.be/hXTlV_RnELc?feature=shared">Custom Java Instrumentation with OpenTelemetry</a></li>
<li><a href="https://youtu.be/E8g9u_uOFO4?feature=shared">Elastic APM - Automatic .NET Instrumentation with OpenTelemetry</a></li>
<li><a href="https://youtu.be/7J9M2JsHwRE?feature=shared">How to Manually Instrument .NET Applications with OpenTelemetry</a></li>
</ul>
<p>Given Elastic and OpenTelemetry’s vast user base, these provide a rich source of education for anyone trying to learn the intricacies of instrumenting with OpenTelemetry.</p>
<h3 id="elasticagentssupportingopentelemetry">Elastic Agents supporting OpenTelemetry</h3>
<p>If you’ve already implemented OpenTelemetry, you can still use them with OpenTelemetry. <a href="https://www.elastic.co/blog/opentelemetry-instrumentation-elastic-apm-agent-features">Elastic APM agents today are able to ship OpenTelemetry</a> spans as part of a trace. This means that if you have any component in your application that emits an OpenTelemetry span, it’ll be part of the trace the Elastic APM agent captures.</p>
<h2 id="opentelemetrylogsinelastic">OpenTelemetry logs in Elastic</h2>
<p>If you look at OpenTelemetry documentation, you will see that a lot of language libraries are still in experimental or not implemented yet state. Java is in stable state, per the documentation. Depending on your service’s language, and your appetite for adventure, there exist several options for exporting logs from your services and applications and marrying them together in your observability backend.</p>
<p>In a previous blog, we discussed <a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 different configurations to properly get logging data into Elastic for Java</a>. The blog explores the current state of the art of OpenTelemetry logging and provides guidance on the available approaches with the following tenants in mind:</p>
<ul>
<li>Correlation of service logs with OTel-generated tracing where applicable</li>
<li>Proper capture of exceptions</li>
<li>Common context across tracing, metrics, and logging</li>
<li>Support for slf4j key-value pairs (“structured logging”)</li>
<li>Automatic attachment of metadata carried between services via OTel baggage</li>
<li>Use of an Elastic Observability backend</li>
<li>Consistent data fidelity in Elastic regardless of the approach taken</li>
</ul>
<p>Three models, which are covered in the blog, currently exist for getting your application or service logs to Elastic with correlation to OTel tracing and baggage:</p>
<ul>
<li>Output logs from your service (alongside traces and metrics) using an embedded OpenTelemetry Instrumentation library to Elastic via the OTLP protocol</li>
<li>Write logs from your service to a file scrapped by the OpenTelemetry Collector, which then forwards to Elastic via the OTLP protocol</li>
<li>Write logs from your service to a file scrapped by Elastic Agent (or Filebeat), which then forwards to Elastic via an Elastic-defined protocol</li>
</ul>
<p>Note that (1), in contrast to (2) and (3), does not involve writing service logs to a file prior to ingestion into Elastic.</p>
<h2 id="opentelemetryiselasticspreferredschema">OpenTelemetry is Elastic’s preferred schema</h2>
<p>Elastic recently contributed the <a href="https://opentelemetry.io/blog/2023/ecs-otel-semconv-convergence/">Elastic Common Schema (ECS) to the OpenTelemetry (OTel)</a> project, enabling a unified data specification for security and observability data within the OTel Semantic Conventions framework.</p>
<p>ECS, an open source specification, was developed with support from the Elastic user community to define a common set of fields to be used when storing event data in Elasticsearch<sup>®</sup>. ECS helps reduce management and storage costs stemming from data duplication, improving operational efficiency.</p>
<p>Similarly, OTel’s Semantic Conventions (SemConv) also specify common names for various kinds of operations and data. The benefit of using OTel SemConv is in following a common naming scheme that can be standardized across a codebase, libraries, and platforms for OTel users.</p>
<p>The merging of ECS and OTel SemConv will help advance OTel’s adoption and the continued evolution and convergence of observability and security domains.</p>
<h2 id="elasticobservabilityapmandmachinelearningcapabilities">Elastic Observability APM and machine learning capabilities</h2>
<p>All of Elastic Observability’s APM capabilities are available with OTel data (read more on this in our blog, <a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry</a>):</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services</li>
<li>Transactions (traces)</li>
<li>ML correlations (specifically for latency)</li>
<li>Service logs</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt64b1360c6f4f835b/6a7f0e652f00b28c7befebf4/elastic-blog-3-services.png" alt="services" /></p>
<p>In addition to Elastic’s APM and unified view of the telemetry data, you will now be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR. Here are some of the ML based AIOps capabilities we have:</p>
<ul>
<li><a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability"><strong>Anomaly detection:</strong></a> Elastic Observability, when turned on (<a href="https://www.elastic.co/guide/en/kibana/current/xpack-ml-anomalies.html">see documentation</a>), automatically detects anomalies by continuously modeling the normal behavior of your OpenTelemetry data — learning trends, periodicity, and more.</li>
<li><a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability"><strong>Log categorization:</strong></a> Elastic also identifies patterns in your OpenTelemetry log events quickly, so that you can take action quicker.</li>
<li><strong>High-latency or erroneous transactions:</strong> Elastic Observability’s APM capability helps you discover which attributes are contributing to increased transaction latency and identifies which attributes are most influential in distinguishing between transaction failures and successes.</li>
<li><a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops"><strong>Log spike detector</strong></a> helps identify reasons for increases in OpenTelemetry log rates. It makes it easy to find and investigate causes of unusual spikes by using the analysis workflow view.</li>
<li><a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops"><strong>Log pattern analysis</strong></a> helps you find patterns in unstructured log messages and makes it easier to examine your data.</li>
</ul>
<h2 id="elasticallowsyoutomigratetootelonyourschedule">Elastic allows you to migrate to OTel on your schedule</h2>
<p>Although OpenTelemetry supports many programming languages, the <a href="https://opentelemetry.io/docs/instrumentation/">status of its major functional components</a> — metrics, traces, and logs — are still at various stages. Thus migrating applications written in Java, Python, and JavaScript are good choices to start with as their metrics, traces, and logs (for Java) are stable.</p>
<p>For the other languages that are not yet supported, you can easily instrument those using Elastic Agents, therefore running your <a href="https://www.elastic.co/observability">full stack observability platform</a> in mixed mode (Elastic agents with OpenTelemetry agents).</p>
<p>Here is a simple example:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbff34e303f9e3330/6a7f0e67ea068d2474f09f1c/elastic-blog-4-services2.png" alt="services 2" /></p>
<p>The above shows a simple variation of our standard Elastic Agent application with one service flipped to OTel — the newsletter-otel service. But we can easily and as needed convert each of these services to OTel as development resources allow.</p>
<p>Hence you can migrate what you need to OpenTelemetry with Elastic as specific languages reach a stable state, and you can then continue your migration to OpenTelemetry agents.</p>
<h2 id="integratedkubernetesandopentelemetryviewsinelastic">Integrated Kubernetes and OpenTelemetry views in Elastic</h2>
<p>Elastic manages your Kubernetes cluster using the Elastic Agent, and you can use it on your Kubernetes cluster where your OpenTelemetry application is running. Hence you can not only use OpenTelemetry for your application, but Elastic can also monitor the corresponding Kubernetes cluster.</p>
<p>There are two configurations for Kubernetes:</p>
<p><strong>1. Simply deploying the Elastic Agent daemon set on the kubernetes cluster.</strong> We outline this out in the article entitled <a href="https://www.elastic.co/blog/kubernetes-cluster-metrics-logs-monitoring">Managing your Kubernetes cluster with Elastic Observability</a>. This would also push just the Kubernetes metrics and logs to Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0f91de133133262e/6a7f0e6a3ce8e2abc1cf540f/elastic-blog-5-cloud-nodes.png" alt="elastic cloud nodes" /></p>
<p><strong>2. Deploying the Elastic Agent with not only the Kubernetes Daemon set, but also Elastic’s APM integration, the Defend (Security) integration, and Network Packet capture integration</strong> to provide more comprehensive Kubernetes cluster observability. We outline this configuration in the following article <a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd34ef1f6e71a7446/6a7f0e6dea068d609ff09f20/elastic-blog-6-flowhcart.png" alt="flowchart" /></p>
<p>Both <a href="https://www.elastic.co/observability/opentelemetry">OpenTelemetry visualization</a> examples use the OpenTelemetry demo, and in Elastic, we tie the Kubernetes information with the application to provide you an ability to see Kubernetes information from your traces in APM. This provides a more integrated approach when troubleshooting.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0b4b8776d336437e/6a7f0e706c6eac80c7f141a9/elastic-blog-7-pod-deets.png" alt="pod details" /></p>
<h2 id="summary">Summary</h2>
<p>In essence, Elastic's commitment goes beyond mere support for OpenTelemetry. We are dedicated to ensuring our customers not only adopt OpenTelemetry but thrive with it. Through our solutions, expertise, and resources, we aim to elevate the observability journey for every business, turning data into actionable insights that drive growth and innovation.</p>
<blockquote>
  <p>Developer resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/getting-started-opentelemetry-instrumentation-sample-app">Elastiflix application</a>, a guide to instrument different languages with OpenTelemetry</li>
  <li>Python: <a href="https://www.elastic.co/blog/auto-instrumentation-of-python-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-python-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Java: <a href="https://www.elastic.co/blog/auto-instrumentation-of-java-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-java-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Node.js: <a href="https://www.elastic.co/blog/auto-instrument-nodejs-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-nodejs-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>.NET: <a href="https://www.elastic.co/blog/auto-instrumentation-of-net-applications-opentelemetry">Auto-instrumentation</a>, <a href="https://www.elastic.co/blog/manual-instrumentation-of-net-applications-opentelemetry">Manual-instrumentation</a></li>
  <li>Go: <a href="https://elastic.co/blog/manual-instrumentation-of-go-applications-opentelemetry">Manual-instrumentation</a></li>
  <li><a href="https://www.elastic.co/blog/best-practices-instrumenting-opentelemetry">Best practices for OpenTelemetry</a></li>
  </ul>
  <p>General configuration and use case resources:</p>
  <ul>
  <li><a href="https://www.elastic.co/blog/opentelemetry-observability">Independence with OpenTelemetry on Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/implementing-kubernetes-observability-security-opentelemetry">Modern observability and security on Kubernetes with Elastic and OpenTelemetry</a></li>
  <li><a href="https://www.elastic.co/blog/3-models-logging-opentelemetry-elastic">3 models for logging with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">Adding free and open Elastic APM as part of your Elastic Observability deployment</a></li>
  <li><a href="https://www.elastic.co/blog/custom-metrics-app-code-java-agent-plugin">Capturing custom metrics through OpenTelemetry API in code with Elastic</a></li>
  <li><a href="https://www.elastic.co/virtual-events/future-proof-your-observability-platform-with-opentelemetry-and-elastic">Future-proof your observability platform with OpenTelemetry and Elastic</a></li>
  <li><a href="https://www.elastic.co/blog/kubernetes-k8s-observability-elasticsearch-cncf">Elastic Observability: Built for open technologies like Kubernetes, OpenTelemetry, Prometheus, Istio, and more</a></li>
  </ul>
</blockquote>
<p>Don’t have an Elastic Cloud account yet? <a href="https://cloud.elastic.co/registration">Sign up for Elastic Cloud</a> and try out the instrumentation capabilities that I discussed above. I would be interested in getting your feedback about your experience in gaining visibility into your application stack with Elastic.</p>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/native-opentelemetry-support-in-elastic-observability</link>
    <guid isPermaLink="false">native-opentelemetry-support-in-elastic-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2700a8e353c3fb55/6a7f0e7342a117e08695bf4c/ecs-otel-announcement-2.jpg" length="0" type="image/jpeg"/>
    <pubDate>Wed, 13 Sep 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using the Elastic Agent to monitor Amazon ECS and AWS Fargate with Elastic Observability]]></title>
    <description><![CDATA[In this article, we’ll guide you through how to install the Elastic Agent with the AWS Fargate integration as a sidecar container to send host metrics and logs to Elastic Observability.]]></description>
    <content:encoded><![CDATA[<h2 id="serverlessandawsecsfargate">Serverless and AWS ECS Fargate</h2>
<p>AWS Fargate is a serverless pay-as-you-go engine used for Amazon Elastic Container Service (ECS) to run Docker containers without having to manage servers or clusters. The goal of Fargate is to containerize your application and specify the OS, CPU and memory, networking, and IAM policies needed for launch. Additionally, AWS Fargate can be used with Elastic Kubernetes Service (EKS) in a <a href="https://docs.aws.amazon.com/eks/latest/userguide/fargate.html">similar manner</a>.</p>
<p>Although the provisioning of servers would be handled by a third party, the need to understand the health and performance of containers within your serverless environment becomes even more vital in identifying root causes and system interruptions. Serverless still requires observability. Elastic Observability can provide observability for not only AWS ECS with Fargate, as we will discuss in this blog, but also for a number of AWS services (EC2, RDS, ELB, etc). See our <a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">previous blog</a> on managing an EC2-based application with Elastic Observability.</p>
<h2 id="gainingfullvisibilitywithelasticobservability">Gaining full visibility with Elastic Observability</h2>
<p>Elastic Observability is governed by the three pillars involved in creating full visibility within a system: logs, metrics, and traces. Logs list all the events that have taken place in the system. Metrics keep track of data that will tell you if the system is down, like response time, CPU usage, memory usage, and latency. Traces give a good indication of the performance of your system based on the execution of requests.</p>
<p>These pillars by themselves offer some insight, but combining them allows for you to see the full scope of your system and how it handles increases in load or traffic over time. Connecting Elastic Observability to your serverless environment will help you deal with outages quicker and perform root cause analysis to prevent any future problems.</p>
<p>In this article, we’ll guide you through how to install the Elastic Agent with the <a href="https://docs.elastic.co/integrations/awsfargate">AWS Fargate</a> integration as a sidecar container to send host metrics and logs to Elastic Observability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt803b7dadd7538890/6a85c8b42d64d57d43081cea/Screenshot_2023-06-16_at_12.58.05_PM.png" alt="" /></p>
<h2 id="prerequisites">Prerequisites:</h2>
<ul>
<li>AWS account with AWS CLI configured</li>
<li>GitHub account</li>
<li>Elastic Cloud account</li>
<li>An app running on a container in AWS</li>
</ul>
<p>This tutorial is divided into two parts:</p>
<ol>
<li>Set up the Fleet server to be used by the sidecar container in AWS.</li>
<li>Create the sidecar container in AWS Fargate to send data back to Elastic Observability.</li>
</ol>
<h2 id="partisetupthefleetserver">Part I: Set up the Fleet server</h2>
<p>First, let’s log in to Elastic Cloud.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8c64699a3a4d241e/6a85c8b74710c65ef1d3cb05/image4.png" alt="" /></p>
<p>You can either create a new deployment or use an existing one.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6c2c2758222a2477/6a85c8bad7b2e7717efe849c/image35.png" alt="" /></p>
<p>From the <strong>Home</strong> page, use the side panel to scroll to Management &gt; Fleet &gt; Agent policies. Click <strong>Add policy</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0fcb9b2f065f6980/6a85c8bd5c27905f1ef59acf/image30.png" alt="" /></p>
<p>Click <strong>Create agent policy</strong>. Here we’ll create a policy to attach to the Fleet agent.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf7486ca256e624a4/6a85c8c093ffb9c405b913eb/image38.png" alt="" /></p>
<p>Give the policy a name and save changes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8affe45d856644e2/6a85c8c30782905f6c32172e/image44.png" alt="" /></p>
<p>Click <strong>Create agent policy</strong>. You should see the agent policy AWS Fargate in the list of policies.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb90b92edc7f54ea0/6a85c8c69d2b71099cf93945/image42.png" alt="" /></p>
<p>Now that we have an agent policy, let’s add the integration to collect logs and metrics from the host. Click on <strong>AWS Fargate -&gt; Add integration</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt66bcb99f5f15a63f/6a85c8c8abdc29673b1224ac/image19.png" alt="" /></p>
<p>We’ll be adding to the policy AWS to collect overall AWS metrics and AWS Fargate to collect metrics from this integration. You can find each one by typing them in the search bar.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd0b1c57451639be3/6a85c8cb11893c866da7ab3a/image1.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta30ddfb73a520417/6a85c8ce2d64d5e12a081cf2/image34.png" alt="" /></p>
<p>Once you click on the integration, it will take you to its landing page, where you can add it to the policy.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt29db850a592cc5da/6a85c8d1d6cf290f0ebb08bc/image48.png" alt="" /></p>
<p>For the AWS integration, the only collection settings that we will configure are Collect billing metrics, Collect logs from CloudWatch, Collect metrics from CloudWatch, Collect ECS metrics, and Collect Usage metrics. Everything else can be left disabled.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta9356012ac813662/6a85c8d45c27907614f59ad7/Screenshot_2023-06-15_at_11.35.28_AM.png" alt="" /></p>
<p>Another thing to keep in mind when using this integration is the set of permissions required to collect data from AWS. This can be found on the AWS integration page under AWS permissions. Take note of these permissions, as we will use them to create an IAM policy.</p>
<p>Next, we will add the AWS Fargate integration, which doesn’t require further configuration settings.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt143ff336054d94af/6a85c8d79bf99466ec0a052d/image37.png" alt="" /></p>
<p>Now that we have created the agent policy and attached the proper integrations, let’s create the agent that will implement the policy. Navigate back to the main Fleet page and click <strong>Add agent</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt70004b5c001ab658/6a85c8dabc5bb3ac12f81aa7/image41.png" alt="" /></p>
<p>Since we’ll be connecting to AWS Fargate through ECS, the host type should be set to this value. All the other default values can stay the same.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt25c143567249b28d/6a85c8dc5c279077d4f59adb/image15.png" alt="" /></p>
<p>Lastly, let’s create the enrollment token and attach the agent policy. This will enable AWS ECS Fargate to access Elastic and send data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6f72869a6ec7b0ee/6a85c8df43c0b77c5c2f05d8/image6.png" alt="" /></p>
<p>Once created, you should be able to see policy name, secret, and agent policy listed.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5f34035a6752e29a/6a85c8e2bc5bb317c8f81aad/image43.png" alt="" /></p>
<p>We’ll be using our Fleet credentials in the next step to send data to Elastic from AWS Fargate.</p>
<h2 id="partiisenddatatoelasticobservability">Part II: Send data to Elastic Observability</h2>
<p>It’s time to create our ECS Cluster, Service, and task definition in order to start running the container.</p>
<p>Log in to your AWS account and navigate to ECS.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b7234b6f3e2fa20/6a85c8e418249c2d6c18f755/image46.png" alt="" /></p>
<p>We’ll start by creating the cluster.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbc0ac0f14d810fda/6a85c8e7eaf24536dda49f19/image9.png" alt="" /></p>
<p>Add a name to the Cluster. And for subnets, only select the first two for us-east-1a and us-eastlb.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9997e6a16775a270/6a85c8ea501a85704bfbb2e2/image10.png" alt="" /></p>
<p>For the sake of the demo, we’ll keep the rest of the options set to default. Click <strong>Create</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltadfa4acf490f8759/6a85c8ed331d7a9211c31743/image11.png" alt="" /></p>
<p>We should see the cluster we created listed below.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4737257dd5cf14ff/6a85c8ef18249c82f618f759/Screenshot_2023-06-15_at_11.15.51_AM.png" alt="" /></p>
<p>Now that we’ve created our cluster to host our container, we want to create a task definition that will be used to set up our container. But before we do this, we will need to create a task role with an associated policy. This task role will allow for AWS metrics to be sent from AWS to the Elastic Agent.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfe7aa2800cf35d7b/6a85c8f28c2944847eb88ff9/image47.png" alt="" /></p>
<p>Navigate to IAM in AWS.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta32f8d2eeb1538e1/6a85c8f568266660a61eabbf/image32.png" alt="" /></p>
<p>Go to <strong>Policies -&gt; Create policy</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt60aaab8d80cfd8da/6a85c8f893ffb9fa6fb913f9/image31.png" alt="" /></p>
<p>Now we will reference the AWS permissions from the Fleet AWS integration page and use them to configure the policy. In addition to these permissions, we will also add the GetAtuhenticationToken action for ECR.</p>
<p>You can configure each one using the visual editor.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb1ffec16aa32f905/6a85c8fad7b2e7d13cfe84a8/image22.png" alt="" /></p>
<p>Or, use the JSON option. Don’t forget to replace the \&lt;account_id&gt; with your own.</p>
<pre><code>{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "VisualEditor0",
      "Effect": "Allow",
      "Action": [
        "sqs:DeleteMessage",
        "sqs:ChangeMessageVisibility",
        "sqs:ReceiveMessage",
        "ecr:GetDownloadUrlForLayer",
        "ecr:UploadLayerPart",
        "ecr:PutImage",
        "sts:AssumeRole",
        "rds:ListTagsForResource",
        "ecr:BatchGetImage",
        "ecr:CompleteLayerUpload",
        "rds:DescribeDBInstances",
        "logs:FilterLogEvents",
        "ecr:InitiateLayerUpload",
        "ecr:BatchCheckLayerAvailability"
      ],
      "Resource": [
        "arn:aws:iam::&lt;account_id&gt;:role/*",
        "arn:aws:logs:*:&lt;account_id&gt;:log-group:*",
        "arn:aws:sqs:*:&lt;account_id&gt;:*",
        "arn:aws:ecr:*:&lt;account_id&gt;:repository/*",
        "arn:aws:rds:*:&lt;account_id&gt;:target-group:*",
        "arn:aws:rds:*:&lt;account_id&gt;:subgrp:*",
        "arn:aws:rds:*:&lt;account_id&gt;:pg:*",
        "arn:aws:rds:*:&lt;account_id&gt;:ri:*",
        "arn:aws:rds:*:&lt;account_id&gt;:cluster-snapshot:*",
        "arn:aws:rds:*:&lt;account_id&gt;:cev:*/*/*",
        "arn:aws:rds:*:&lt;account_id&gt;:og:*",
        "arn:aws:rds:*:&lt;account_id&gt;:db:*",
        "arn:aws:rds:*:&lt;account_id&gt;:es:*",
        "arn:aws:rds:*:&lt;account_id&gt;:db-proxy-endpoint:*",
        "arn:aws:rds:*:&lt;account_id&gt;:secgrp:*",
        "arn:aws:rds:*:&lt;account_id&gt;:cluster:*",
        "arn:aws:rds:*:&lt;account_id&gt;:cluster-pg:*",
        "arn:aws:rds:*:&lt;account_id&gt;:cluster-endpoint:*",
        "arn:aws:rds:*:&lt;account_id&gt;:db-proxy:*",
        "arn:aws:rds:*:&lt;account_id&gt;:snapshot:*"
      ]
    },
    {
      "Sid": "VisualEditor1",
      "Effect": "Allow",
      "Action": [
        "sqs:ListQueues",
        "organizations:ListAccounts",
        "ec2:DescribeInstances",
        "tag:GetResources",
        "cloudwatch:GetMetricData",
        "ec2:DescribeRegions",
        "iam:ListAccountAliases",
        "sns:ListTopics",
        "sts:GetCallerIdentity",
        "cloudwatch:ListMetrics"
      ],
      "Resource": "*"
    },
    {
      "Sid": "VisualEditor2",
      "Effect": "Allow",
      "Action": "ecr:GetAuthorizationToken",
      "Resource": "arn:aws:ecr:*:&lt;account_id&gt;:repository/*"
    }
  ]
}
</code></pre>
<p>Review your changes.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltfa30238f739c3cbf/6a85c8fed7b2e74b05fe84ac/image3.png" alt="" /></p>
<p>Now let’s attach this policy to a role. Navigate to <strong>IAM -&gt; Roles</strong>. Click <strong>Create role</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7927c953da8dd23c/6a85c9014710c60f32d3cb0b/image45.png" alt="" /></p>
<p>Select AWS service as Trusted entity type and select EC2 as Use case. Click <strong>Next</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt09f9f2b78eccb086/6a85c90480984cadea668f9e/image24.png" alt="" /></p>
<p>Under permissions policies, select the policy we just created, as well as CloudWatchLogsFullAccess and AmazonEC2ContainerRegistryFullAccess. Click <strong>Next</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt76a43671e00db1ea/6a85c90768266655661eabc7/image27.png" alt="" /></p>
<p>Give the task role a name and description.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3f49c33f73d635e1/6a85c90a93ffb98d45b913fd/image39.png" alt="" /></p>
<p>Click <strong>Create role</strong>.</p>
<p>Now it’s time to create the task definition. Navigate to <strong>ECS -&gt; Task definitions</strong>. Click <strong>Create new task definition</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt710c84e6d06460ef/6a85c90c501a8573a7fbb2e8/image21.png" alt="" /></p>
<p>Let’s give this task definition a name.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt60dc8e5f34f7de35/6a85c90f9d2b7111f2f9394d/image14.png" alt="" /></p>
<p>After giving the task definition a name, you’ll add the Fleet credentials to the container section, which you can obtain from the Enrollment Tokens section of the Fleet section in Elastic Cloud. This allows us to host the Elastic Agent on the ECS container as a sidecar and send data to Elastic using Fleet credentials.</p>
<ul>
<li><p>Container name: <strong>elastic-agent-container</strong></p></li>
<li><p>Image: <strong>docker.elastic.co/beats/elastic-agent:8.19.13</strong></p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt801abae39b4a9621/6a85c91ebc5bb3be21f81abf/image40.png" alt="" /></p>
<p>Now let’s add the environment variables:</p>
<ul>
<li><p>FLEET_ENROLL: <strong>yes</strong></p></li>
<li><p>FLEET_ENROLLMENT_TOKEN: <strong>\</strong></p></li>
<li><p>FLEET_URL: <strong>\</strong></p></li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7d33d06272460b61/6a85c92133f244901449f4da/image26.png" alt="" /></p>
<p>For the sake of the demo, leave Environment, Monitoring, Storage, and Tags as default values. Now we will need to create a second container to run the image for the golang app stored in ECR. Click <strong>Add more containers</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltcfc6c6fd29e5e54b/6a85c924abdc29751c1224b8/image5.png" alt="" /></p>
<p>For Environment, we will reserve 1 vCPU and 3 GB of memory. Under Task role, search for the role we created that uses the IAM policy.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaa0e4e527e1f6176/6a85c92718249c3d0418f789/image7.png" alt="" /></p>
<p>Review the changes, then click <strong>Create</strong>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6145ac851750d6bb/6a85c929e2447ae70a8b13c4/image25.png" alt="" /></p>
<p>You should see your new task definition included in the list.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c4109a155aa44bf/6a85c92cabdc293fae1224bc/image20.png" alt="" /></p>
<p>The final step is to create the service that will connect directly to the fleet server.<br />
Navigate to the cluster you created and click <strong>Create</strong> under the Service tab.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbe52f20828680633/6a85c92f18249cfd8018f78d/image18.png" alt="" /></p>
<p>Let’s get our service environment configured.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt06147dd1ec890489/6a85c932abdc296cfa1224c0/image28.png" alt="" /></p>
<p>Set up the deployment configuration. Here you should provide the name of the task definition you created in the previous step. Also, provide the service with a unique name. Set the number of <strong>desired tasks</strong> to 2 instead of 1.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt83da0a1232f4abe8/6a85c93493ffb97168b91405/image16.png" alt="" /></p>
<p>Click <strong>Create</strong>. Now your service is running two tasks in your cluster using the task definition you provided.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4f77cfa47a90aeae/6a85c937f9373d15c996f568/image33.png" alt="" /></p>
<p>To recap, we set up a Fleet server in Elastic Cloud to receive AWS Fargate data. We then created our AWS Fargate cluster task definition with the Fleet credentials implemented within the container. Lastly, we created the service to send data about our host to Elastic.</p>
<p>Now let’s verify our Elastic Agent is healthy and properly receiving data from AWS Fargate.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3d79a96ce36d7900/6a85c93a68266621071eabd3/image36.png" alt="" /></p>
<p>We can also view a better breakdown of our agent on the Observability Overview page.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt04e50bfd77416b38/6a85c93d342d69d55721b0bb/image2.png" alt="" /></p>
<p>If we drill down to hosts, by clicking on host name we should be able to see more granular data. For instance, we can see the CPU Usage of the Elastic Agent itself that is deployed in our AWS Fargate environment.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5655c577c37482f4/6a85c93f11893c1a84a7ab5c/image8.png" alt="" /></p>
<p>Lastly, we can view the AWS Fargate dashboard generated using the data collected by our Elastic Agent. This is an out-of-the-box dashboard that can also be customized based on the data you would like to visualize.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt640bb8ca9841e713/6a85c9439bf9947fa00a0543/image23.png" alt="" /></p>
<p>As you can see in the dashboard we’re able to filter based on running tasks, as well as see a list of containers running in our environment. Something else that could be useful to show is the CPU usage per cluster as shown under CPU Utilization per Cluster.</p>
<p>The dashboard can pull data from different sources and in this case shows data for both AWS Fargate and the greater ECS cluster. The two containers at the bottom display the CPU and memory usage directly from ECS.</p>
<h2 id="conclusion">Conclusion</h2>
<p>In this article, we showed how to send data from AWS Fargate to Elastic Observability using the Elastic Agent and Fleet. Serverless architectures are quickly becoming industry standard in offloading the management of servers to third parties. However, this does not alleviate the responsibility of operations engineers to manage the data generated within these environments. Elastic Observability provides a way to not only ingest the data from serverless architectures, but also establish a roadmap to address future problems.</p>
<p>Start your own <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k?trk=5fbc596b-6d2a-433a-8333-0bd1f28e84da%E2%89%BBchannel=el">7-day free trial</a> by signing up via <a href="https://aws.amazon.com/marketplace/pp/prodview-voru33wi6xs7k?trk=d54b31eb-671c-49ba-88bb-7a1106421dfa%E2%89%BBchannel=el">AWS Marketplace</a> and quickly spin up a deployment in minutes on any of the <a href="https://www.elastic.co/guide/en/cloud/current/ec-reference-regions.html#ec_amazon_web_services_aws_regions">Elastic Cloud regions on AWS</a> around the world. Your AWS Marketplace purchase of Elastic will be included in your monthly consolidated billing statement and will draw against your committed spend with AWS.</p>
<p><strong>More resources on serverless and observability and AWS:</strong></p>
<ul>
<li><a href="https://www.elastic.co/blog/aws-service-metrics-monitor-observability-easy">Analyze your AWS application’s service metrics on Elastic Observability (EC2, ELB, RDS, and NAT)</a></li>
<li><a href="https://www.elastic.co/blog/observability-apm-aws-lambda-serverless-functions">Get visibility into AWS Lambda serverless functions with Elastic Observability</a></li>
<li><a href="https://www.elastic.co/blog/trace-based-testing-elastic-apm-tracetest">Trace-based testing with Elastic APM and Tracetest</a></li>
<li><a href="https://www.elastic.co/blog/aws-kinesis-data-firehose-elastic-observability-analytics">Sending AWS logs into Elastic via AWS Firehose</a></li>
</ul>
<p><em>The release and timing of any features or functionality described in this post remain at Elastic's sole discretion. Any features or functionality not currently available may not be delivered on time or at all.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/elastic-agent-monitor-ecs-aws-fargate-observability</link>
    <guid isPermaLink="false">elastic-agent-monitor-ecs-aws-fargate-observability</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[Alexis Roberson]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt916eb77a3c2b2a74/6a85c945682666aa6a1eabd7/blog-thumb-observability-pattern-color.png" length="0" type="image/png"/>
    <pubDate>Thu, 15 Jun 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to enable Kubernetes alerting with Elastic Observability]]></title>
    <description><![CDATA[In the Kubernetes world, different personas demand different kinds of insights. In this post, we’ll focus on alerting and provide an overview of how alerts in Elastic Observability can help users quickly identify Kubernetes problems.]]></description>
    <content:encoded><![CDATA[<p>In the Kubernetes world, different personas demand different kinds of insights. Developers are interested in granular metrics and debugging information. <a href="https://www.elastic.co/blog/elastic-observability-sre-incident-response">SREs</a> are interested in seeing everything at once to quickly get notified when a problem occurs and spot where the root cause is. In this post, we’ll focus on alerting and provide an overview of how alerts in Elastic Observability can help users quickly identify Kubernetes problems.</p>
<h2 id="whydoweneedalerts">Why do we need alerts?</h2>
<p>Logs, metrics, and traces are just the base to build a complete <a href="https://www.elastic.co/blog/kubernetes-cluster-metrics-logs-monitoring">monitoring solution for Kubernetes clusters</a>. Their main goal is to provide debugging information and historical evidence for the infrastructure.</p>
<p>While out-of-the-box dashboards, infrastructure topology, and logs exploration through Kibana are already quite handy to perform ad-hoc analyses, adding notifications and active monitoring of infrastructure allows users to deal with problems detected as early as possible and even proactively take actions to prevent their Kubernetes environments from facing even more serious issues.</p>
<h3 id="howcanthisbeachieved">How can this be achieved?</h3>
<p>By building alerts on top of their infrastructure, users can leverage the data and effectively correlate it to a specific notification, creating a wide range of possibilities to dynamically monitor and observe their Kubernetes cluster.</p>
<p>In this blog post, we will explore how users can leverage Elasticsearch’s search powers to define alerting rules in order to be notified when a specific condition occurs.</p>
<h2 id="slisalertsandsloswhyaretheyimportantforsres">SLIs, alerts, and SLOs: Why are they important for SREs?</h2>
<p>For site reliability engineers (SREs), the <a href="https://www.elastic.co/blog/elastic-observability-sre-incident-response">incident response time</a> is tightly coupled with the success of everyday work. Monitoring, alerting, and actions will help to discover, resolve, or prevent issues in their systems.</p>
<blockquote>
  <ul>
  <li><em>An SLA (Service Level Agreement) is an agreement you create with your users to specify the level of service they can expect.</em></li>
  <li><em>An SLO (Service Level Objective) is an agreement within an SLA about a specific metric like uptime or response time.</em></li>
  <li><em>An SLI (Service Level Indicator) measures compliance with an SLO.</em></li>
  </ul>
</blockquote>
<p>SREs’ day-to-day tasks and projects are driven by SLOs. By ensuring that SLOs are defended in the short term and that they can be maintained in the medium to long term, we lay the basis of a stable working infrastructure.</p>
<p>Having said this, identifying the high-level categories of SLOs is crucial in order to organize the work of an SRE. Then in each category of SLOs, SREs will need the corresponding SLIs that can cover the most important cases of their system under observation. Therefore, the decision of which SLIs we will need demands additional knowledge of the underlying system infrastructure.</p>
<p>One widely used approach to categorize SLIs and SLOs is the <a href="https://landing.google.com/sre/sre-book/chapters/monitoring-distributed-systems/#xref_monitoring_golden-signals">Four Golden Signals</a> method. The categories defined are Latency, Traffic, Errors, and Saturation.</p>
<p>A more specific approach is the <a href="https://thenewstack.io/monitoring-microservices-red-method/">The RED method</a> developed by Tom Wilkie, who was an SRE at Google and used the Four Golden Signals. The RED method drops the saturation category because this one is mainly used for more advanced cases — and people remember better things that come in threes.</p>
<p>Focusing on Kubernetes infrastructure operators, we will consider the following groups of infrastructure SLIs/SLOs:</p>
<ul>
<li>Group 1: Latency of control plane (apiserver,</li>
<li>Group 2: Resource utilization of the nodes/pods (how much cpu, memory, etc. is consumed)</li>
<li>Group 3: Errors (errors on logs or events or error count from components, network, etc.)</li>
</ul>
<h2 id="creatingalertsforakubernetescluster">Creating alerts for a Kubernetes cluster</h2>
<p>Now that we have a complete outline of our goal to define alerts based on SLIs/SLOs, we will dive into defining the proper alerting. Alerts can be built using <a href="https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html">Kibana</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt57faef9de27c8bb1/6a85cefc9829269340583960/blog-elastic-create-rule.png" alt="kubernetes create rule" /></p>
<p>See Elastic <a href="https://www.elastic.co/guide/en/kibana/current/alerting-getting-started.html">documentation</a>.</p>
<p>In this blog, we will define more complex alerts based on complex Elasticsearch queries provided by <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/watcher-getting-started.html">Watcher</a>’s functionality. <a href="https://www.elastic.co/guide/en/kibana/8.8/watcher-ui.html">Read more about Watcher</a> and how to properly use it in addition to the examples in this blog.</p>
<h3 id="latencyalerts">Latency alerts</h3>
<p>For this kind of alert, we want to define the basic SLOs for a Kubernetes control plane, which will ensure that the basic control plane components can service the end users without an issue. For instance, facing high latencies in queries against the Kubernetes API Server is enough of a signal that action needs to be taken.</p>
<h3 id="resourcesaturation">Resource saturation</h3>
<p>The next group of alerting will be resource utilization. Node’s CPU utilization or changes in Node’s condition is something critical for a cluster to ensure the smooth servicing of the workloads provisioned to run the applications that end users will interact with.</p>
<h3 id="errordetection">Error detection</h3>
<p>Last but not least, we will define alerts based on specific errors like the network error rate or Pods’ failures like the OOMKilled situation. It’s a very useful indicator for SRE teams to either detect issues on the infrastructure level or just be able to notify developer teams about problematic workloads. One example that we will examine later is having an application running as a Pod and constantly getting restarted because it hits its memory limit. In that case, the owners of this application will need to get notified to act properly.</p>
<h2 id="fromkubernetesdatatoelasticsearchqueries">From Kubernetes data to Elasticsearch queries</h2>
<p>Having a solid plan about the alerts that we want to implement, it's time to explore the data we have collected from the Kubernetes cluster and stored in Elasticsearch. For this we will consult the list of the available data fields that are ingested using the Elastic Agent Kubernetes <a href="https://docs.elastic.co/en/integrations/kubernetes">integration</a> (the full list of fields can be found <a href="https://www.elastic.co/guide/en/beats/metricbeat/current/exported-fields-kubernetes.html">here</a>). Using these fields we can create various alerts like:</p>
<ul>
<li>Node CPU utilization</li>
<li>Node Memory utilization</li>
<li>BW utilization</li>
<li>Pod restarts</li>
<li>Pod CPU/memory utilization</li>
</ul>
<h3 id="cpuutilizationalert">CPU utilization alert</h3>
<p>Our first example will use the CPU utilization fields to calculate the Node’s CPU utilization and create an alert. For this alert, we leverage the metrics:</p>
<pre><code>kubernetes.node.cpu.usage.nanocores
kubernetes.node.cpu.capacity.cores.
</code></pre>
<p>The following calculation (nodeUsage / 1000000000 ) /nodeCap grouped by node name will give us the CPU utilization of our cluster’s nodes.</p>
<p>The Watcher definition that implements this query can be created with the following API call to Elasticsearch:</p>
<pre><code>curl -X PUT "https://elastic:changeme@localhost:9200/_watcher/watch/Node-CPU-Usage?pretty" -k -H 'Content-Type: application/json' -d'
{
  "trigger": {
    "schedule": {
      "interval": "10m"
    }
  },
  "input": {
    "search": {
      "request": {
        "body": {
          "size": 0,
          "query": {
            "bool": {
              "must": [
                {
                  "range": {
                    "@timestamp": {
                      "gte": "now-10m",
                      "lte": "now",
                      "format": "strict_date_optional_time"
                    }
                  }
                },
                {
                  "bool": {
                    "must": [
                      {
                        "query_string": {
                          "query": "data_stream.dataset: kubernetes.node OR data_stream.dataset: kubernetes.state_node",
                          "analyze_wildcard": true
                        }
                      }
                    ],
                    "filter": [],
                    "should": [],
                    "must_not": []
                  }
                }
              ],
              "filter": [],
              "should": [],
              "must_not": []
            }
          },
          "aggs": {
            "nodes": {
              "terms": {
                "field": "kubernetes.node.name",
                "size": "10000",
                "order": {
                  "_key": "asc"
                }
              },
              "aggs": {
                "nodeUsage": {
                  "max": {
                    "field": "kubernetes.node.cpu.usage.nanocores"
                  }
                },
                "nodeCap": {
                  "max": {
                    "field": "kubernetes.node.cpu.capacity.cores"
                  }
                },
                "nodeCPUUsagePCT": {
                  "bucket_script": {
                    "buckets_path": {
                      "nodeUsage": "nodeUsage",
                      "nodeCap": "nodeCap"
                    },
                    "script": {
                      "source": "( params.nodeUsage / 1000000000 ) / params.nodeCap",
                      "lang": "painless",
                      "params": {
                        "_interval": 10000
                      }
                    },
                    "gap_policy": "skip"
                  }
                }
              }
            }
          }
        },
        "indices": [
          "metrics-kubernetes*"
        ]
      }
    }
  },
  "condition": {
    "array_compare": {
      "ctx.payload.aggregations.nodes.buckets": {
        "path": "nodeCPUUsagePCT.value",
        "gte": {
          "value": 80
        }
      }
    }
  },
  "actions": {
    "log_hits": {
      "foreach": "ctx.payload.aggregations.nodes.buckets",
      "max_iterations": 500,
      "logging": {
        "text": "Kubernetes node found with high CPU usage: {{ctx.payload.key}} -&gt; {{ctx.payload.nodeCPUUsagePCT.value}}"
      }
    }
  },
  "metadata": {
    "xpack": {
      "type": "json"
    },
    "name": "Node CPU Usage"
  }
}
</code></pre>
<h3 id="oomkilledpodsdetectionandalerting">OOMKilled Pods detection and alerting</h3>
<p>Another Watcher that we will explore is the one that detects Pods that have been restarted due to an OOMKilled error. This error is quite common in Kubernetes workloads and is useful to detect this early on to inform the team that owns this workload, so they can either investigate issues that could cause memory leaks or just consider increasing the required resources for the workload itself.</p>
<p>This information can be retrieved from a query like the following:</p>
<pre><code>kubernetes.container.status.last_terminated_reason: OOMKilled
</code></pre>
<p>Here is how we can create the respective Watcher with an API call:</p>
<pre><code>curl -X PUT "https://elastic:changeme@localhost:9200/_watcher/watch/Pod-Terminated-OOMKilled?pretty" -k -H 'Content-Type: application/json' -d'
{
  "trigger": {
    "schedule": {
      "interval": "1m"
    }
  },
  "input": {
    "search": {
      "request": {
        "search_type": "query_then_fetch",
        "indices": [
          "*"
        ],
        "rest_total_hits_as_int": true,
        "body": {
          "size": 0,
          "query": {
            "bool": {
              "must": [
                {
                  "range": {
                    "@timestamp": {
                      "gte": "now-1m",
                      "lte": "now",
                      "format": "strict_date_optional_time"
                    }
                  }
                },
                {
                  "bool": {
                    "must": [
                      {
                        "query_string": {
                          "query": "data_stream.dataset: kubernetes.state_container",
                          "analyze_wildcard": true
                        }
                      },
                      {
                        "exists": {
                          "field": "kubernetes.container.status.last_terminated_reason"
                        }
                      },
                      {
                        "query_string": {
                          "query": "kubernetes.container.status.last_terminated_reason: OOMKilled",
                          "analyze_wildcard": true
                        }
                      }
                    ],
                    "filter": [],
                    "should": [],
                    "must_not": []
                  }
                }
              ],
              "filter": [],
              "should": [],
              "must_not": []
            }
          },
          "aggs": {
            "pods": {
              "terms": {
                "field": "kubernetes.pod.name",
                "order": {
                  "_key": "asc"
                }
              }
            }
          }
        }
      }
    }
  },
  "condition": {
    "array_compare": {
      "ctx.payload.aggregations.pods.buckets": {
        "path": "doc_count",
        "gte": {
          "value": 1,
          "quantifier": "some"
        }
      }
    }
  },
  "actions": {
    "ping_slack": {
      "foreach": "ctx.payload.aggregations.pods.buckets",
      "max_iterations": 500,
      "webhook": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/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",
        "body": "{\"channel\": \"#k8s-alerts\", \"username\": \"k8s-cluster-alerting\", \"text\": \"Pod {{ctx.payload.key}} was terminated with status OOMKilled.\"}"
      }
    }
  },
  "metadata": {
    "xpack": {
      "type": "json"
    },
    "name": "Pod Terminated OOMKilled"
  }
}
</code></pre>
<h3 id="fromkubernetesdatatoalertssummary">From Kubernetes data to alerts summary</h3>
<p>So far we saw how we can start from plain Kubernetes fields, use them in ES queries, and build Watchers and alerts on top of them.</p>
<p>One can explore more possible data combinations and build queries and alerts following the examples we provided here. A <a href="https://github.com/elastic/integrations/tree/main/packages/kubernetes/docs">full list of alerts</a> is available, as well as a <a href="https://github.com/elastic/k8s-integration-infra/tree/main/scripts/alerting">basic scripted way of installing them</a>.</p>
<p>Of course, these examples come with simple actions defined that only log messages into the Elasticsearch logs. However, one can use more advanced and useful outputs like Slack’s webhooks:</p>
<pre><code>"actions": {
    "ping_slack": {
      "foreach": "ctx.payload.aggregations.pods.buckets",
      "max_iterations": 500,
      "webhook": {
        "method": "POST",
        "url": "https://hooks.slack.com/services/T04SW3JHXasdfasdfasdfasdfasdf",
        "body": "{\"channel\": \"#k8s-alerts\", \"username\": \"k8s-cluster-alerting\", \"text\": \"Pod {{ctx.payload.key}} was terminated with status OOMKilled.\"}"
      }
    }
  }
</code></pre>
<p>The result would be a Slack message like the following:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta7d49603f39ee1d7/6a85ceffbc5bb3503af81b75/blog-elastic-k8s-cluster-alerting.png" alt="" /></p>
<h2 id="nextsteps">Next steps</h2>
<p>In our next steps, we would like to make these alerts part of our Kubernetes integration, which would mean that the predefined alerts would be installed when users install or enable the Kubernetes integration. At the same time, we plan to implement some of these as Kibana’s native SLIs, providing the option to our users to quickly define SLOs on top of the SLIs through a nice user interface. If you’re interested to learn more about these, follow the public GitHub issues for more information and feel free to provide your feedback:</p>
<ul>
<li><a href="https://github.com/elastic/package-spec/issues/484">https://github.com/elastic/package-spec/issues/484</a></li>
<li><a href="https://github.com/elastic/kibana/issues/150050">https://github.com/elastic/kibana/issues/150050</a></li>
</ul>
<p>For those who are eager to start using Kubernetes alerting today, here is what you need to do:</p>
<ol>
<li>Make sure that you have an Elastic cluster up and running. The fastest way to deploy your cluster is to spin up a <a href="https://www.elastic.co/elasticsearch/service">free trial of Elasticsearch Service</a>.</li>
<li>Install the latest Elastic Agent on your Kubernetes cluster following the respective <a href="https://www.elastic.co/guide/en/fleet/master/running-on-kubernetes-managed-by-fleet.html">documentation</a>.</li>
<li>Install our provided alerts that can be found at <a href="https://github.com/elastic/integrations/tree/main/packages/kubernetes/docs">https://github.com/elastic/integrations/tree/main/packages/kubernetes/docs</a> or at <a href="https://github.com/elastic/k8s-integration-infra/tree/main/scripts/alerting">https://github.com/elastic/k8s-integration-infra/tree/main/scripts/alerting</a>.</li>
</ol>
<p>Of course, if you have any questions, remember that we are always happy to help on the Discuss <a href="https://discuss.elastic.co/">forums</a>.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/enable-kubernetes-alerting-observability</link>
    <guid isPermaLink="false">enable-kubernetes-alerting-observability</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Incident Management]]></category>
    <dc:creator><![CDATA[Christos Markou]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt691f1d7ad04639d8/6a85cf02501a854cfbfbb3a7/alert-management.jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 30 May 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Gain insights into Kubernetes errors with Elastic Observability logs and OpenAI]]></title>
    <description><![CDATA[This blog post provides an example of how one can analyze error messages in Elasticsearch with ChatGPT using the OpenAI API via Elasticsearch.]]></description>
    <content:encoded><![CDATA[<p>As we’ve shown in previous blogs, Elastic<sup>®</sup> provides a way to ingest and manage telemetry from the <a href="https://www.elastic.co/blog/kubernetes-cluster-metrics-logs-monitoring">Kubernetes cluster</a> and the <a href="https://www.elastic.co/blog/opentelemetry-observability">application</a> running on it. Elastic provides out-of-the-box dashboards to help with tracking metrics, <a href="https://www.elastic.co/blog/log-management-observability-operations">log management and analytics</a>, <a href="https://www.elastic.co/blog/adding-free-and-open-elastic-apm-as-part-of-your-elastic-observability-deployment">APM functionality</a> (which also supports <a href="https://www.elastic.co/blog/opentelemetry-observability">native OpenTelemetry</a>), and the ability to analyze everything with <a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops">AIOps features</a> and <a href="https://www.elastic.co/what-is/elasticsearch-machine-learning?elektra=home">machine learning</a> (ML). While you can use pre-existing <a href="https://www.elastic.co/blog/improving-information-retrieval-elastic-stack-search-relevance">ML models in Elastic</a>, <a href="https://www.elastic.co/blog/aiops-automation-analytics-elastic-observability-use-cases">out-of-the-box AIOps features</a>, or your own ML models, there is a need to dig deeper into the root cause of an issue.</p>
<p>Elastic helps reduce the operational work to support more efficient operations, but users still need a way to investigate and understand everything from the cause of an issue to the meaning of specific error messages. As an operations user, if you haven’t run into a particular error before or it's part of some runbook, you will likely go to Google and start searching for information.</p>
<p>OpenAI’s ChatGPT is becoming an interesting generative AI tool that helps provide more information using the models behind it. What if you could use OpenAI to obtain deeper insights (even simple semantics) for an error in your production or development environment? You can easily tie Elastic to OpenAI’s API to achieve this.</p>
<p>Kubernetes, a mainstay in most deployments (on-prem or in a cloud service provider) requires a significant amount of expertise — even if that expertise is to manage a service like GKE, EKS, or AKS.</p>
<p>In this blog, I will cover how you can use <a href="https://www.elastic.co/guide/en/kibana/current/watcher-ui.html">Elastic’s watcher</a> capability to connect Elastic to OpenAI and ask it for more information about the error logs Elastic is ingesting from a Kubernetes cluster(s). More specifically, we will use <a href="https://azure.microsoft.com/en-us/products/cognitive-services/openai-service">Azure’s OpenAI Service</a>. Azure OpenAI is a partnership between Microsoft and OpenAI, so the same models from OpenAI are available in the Microsoft version.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blteb7528d40990ace0/6a85cc4de2447afd238b1428/blog-elastic-azure-openai.png" alt="elastic azure openai" /></p>
<p>While this blog goes over a specific example, it can be modified for other types of errors Elastic receives in logs. Whether it's from AWS, the application, databases, etc., the configuration and script described in this blog can be modified easily.</p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up the configuration:</p>
<ul>
<li>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>).</li>
<li>We used a GCP GKE Kubernetes cluster, but you can use any Kubernetes cluster service (on-prem or cloud based) of your choice.</li>
<li>We’re also running with a version of the OpenTelemetry Demo. Directions for using Elastic with OpenTelemetry Demo are <a href="https://github.com/elastic/opentelemetry-demo">here</a>.</li>
<li>We also have an Azure account and <a href="https://azure.microsoft.com/en-us/products/cognitive-services/openai-service">Azure OpenAI service configured</a>. You will need to get the appropriate tokens from Azure and the proper URL endpoint from Azure’s OpenAI service.</li>
<li>We will use <a href="https://www.elastic.co/guide/en/kibana/current/devtools-kibana.html">Elastic’s dev tools</a>, the console to be specific, to load up and run the script, which is an <a href="https://www.elastic.co/guide/en/kibana/current/watcher-ui.html">Elastic watcher</a>.</li>
<li>We will also add a new index to store the results from the OpenAI query.</li>
</ul>
<p>Here is the configuration we will set up in this blog:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5a82668ffd902a5/6a85cc5033f2444aab49f528/blog-elastic-configuration.png" alt="Configuration to analyze Kubernetes cluster errors" /></p>
<p>As we walk through the setup, we’ll also provide the alternative setup with OpenAI versus Azure OpenAI Service.</p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Over the next few steps, I’ll walk through:</p>
<ul>
<li>Getting an account on Elastic Cloud and setting up your K8S cluster and application</li>
<li>Gaining Azure OpenAI authorization (alternative option with OpenAI)</li>
<li>Identifying Kubernetes error logs</li>
<li>Configuring the watcher with the right script</li>
<li>Comparing the output from Azure OpenAI/OpenAI versus ChatGPT UI</li>
</ul>
<h3 id="step0createanaccountonelasticcloud">Step 0: Create an account on Elastic Cloud</h3>
<p>Follow the instructions to <a href="https://cloud.elastic.co/registration?fromURI=/home">get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c2915ffc1f2fa55/6a85cc538c2944b302b89067/blog-elastic-start-cloud-trial.png" alt="elastic start cloud trial" /></p>
<p>Once you have the Elastic Cloud login, set up your Kubernetes cluster and application. A complete step-by-step instructions blog is available <a href="https://www.elastic.co/blog/kubernetes-cluster-metrics-logs-monitoring">here</a>. This also provides an overview of how to see Kubernetes cluster metrics in Elastic and how to monitor them with dashboards.</p>
<h3 id="step1azureopenaiserviceandauthorization">Step 1: Azure OpenAI Service and authorization</h3>
<p>When you log in to your Azure subscription and set up an instance of Azure OpenAI Service, you will be able to get your keys under Manage Keys.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc803bb9220bc5e6a/6a85cc56abdc296504122528/blog-elastic-microsoft-azure-manage-keys.png" alt="microsoft azure manage keys" /></p>
<p>There are two keys for your OpenAI instance, but you only need KEY 1 .</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd656cf4c96ad1ded/6a85cc5993ffb96abab9145d/blog-elastic-pme-openai-keys-and-endpoint.png" alt="Used with permission from Microsoft." /></p>
<p>Additionally, you will need to get the service URL. See the image above with our service URL blanked out to understand where to get the KEY 1 and URL.</p>
<p>If you are not using Azure OpenAI Service and the standard OpenAI service, then you can get your keys at:</p>
<pre><code>**https** ://platform.openai.com/account/api-keys
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda718eafc61eb455/6a85cc5c5c27903359f59b35/blog-elastic-api-keys.png" alt="api keys" /></p>
<p>You will need to create a key and save it. Once you have the key, you can go to Step 2.</p>
<h3 id="step2identifyingkuberneteserrorsinelasticlogs">Step 2: Identifying Kubernetes errors in Elastic logs</h3>
<p>As your Kubernetes cluster is running, <a href="https://docs.elastic.co/en/integrations/kubernetes">Elastic’s Kubernetes integration</a> running on the Elastic agent daemon set on your cluster is sending logs and metrics to Elastic. <a href="https://www.elastic.co/blog/log-monitoring-management-enterprise">The telemetry is ingested, processed, and indexed</a>. Kubernetes logs are stored in an index called .ds-logs-kubernetes.container_logs-default-* (* is for the date), and an automatic data stream logs-kubernetes.container_logs is also pre-loaded. So while you can use some of the out-of-the-box dashboards to investigate the metrics, you can also look at all the logs in Elastic Discover.</p>
<p>While any error from Kubernetes can be daunting, the more nuanced issues occur with errors from the pods running in the kube-system namespace. Take the pod konnectivity agent, which is essentially a network proxy agent running on the node to help establish tunnels and is a vital component in Kubernetes. Any error will cause the cluster to have connectivity issues and lead to a cascade of issues, so it’s important to understand and troubleshoot these errors.</p>
<p>When we filter out for error logs from the konnectivity agent, we see a good number of errors.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt61aec76988119b34/6a85cc604710c6e78bd3cb7d/blog-elastic-expanded-document.png" alt="expanded document" /></p>
<p>But unfortunately, we still can’t understand what these errors mean.</p>
<p>Enter OpenAI to help us understand the issue better. Generally, you would take the error message from Discover and paste it with a question in ChatGPT (or run a Google search on the message).</p>
<p>One error in particular that we’ve run into but do not understand is:</p>
<pre><code>E0510 02:51:47.138292       1 client.go:388] could not read stream err=rpc error: code = Unavailable desc = error reading from server: read tcp 10.120.0.8:46156-&gt;35.230.74.219:8132: read: connection timed out serverID=632d489f-9306-4851-b96b-9204b48f5587 agentID=e305f823-5b03-47d3-a898-70031d9f4768
</code></pre>
<p>The OpenAI output is as follows:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1cdff890473ba8c8/6a85cc63bc5bb35efdf81b21/blog-elastic-openai-output.png" alt="openai output" /></p>
<p>ChatGPT has given us a fairly nice set of ideas on why this rpc error is occurring against our konnectivity-agent.</p>
<p>So how can we get this output automatically for any error when those errors occur?</p>
<h3 id="step3configuringthewatcherwiththerightscript">Step 3: Configuring the watcher with the right script</h3>
<p><a href="https://www.elastic.co/guide/en/kibana/current/watcher-ui.html">What is an Elastic watcher?</a> Watcher is an Elasticsearch feature that you can use to create actions based on conditions, which are periodically evaluated using queries on your data. Watchers are helpful for analyzing mission-critical and business-critical streaming data. For example, you might watch application logs for errors causing larger operational issues.</p>
<p>Once a watcher is configured, it can be:</p>
<ol>
<li>Manually triggered</li>
<li>Run periodically</li>
<li>Created using a UI or a script</li>
</ol>
<p>In this scenario, we will use a script, as we can modify it easily and run it as needed.</p>
<p>We’re using the DevTools Console to enter the script and test it out:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt009cda608e30cff4/6a85cc66f61d6e61f59c2b4f/blog-elastic-test-script.png" alt="test script" /></p>
<p>The script is listed at the end of the blog in the <strong>appendix</strong>. It can also be downloaded <a href="https://github.com/elastic/chatgpt-error-analysis"><strong>here</strong></a> <strong>.</strong></p>
<p>The script does the following:</p>
<ol>
<li>It runs continuously every five minutes.</li>
<li>It will search the logs for errors from the container konnectivity-agent.</li>
<li>It will take the first error’s message, transform it (re-format and clean up), and place it into a variable first_hit.</li>
</ol>
<pre><code>"script": "return ['first_hit': ctx.payload.first.hits.hits.0._source.message.replace('\"', \"\")]"
</code></pre>
<ol>
<li>The error message is sent into OpenAI with a query:</li>
</ol>
<pre><code>What are the potential reasons for the following kubernetes error:
  { { ctx.payload.second.first_hit } }
</code></pre>
<ol>
<li>If the search yielded an error, it will proceed to then create an index and place the error message, pod.name (which is konnectivity-agent-6676d5695b-ccsmx in our setup), and OpenAI output into a new index called chatgpt_k8_analyzed.</li>
</ol>
<p>To see the results, we created a new data view called chatgpt_k8_analyzed against the newly created index:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte281840a289fb3f9/6a85cc6899083f864140f9f9/blog-elastic-edit-data-view.png" alt="edit data view" /></p>
<p>In Discover, the output on the data view provides us with the analysis of the errors.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf80847d904c25e91/6a85cc6c2d64d54455081d6a/blog-elastic-analysis-of-errors.png" alt="analysis of errors" /></p>
<p>For every error the script sees in the five minute interval, it will get an analysis of the error. We could alternatively also use a range as needed to analyze during a specific time frame. The script would just need to be modified accordingly.</p>
<h3 id="step4outputfromazureopenaiopenaivschatgptui">Step 4. Output from Azure OpenAI/OpenAI vs. ChatGPT UI</h3>
<p>As you noticed above, we got relatively the same result from the Azure OpenAI API call as we did by testing out our query in the ChatGPT UI. This is because we configured the API call to run the same/similar model as what was selected in the UI.</p>
<p>For the API call, we used the following parameters:</p>
<pre><code>"request": {
             "method" : "POST",
             "Url": "https://XXX.openai.azure.com/openai/deployments/pme-gpt-35-turbo/chat/completions?api-version=2023-03-15-preview",
             "headers": {"api-key" : "XXXXXXX",
                         "content-type" : "application/json"
                        },
             "body" : "{ \"messages\": [ { \"role\": \"system\", \"content\": \"You are a helpful assistant.\"}, { \"role\": \"user\", \"content\": \"What are the potential reasons for the following kubernetes error: {{ctx.payload.second.first_hit}}\"}], \"temperature\": 0.5, \"max_tokens\": 2048}" ,
              "connection_timeout": "60s",
               "read_timeout": "60s"
                            }
</code></pre>
<p>By setting the role: system with You are a helpful assistant and using the gpt-35-turbo url portion, we are essentially setting the API to use the davinci model, which is the same as the ChatGPT UI model set by default.</p>
<p>Additionally, for Azure OpenAI Service, you will need to set the URL to something similar the following:</p>
<pre><code>https://YOURSERVICENAME.openai.azure.com/openai/deployments/pme-gpt-35-turbo/chat/completions?api-version=2023-03-15-preview
</code></pre>
<p>If you use OpenAI (versus Azure OpenAI Service), the request call (against <a href="https://api.openai.com/v1/completions">https://api.openai.com/v1/completions</a>) would be as such:</p>
<pre><code>"request": {
            "scheme": "https",
            "host": "api.openai.com",
            "port": 443,
            "method": "post",
            "path": "\/v1\/completions",
            "params": {},
            "headers": {
               "content-type": "application\/json",
               "authorization": "Bearer YOUR_ACCESS_TOKEN"
                        },
            "body": "{ \"model\": \"text-davinci-003\",  \"prompt\": \"What are the potential reasons for the following kubernetes error: {{ctx.payload.second.first_hit}}\",  \"temperature\": 1,  \"max_tokens\": 512,     \"top_p\": 1.0,      \"frequency_penalty\": 0.0,   \"presence_penalty\": 0.0 }",
            "connection_timeout_in_millis": 60000,
            "read_timeout_millis": 60000
          }
</code></pre>
<p>If you are interested in creating a more OpenAI-based version, you can <a href="https://elastic-content-share.eu/downloads/watcher-job-to-integrate-chatgpt-in-elasticsearch/">download an alternative script</a> and look at <a href="https://mar1.hashnode.dev/unlocking-the-power-of-aiops-with-chatgpt-and-elasticsearch">another blog from an Elastic community member</a>.</p>
<h2 id="gainingotherinsightsbeyondkuberneteslogs">Gaining other insights beyond Kubernetes logs</h2>
<p>Now that the script is up and running, you can modify it using different:</p>
<ul>
<li>Inputs</li>
<li>Conditions</li>
<li>Actions</li>
<li>Transforms</li>
</ul>
<p>Learn more on how to modify it <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/xpack-alerting.html">here</a>. Some examples of modifications could include:</p>
<ol>
<li>Look for error logs from application components (e.g., cartService, frontEnd, from the OTel demo), cloud service providers (e.g., AWS/Azure/GCP logs), and even logs from components such as Kafka, databases, etc.</li>
<li>Vary the time frame from running continuously to running over a specific <a href="https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-range-query.html">range</a>.</li>
<li>Look for specific errors in the logs.</li>
<li>Query for analysis on a set of errors at once versus just one, which we demonstrated.</li>
</ol>
<p>The modifications are endless, and of course you can run this with OpenAI rather than Azure OpenAI Service.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you connect to OpenAI services (Azure OpenAI, as we showed, or even OpenAI) to better analyze an error log message instead of having to run several Google searches and hunt for possible insights.</p>
<p>Here’s a quick recap of what we covered:</p>
<ul>
<li>Developing an Elastic watcher script that can be used to find and send Kubernetes errors into OpenAI and insert them into a new index</li>
<li>Configuring Azure OpenAI Service or OpenAI with the right authorization and request parameters</li>
</ul>
<p>Ready to get started? Sign up <a href="https://cloud.elastic.co/registration">for Elastic Cloud</a> and try out the features and capabilities I’ve outlined above to get the most value and visibility out of your OpenTelemetry data.</p>
<h2 id="appendix">Appendix</h2>
<p>Watcher script</p>
<pre><code>PUT _watcher/watch/chatgpt_analysis
{
    "trigger": {
      "schedule": {
        "interval": "5m"
      }
    },
    "input": {
      "chain": {
          "inputs": [
              {
                  "first": {
                      "search": {
                          "request": {
                              "search_type": "query_then_fetch",
                              "indices": [
                                "logs-kubernetes*"
                              ],
                              "rest_total_hits_as_int": true,
                              "body": {
                                "query": {
                                  "bool": {
                                    "must": [
                                      {
                                        "match": {
                                          "kubernetes.container.name": "konnectivity-agent"
                                        }
                                      },
                                      {
                                        "match" : {
                                          "message":"error"
                                        }
                                      }
                                    ]
                                  }
                                },
                                "size": "1"
                              }
                            }
                        }
                    }
                },
                {
                    "second": {
                        "transform": {
                            "script": "return ['first_hit': ctx.payload.first.hits.hits.0._source.message.replace('\"', \"\")]"
                        }
                    }
                },
                {
                    "third": {
                        "http": {
                            "request": {
                                "method" : "POST",
                                "url": "https://XXX.openai.azure.com/openai/deployments/pme-gpt-35-turbo/chat/completions?api-version=2023-03-15-preview",
                                "headers": {
                                    "api-key" : "XXX",
                                    "content-type" : "application/json"
                                },
                                "body" : "{ \"messages\": [ { \"role\": \"system\", \"content\": \"You are a helpful assistant.\"}, { \"role\": \"user\", \"content\": \"What are the potential reasons for the following kubernetes error: {{ctx.payload.second.first_hit}}\"}], \"temperature\": 0.5, \"max_tokens\": 2048}" ,
                                "connection_timeout": "60s",
                                "read_timeout": "60s"
                            }
                        }
                    }
                }
            ]
        }
    },
    "condition": {
      "compare": {
        "ctx.payload.first.hits.total": {
          "gt": 0
        }
      }
    },
    "actions": {
        "index_payload" : {
            "transform": {
                "script": {
                    "source": """
                        def payload = [:];
                        payload.timestamp = new Date();
                        payload.pod_name = ctx.payload.first.hits.hits[0]._source.kubernetes.pod.name;
                        payload.error_message = ctx.payload.second.first_hit;
                        payload.chatgpt_analysis = ctx.payload.third.choices[0].message.content;
                        return payload;
                    """
                }
            },
            "index" : {
                "index" : "chatgpt_k8s_analyzed"
            }
        }
    }
}
</code></pre>
<h3 id="additionalloggingresources">Additional logging resources:</h3>
<ul>
<li><a href="https://www.elastic.co/getting-started/observability/collect-and-analyze-logs">Getting started with logging on Elastic (quickstart)</a></li>
<li><a href="https://www.elastic.co/guide/en/observability/current/logs-metrics-get-started.html">Ingesting common known logs via integrations (compute node example)</a></li>
<li><a href="https://docs.elastic.co/integrations">List of integrations</a></li>
<li><a href="https://www.elastic.co/blog/log-monitoring-management-enterprise">Ingesting custom application logs into Elastic</a></li>
<li><a href="https://www.elastic.co/blog/observability-logs-parsing-schema-read-write">Enriching logs in Elastic</a></li>
<li>Analyzing Logs with <a href="https://www.elastic.co/blog/reduce-mttd-ml-machine-learning-observability">Anomaly Detection (ML)</a> and <a href="https://www.elastic.co/blog/observability-logs-machine-learning-aiops">AIOps</a></li>
</ul>
<h3 id="commonusecaseexampleswithlogs">Common use case examples with logs:</h3>
<ul>
<li><a href="https://youtu.be/ax04ZFWqVCg">Nginx log management</a></li>
<li><a href="https://www.elastic.co/blog/vpc-flow-logs-monitoring-analytics-observability">AWS VPC Flow log management</a></li>
<li><a href="https://youtu.be/Li5TJAWbz8Q">PostgreSQL issue analysis with AIOps</a></li>
</ul>
<p><em>In this blog post, we may have used third party generative AI tools, which are owned and operated by their respective owners. Elastic does not have any control over the third party tools and we have no responsibility or liability for their content, operation or use, nor for any loss or damage that may arise from your use of such tools. Please exercise caution when using AI tools with personal, sensitive or confidential information. Any data you submit may be used for AI training or other purposes. There is no guarantee that information you provide will be kept secure or confidential. You should familiarize yourself with the privacy practices and terms of use of any generative AI tools prior to use.</em></p>
<p><em>Elastic, Elasticsearch and associated marks are trademarks, logos or registered trademarks of Elasticsearch N.V. in the United States and other countries. All other company and product names are trademarks, logos or registered trademarks of their respective owners.</em></p>
<p><em>Screenshots of Microsoft products used with permission from Microsoft.</em></p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kubernetes-errors-observability-logs-openai</link>
    <guid isPermaLink="false">kubernetes-errors-observability-logs-openai</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[LLM Observability]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf5a82668ffd902a5/6a85cc5033f2444aab49f528/blog-elastic-configuration.png" length="0" type="image/png"/>
    <pubDate>Thu, 18 May 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[How to monitor Kafka and Confluent Cloud with Elastic Observability]]></title>
    <description><![CDATA[This blog post will take you through best practices to observe Kafka-based solutions implemented on Confluent Cloud with Elastic Observability.]]></description>
    <content:encoded><![CDATA[<p>The blog will take you through best practices to observe Kafka-based solutions implemented on Confluent Cloud with Elastic Observability. (To monitor Kafka brokers that are not in Confluent Cloud, I recommend checking out <a href="https://www.elastic.co/blog/how-to-monitor-containerized-kafka-with-elastic-observability">this blog</a>.) We will instrument Kafka applications with <a href="https://www.elastic.co/observability/application-performance-monitoring">Elastic APM</a>, use the Confluent Cloud metrics endpoint to get data about brokers, and pull it all together with a unified Kafka and Confluent Cloud monitoring dashboard in <a href="https://www.elastic.co/observability">Elastic Observability</a>.</p>
<h2 id="usingfullstackelasticobservabilitytounderstandkafkaandconfluentperformance">Using full-stack Elastic Observability to understand Kafka and Confluent performance</h2>
<p>In the <a href="https://dice.viewer.foleon.com/ebooks/dice-tech-salary-report-explore/">2023 Dice Tech Salary Report</a>, Elasticsearch and Kakfa are ranked #3 and #5 out of the top 12 <a href="https://dice.viewer.foleon.com/ebooks/dice-tech-salary-report-explore/salary-trends#Skills">most in demand skills</a> at the moment, so it’s no surprise that we are seeing a large number of customers who are implementing data in motion with Kafka.</p>
<p><a href="https://www.elastic.co/integrations/data-integrations?search=kafka">Kafka</a> comes with some additional complexities that go beyond traditional architectures and which make observability an even more important topic. Understanding where the bottlenecks are in messaging and stream-based architectures can be tough. This is why you need a comprehensive observability solution with <a href="https://www.elastic.co/blog/aiops-use-cases-observability-operations">machine learning</a> to help you.</p>
<p>In this blog, we will explore how to get Kafka applications instrumented with <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">Elastic APM</a>, how to collect performance data with JMX, and how you can use the Elasticsearch Platform to pull in data from Confluent Cloud — which is by far the easiest and most cost-effective way to implement Kafka architectures.</p>
<p>For this blog post, we will be following the code at this <a href="https://github.com/davidgeorgehope/multi-cloud">git repository</a>. There are three services here that are designed to run on two clouds and push data from one cloud to the other and finally into Google BigQuery. We want to monitor all of this using Elastic Observability to give you a complete picture of Confluent and Kafka Services performance as a teaser — this is the goal below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b83acb7398304ef/6a85cb8e80984cb656668fec/blog-elastic-observability-producer_metrics.png" alt="kafka producer metrics" /></p>
<h2 id="alookatthearchitecture">A look at the architecture</h2>
<p>As mentioned, we have three <a href="https://www.elastic.co/observability/cloud-monitoring">multi-cloud services</a> implemented in our example application.</p>
<p>The first service is a Spring WebFlux service that runs inside AWS EKS. This service will take a message from a REST Endpoint and simply put it straight on to a Kafka topic.</p>
<p>The second service, which is also a Spring WebFlux service hosted inside Google Cloud Platform (GCP) with its <a href="https://www.elastic.co/observability/google-cloud-monitoring">Google Cloud monitoring</a>, will then pick this up and forward it to another service that will put the message into BigQuery.</p>
<p>These services are all instrumented using Elastic APM. For this blog, we have decided to use Spring config to inject and configure the APM agent. You could of course use the “-javaagent” argument to inject the agent instead if preferred.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26abffd850b918eb/6a85cb90078290b03c32177c/blog-elastic-obsevability-aws-kafka-google-cloud.png" alt="aws kafka google cloud" /></p>
<h2 id="gettingstartedwithelasticobservabilityandconfluentcloud">Getting started with Elastic Observability and Confluent Cloud</h2>
<p>Before we dive into the application and its configuration, you will want to get an Elastic Cloud and Confluent Cloud account. You can sign up here for <a href="https://www.elastic.co/cloud/">Elastic</a> and here for <a href="https://www.confluent.io/confluent-cloud/">Confluent Cloud</a>. There are some initial configuration steps we need to do inside Confluent Cloud, as you will need to create three topics: gcpTopic, myTopic, and topic_2.</p>
<p>When you sign up for Confluent Cloud, you will be given an option of what type of cluster to create. For this walk-through, a Basic cluster is fine (as shown) — if you are careful about usage, it will not cost you a penny.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc4c5c643934dbd2f/6a85cb9411893c4c32a7aba0/blog-elastic-observability-confluent-create-cluster.png" alt="confluent create cluster" /></p>
<p>Once you have a cluster, go ahead and create the three topics.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb7f03631f81bcf62/6a85cb96331d7aaed8c317a9/blog-elastic-observability-confluent-topics.png" alt="confluent topics" /></p>
<p>For this walk-through, you will only need to create single partition topics as shown below:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt480e1df0acaafce5/6a85cb999bf99456280a0581/blog-elastic-observability-new-topic.png" alt="new topic" /></p>
<p>Now we are ready to set up the Elastic Cloud cluster.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3c52f84578c22908/6a85cb9c18249c40f018f7cb/blog-elastic-observability-create-a-deployment.png" alt="create a deployment" /></p>
<p>One thing to note here is that when setting up an Elastic cluster, the defaults are mostly OK. With one minor tweak to add in the Machine Learning under “Advanced Settings,” add capacity for machine learning here.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6d43d7fb6ea1311d/6a85cb9f99083f43c340f9e5/blog-elastic-observability-machine-learning-instances.png" alt="machine learning instances" /></p>
<h2 id="gettingapmupandrunning">Getting APM up and running</h2>
<p>The first thing we want to do here is get our Spring Boot Webflux-based services up and running. For this blog, I have decided to implement this using the Spring Configuration, as you can see below. For brevity, I have not listed all the JMX configuration information, but you can see those details in <a href="https://github.com/davidgeorgehope/multi-cloud/blob/main/aws-multi-cloud/src/main/java/com/elastic/multicloud/ElasticApmConfig.java">GitHub</a>.</p>
<pre><code>package com.elastic.multicloud;
import co.elastic.apm.attach.ElasticApmAttacher;
import jakarta.annotation.PostConstruct;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Setter
@Configuration
@ConfigurationProperties(prefix = "elastic.apm")
@ConditionalOnProperty(value = "elastic.apm.enabled", havingValue = "true")
public class ElasticApmConfig {

    private static final String SERVER_URL_KEY = "server_url";
    private String serverUrl;

    private static final String SERVICE_NAME_KEY = "service_name";
    private String serviceName;

    private static final String SECRET_TOKEN_KEY = "secret_token";
    private String secretToken;

    private static final String ENVIRONMENT_KEY = "environment";
    private String environment;

    private static final String APPLICATION_PACKAGES_KEY = "application_packages";
    private String applicationPackages;

    private static final String LOG_LEVEL_KEY = "log_level";
    private String logLevel;
    private static final Logger LOGGER = LoggerFactory.getLogger(ElasticApmConfig.class);

    @PostConstruct
    public void init() {
        LOGGER.info(environment);

        Map&lt;String, String&gt; apmProps = new HashMap&lt;&gt;(6);
        apmProps.put(SERVER_URL_KEY, serverUrl);
        apmProps.put(SERVICE_NAME_KEY, serviceName);
        apmProps.put(SECRET_TOKEN_KEY, secretToken);
        apmProps.put(ENVIRONMENT_KEY, environment);
        apmProps.put(APPLICATION_PACKAGES_KEY, applicationPackages);
        apmProps.put(LOG_LEVEL_KEY, logLevel);
        apmProps.put("enable_experimental_instrumentations","true");
          apmProps.put("capture_jmx_metrics","object_name[kafka.producer:type=producer-metrics,client-id=*] attribute[batch-size-avg:metric_name=kafka.producer.batch-size-avg]");


        ElasticApmAttacher.attach(apmProps);
    }
}
</code></pre>
<p>Now obviously this requires some dependencies, which you can see here in the Maven pom.xml.</p>
<pre><code>&lt;dependency&gt;
            &lt;groupId&gt;co.elastic.apm&lt;/groupId&gt;
            &lt;artifactId&gt;apm-agent-attach&lt;/artifactId&gt;
            &lt;version&gt;1.35.1-SNAPSHOT&lt;/version&gt;
        &lt;/dependency&gt;
        &lt;dependency&gt;
            &lt;groupId&gt;co.elastic.apm&lt;/groupId&gt;
            &lt;artifactId&gt;apm-agent-api&lt;/artifactId&gt;
            &lt;version&gt;1.35.1-SNAPSHOT&lt;/version&gt;
        &lt;/dependency&gt;
</code></pre>
<p>Strictly speaking, the agent-api is not required, but it could be useful if you have a desire to add your own monitoring code (as per the example below). The agent will happily auto-instrument without needing to do that though.</p>
<pre><code>Transaction transaction = ElasticApm.currentTransaction();
        Span span = ElasticApm.currentSpan()
                .startSpan("external", "kafka", null)
                .setName("DAVID").setServiceTarget("kafka","gcp-elastic-apm-spring-boot-integration");
        try (final Scope scope = transaction.activate()) {
            span.injectTraceHeaders((name, value) -&gt; producerRecord.headers().add(name,value.getBytes()));
            return Mono.fromRunnable(() -&gt; {
                kafkaTemplate.send(producerRecord);
            });
        } catch (Exception e) {
            span.captureException(e);
            throw e;
        } finally {
            span.end();
        }
</code></pre>
<p>Now we have enough code to get our agent bootstrapped.</p>
<p>To get the code from the GitHub repository up and running, you will need the following installed on your system and to ensure that you have the credentials for your GCP and AWS cloud.</p>
<pre><code>Java
Maven
Docker
Kubernetes CLI (kubectl)
</code></pre>
<h3 id="clonetheproject">Clone the project</h3>
<p>Clone the multi-cloud Spring project to your local machine.</p>
<pre><code>git clone https://github.com/davidgeorgehope/multi-cloud
</code></pre>
<h3 id="buildtheproject">Build the project</h3>
<p>From each service in the project (aws-multi-cloud, gcp-multi-cloud, gcp-bigdata-consumer-multi-cloud), run the following commands to build the project.</p>
<pre><code>mvn clean install
</code></pre>
<p>Now you can run the Java project locally.</p>
<pre><code>java -jar gcp-bigdata-consumer-multi-cloud-0.0.1-SNAPSHOT.jar --spring.config.location=/Users/davidhope/applicaiton-gcp.properties
</code></pre>
<p>That will just get the Java application running locally, but you can also deploy this to Kubernetes using EKS and GKE as shown below.</p>
<h3 id="createadockerimage">Create a Docker image</h3>
<p>Create a Docker image from the built project using the dockerBuild.sh provided in the project. You may want to customize this shell script to upload the built docker image to your own docker repository.</p>
<pre><code>./dockerBuild.sh
</code></pre>
<h3 id="createanamespaceforeachservice">Create a namespace for each service</h3>
<pre><code>kubectl create namespace aws
</code></pre>
<pre><code>kubectl create namespace gcp-1
</code></pre>
<pre><code>kubectl create namespace gcp-2
</code></pre>
<p>Once you have the namespaces created, you can switch context using the following command:</p>
<pre><code>kubectl config set-context --current --namespace=my-namespace
</code></pre>
<h3 id="configurationforeachservice">Configuration for each service</h3>
<p>Each service needs an application.properties file. I have put an example <a href="https://github.com/davidgeorgehope/multi-cloud/blob/main/gcp-bigdata-consumer-multi-cloud/application.properties">here</a>.</p>
<p>You will need to replace the following properties with those you find in Elastic.</p>
<pre><code>elastic.apm.server-url=
elastic.apm.secret-token=
</code></pre>
<p>These can be found by going into Elastic Cloud and clicking on <strong>Services</strong> inside APM and then <strong>Add Data</strong> , which should be visible in the top right corner.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt74c077968f0c2141/6a85cba168266603f01eac21/blog-elastic-observability-add-data.png" alt="add data" /></p>
<p>From there you will see the following, which gives you the config information you need.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltaacc6d805866b018/6a85cba4501a852f79fbb341/blog-elastic-observability-apm-agents.png" alt="apm agents" /></p>
<p>You will need to replace the following properties with those you find in Confluent Cloud.</p>
<pre><code>elastic.kafka.producer.sasl-jaas-config=
</code></pre>
<p>This configuration comes from the Clients page in Confluent Cloud.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt79dc20646b2e6404/6a85cba7d7b2e75ea0fe84e8/blog-elastic-observability-confluent-new-client.png" alt="confluent new client" /></p>
<h3 id="addingtheconfigforeachserviceinkubernetes">Adding the config for each service in Kubernetes</h3>
<p>Once you have a fully configured application properties, you need to add it to your <a href="https://www.elastic.co/blog/kubernetes-cluster-metrics-logs-monitoring">Kubernetes environment</a> as below.</p>
<p>From the aws namespace.</p>
<pre><code>kubectl create secret generic my-app-config --from-file=application.properties
</code></pre>
<p>From the gcp-1 namespace.</p>
<pre><code>kubectl create secret generic my-app-config --from-file=application.properties
</code></pre>
<p>From the gcp-2 namespace.</p>
<pre><code>kubectl create secret generic bigdata-creds --from-file=elastic-product-marketing-e145e13fbc7c.json

kubectl create secret generic my-app-config-gcp-bigdata --from-file=application.properties
</code></pre>
<h3 id="createakubernetesdeployment">Create a Kubernetes deployment</h3>
<p>Create a Kubernetes deployment YAML file and add your Docker image to it. You can use the deployment.yaml file provided in the project as a template. Make sure to update the image name in the file to match the name of the Docker image you just created.</p>
<pre><code>kubectl apply -f deployment.yaml
</code></pre>
<h3 id="createakubernetesservice">Create a Kubernetes service</h3>
<p>Create a Kubernetes service YAML file and add your deployment to it. You can use the service.yaml file provided in the project as a template.</p>
<pre><code>kubectl apply -f service.yaml
</code></pre>
<h3 id="accessyourapplication">Access your application</h3>
<p>Your application is now running in a Kubernetes cluster. To access it, you can use the service's cluster IP and port. You can get the service's IP and port using the following command.</p>
<pre><code>kubectl get services
</code></pre>
<p>Now once you know where the service is, you need to execute it!</p>
<p>You can regularly poke the service endpoint using the following command.</p>
<pre><code>curl -X POST -H "Content-Type: application/json" -d '{"name": "linuxize", "email": "linuxize@example.com"}' http://localhost:8080/api/my-objects/publish
</code></pre>
<p>With this up and running, you should see the following service map build out in the Elastic APM product.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6bcd176aacf5c2bf/6a85cbaa68266613df1eac25/blog-elastic-observability-aws-elastic-apm-spring-boot.png" alt="aws elastic apm spring boot" /></p>
<p>And traces will contain a waterfall graph showing all the spans that have executed across this distributed application, allowing you to pinpoint where any issues are within each transaction.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt94b9bc91dc092bb9/6a85cbad9bf994e9330a0585/blog-elastic-observability-services.png" alt="observability services" /></p>
<h2 id="jmxforkafkaproducerconsumermetrics">JMX for Kafka Producer/Consumer metrics</h2>
<p>In the previous part of this blog, we briefly touched on the JMX metric configuration you can see below.</p>
<pre><code>"capture_jmx_metrics","object_name[kafka.producer:type=producer-metrics,client-id=*] attribute[batch-size-avg:metric_name=kafka.producer.batch-size-avg]"
</code></pre>
<p>We can use this “capture_jmx_metrics” configuration to configure JMX for any Kafka Producer/Consumer metrics we want to monitor.</p>
<p>Check out the documentation <a href="https://www.elastic.co/guide/en/apm/agent/java/current/config-jmx.html">here</a> to understand how to configure this and <a href="https://docs.confluent.io/platform/current/kafka/monitoring.html">here</a> to see the available JMX metrics you can monitor. In the <a href="https://github.com/davidgeorgehope/multi-cloud/blob/main/gcp-bigdata-consumer-multi-cloud/src/main/java/com/elastic/multicloud/ElasticApmConfig.java">example code in GitHub</a>, we actually pull all the available metrics in, so you can check in there how to configure this.</p>
<p>One thing that’s worth pointing out here is that it’s important to use the “metric_name” property shown above or it gets quite difficult to find the metrics in Elastic Discover without being specific here.</p>
<h2 id="monitoringconfluentcloudwithelasticobservability">Monitoring Confluent Cloud with Elastic Observability</h2>
<p>So we now have some good monitoring set up for Kafka Producers and Consumers and we can trace transactions between services down to the lines of code that are executing. The core part of our Kafka infrastructure is hosted in Confluent Cloud. How, then, do we get data from there into our <a href="https://www.elastic.co/observability">full stack observability solution</a>?</p>
<p>Luckily, Confluent has done a fantastic job of making this easy. It provides important Confluent Cloud metrics via an open Prometheus-based metrics URL. So let's get down to business and configure this to bring data into our <a href="https://www.elastic.co/observability">observability tool</a>.</p>
<p>The first step is to configure Confluent Cloud with the MetricsViewer. The MetricsViewer role provides service account access to the Metrics API for all clusters in an organization. This role also enables service accounts to import metrics into third-party metrics platforms.</p>
<p>To assign the MetricsViewer role to a new service account:</p>
<ol>
<li>In the top-right administration menu (☰) in the upper-right corner of the Confluent Cloud user interface, click <strong>ADMINISTRATION &gt; Cloud API keys</strong>.</li>
<li>Click <strong>Add key</strong>.</li>
<li>Click the <strong>Granular access tile</strong> to set the scope for the API key. Click <strong>Next</strong>.</li>
<li>Click <strong>Create a new one</strong> and specify the service account name. Optionally, add a description. Click <strong>Next</strong>.</li>
<li>The API key and secret are generated for the service account. You will need this API key and secret to connect to the cluster, so be sure to safely store this information. Click <strong>Save</strong>. The new service account with the API key and associated ACLs is created. When you return to the API access tab, you can view the newly-created API key to confirm.</li>
<li>Return to Accounts &amp; access in the administration menu, and in the Accounts tab, click <strong>Service accounts</strong> to view your service accounts.</li>
<li>Select the service account that you want to assign the MetricsViewer role to.</li>
<li>In the service account’s details page, click <strong>Access</strong>.</li>
<li>In the tree view, open the resource where you want the service account to have the MetricsViewer role.</li>
<li>Click <strong>Add role assignment</strong> and select the MetricsViewer tile. Click <strong>Save</strong>.</li>
</ol>
<p>Next we can head to <a href="https://www.elastic.co/observability">Elastic Observability</a> and configure the Prometheus integration to pull in the metrics data.</p>
<p>Go to the integrations page in Kibana.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18bf177b652a48e6/6a85cbb04710c62eb0d3cb55/blog-elastic-observability-integrations.png" alt="observability integrations" /></p>
<p>Find the Prometheus integration. We are using the Prometheus integration because the Confluent Cloud metrics server can provide data in prometheus format. Trust us, this works really well — good work Confluent!</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4d48901dde740fee/6a85cbb243c0b72c932f0622/blog-elastic-observability-integrations-prometheus.png" alt="integrations prometheus" /></p>
<p>Add Prometheus in the next page.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt84626c46ada410b0/6a85cbb507829026aa321782/blog-elastic-observability-add-prometheus.png" alt="add prometheus" /></p>
<p>Configure the Prometheus plugin in the following way: In the hosts box, add the following URL, replacing the resource kafka id with the cluster id you want to monitor.</p>
<pre><code>https://api.telemetry.confluent.cloud:443/v2/metrics/cloud/export?resource.kafka.id=lkc-3rw3gw
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb9a0004dfff705f8/6a85cbb793ffb91265b91441/blog-elastic-observability-collect-prometheus-metrics.png" alt="collect prometheus metrics" /></p>
<p>Add the username and password under the advanced options you got from the API keys step you executed against Confluent Cloud above.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3cf8ea6231c6d4a4/6a85cbba9d2b716e39f9399c/blog-elastic-observability-http-config-options.png" alt="http config options" /></p>
<p>Once the Integration is created, <a href="https://www.elastic.co/guide/en/fleet/current/agent-policy.html#apply-a-policy">the policy needs to be applied</a> to an instance of a running Elastic Agent.</p>
<p>That’s it! It’s that easy to get all the data you need for a full stack observability monitoring solution.</p>
<p>Finally, let’s pull all this together in a dashboard.</p>
<h2 id="pullingitalltogether">Pulling it all together</h2>
<p>Using Kibana to generate dashboards is super easy. If you configured everything the way we recommended above, you should find the metrics (producer/consumer/brokers) you need to create your own dashboard as per the following screenshot.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt3ad8b73135f3f525/6a85cbbd27c5cdc4635f7400/blog-elastic-observability-dashboard-metrics.png" alt="dashboard metrics" /></p>
<p>Luckily, I made a dashboard for you and stored it in <a href="https://github.com/davidgeorgehope/multi-cloud/blob/main/export.ndjson">GitHub</a>. Take a look below and use this to import it into your own environments.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2b83acb7398304ef/6a85cb8e80984cb656668fec/blog-elastic-observability-producer_metrics.png" alt="producer metrics" /></p>
<h2 id="addingtheicingonthecakemachinelearninganomalydetection">Adding the icing on the cake: machine learning anomaly detection</h2>
<p>Now that we have all the critical bits in place, we are going to add the icing on the cake: machine learning (ML)!</p>
<p>Within Kibana, let's head over to the Machine Learning tab in “Analytics.”</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt216b48d488ae22bf/6a85cbc0d7b2e7b72bfe84f0/blog-elastic-observability-kibana-analytics.png" alt="kibana analytics" /></p>
<p>Go to the jobs page, where we’ll get started creating our first anomaly detection job.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltda3a4d09361a209d/6a85cbc3eaf245fde1a49f6b/blog-elastic-observability-create-your-first-anomaly-detection-job.png" alt="create your first anomaly detection job" /></p>
<p>The metrics data view contains what we need to create this new anomaly detection job.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt790afdb1a6ce3000/6a85cbc580984c60f4668ff0/blog-elastic-observability-metrics.png" alt="observability metrics" /></p>
<p>Use the wizard and select a “Single Metric.”</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt035ea305957b7ced/6a85cbc84710c67cdcd3cb59/blog-elastic-observability-use-a-wizard.png" alt="use a wizard" /></p>
<p>Use the full data.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt18b90b2f2b100e42/6a85cbca93ffb9f68ab91445/blog-elastic-observability-use-full-data.png" alt="use full data" /></p>
<p>In this example, we are going to look for anomalies in the connection count. We really do not want a major deviation here, as this could indicate something very bad occurring if we suddenly have too many or too few things connecting to our Kafka cluster.</p>
<p>Once you have selected the connection count metric, you can proceed through the wizard and eventually your ML job will be created and you should be able to view the data as per the example below.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltbb7c65944c99c89b/6a85cbcdf61d6ebd009c2b35/blog-elastic-observability-single-metric-viewer.png" alt="single metric viewer" /></p>
<p>Congratulations, you have now created a machine learning job to alert you if there are any problems with your Kafka cluster, adding <a href="https://www.elastic.co/observability/aiops">a full AIOps solution</a> to your Kafka and Confluent observability!</p>
<h2 id="summary">Summary</h2>
<p>We looked at monitoring Kafka-based solutions implemented on Confluent Cloud using Elastic Observability.</p>
<p>We covered the architecture of a multi-cloud solution involving AWS EKS, Confluent Cloud, and GCP GKE. We looked at how to instrument Kafka applications with Elastic APM, use JMX for Kafka Producer/Consumer metrics, integrate Prometheus, and set up machine learning anomaly detection.</p>
<p>We went through a detailed walk-through with code snippets, configuration steps, and deployment instructions included to help you get started.</p>
<p>Interested in learning more about Elastic Observability? Check out the following resources:</p>
<ul>
<li><a href="https://www.elastic.co/virtual-events/intro-to-elastic-observability">An Introduction to Elastic Observability</a></li>
<li><a href="https://www.elastic.co/training/observability-fundamentals">Observability Fundamentals Training</a></li>
<li><a href="https://www.elastic.co/observability/demo">Watch an Elastic Observability demo</a></li>
<li><a href="https://www.elastic.co/blog/observability-predictions-trends-2023">Observability Predictions and Trends for 2023</a></li>
</ul>
<p>And sign up for our <a href="https://www.elastic.co/virtual-events/emerging-trends-in-observability">Elastic Observability Trends Webinar</a> featuring AWS and Forrester, not to be missed!</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/monitor-kafka-confluent-cloud-elastic-observability</link>
    <guid isPermaLink="false">monitor-kafka-confluent-cloud-elastic-observability</guid>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <dc:creator><![CDATA[David Hope]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdff999229029e1b1/6a85cbd0bc5bb32326f81b11/patterns-white-background-no-logo-observability_(1).png" length="0" type="image/png"/>
    <pubDate>Mon, 03 Apr 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Using Elastic to observe GKE Autopilot clusters]]></title>
    <description><![CDATA[See how deploying the Elastic Agent onto a GKE Autopilot cluster makes observing the cluster’s behavior easy. Kibana integrations make visualizing the behavior a simple addition to your observability dashboards.]]></description>
    <content:encoded><![CDATA[<p>Elastic has formally supported Google Kubernetes Engine (GKE) since January 2020, when Elastic Cloud on Kubernetes was announced. Since then, Google has expanded GKE, with new service offerings and delivery mechanisms. One of those new offerings is GKE Autopilot. Where GKE is a managed Kubernetes environment, GKE Autopilot is a mode of Kubernetes operation where Google manages your cluster configuration, scaling, security, and more. It is production ready and removes many of the challenges associated with tasks like workload management, deployment automation, and scalability rules. Autopilot lets you focus on building and deploying your application while Google manages everything else.</p>
<p>Elastic is committed to supporting Google Kubernetes Engine (GKE) in all of its delivery modes. In October, during the Google Cloud Next ‘22 event, we announced our intention to integrate and certify Elastic Agent on Anthos, Autopilot, Google Distributed Cloud, and more.</p>
<p>Since that event, we have worked together with Google to get the Elastic Agent certified for use on Anthos, but we didn’t stop there.</p>
<p>Today we are happy to <a href="https://github.com/elastic/elastic-agent/blob/autopilotdocumentaton/docs/elastic-agent-gke-autopilot.md">announce</a> that we have been certified for operation on GKE Autopilot.</p>
<h2 id="handsonwithelasticandgkeautopilot">Hands on with Elastic and GKE Autopilot</h2>
<h3 id="kubernetesobservabilityhttpswwwelasticcoobservabilitykubernetesmonitoringhasneverbeeneasier"><a href="https://www.elastic.co/observability/kubernetes-monitoring">Kubernetes observability</a> has never been easier</h3>
<p>To show how easy it is to get started with Autopilot and Elastic, let's walk through deploying the Elastic Agent on an Autopilot cluster. I’ll show how easy it is to set up and monitor an Autopilot cluster with the Elastic Agent and observe the cluster’s behavior with Kibana integrations.</p>
<p>One of the main differences between GKE and GKE Autopilot is that Autopilot protects the system namespace “kube-system.” To increase the stability and security of a cluster, Autopilot prevents user space workloads from adding or modifying system pods. The default configuration for Elastic Agent is to install itself into the system namespace. The majority of the changes we will make here are to convince the Elastic Agent to run in a different namespace.</p>
<h2 id="letsgetstartedwithelasticstack">Let’s get started with Elastic Stack!</h2>
<p>While writing this article, I used the latest version of Elastic. The best way for you to get started with Elastic Observability is to:</p>
<ol>
<li>Get an account on <a href="https://cloud.elastic.co/registration?fromURI=/home">Elastic Cloud</a> and look at this <a href="https://www.elastic.co/videos/training-how-to-series-cloud">tutoria</a>l to help launch your first stack, or</li>
<li><a href="https://www.elastic.co/partners/google-cloud">Launch Elastic Cloud on your Google Account</a></li>
</ol>
<h2 id="provisioninganautopilotclusterandanelasticstack">Provisioning an Autopilot cluster and an Elastic stack</h2>
<p>To test the agent, I first deployed the recommended, default GKE Autopilot cluster. Elastic’s GKE integration supports kube-state-metrics (KSM), which will increase the number of reported metrics available for reporting and dashboards. Like the Elastic Agent, KSM defaults to running in the system namespace, so I modified its manifest to work with Autopilot. For my testing, I also deployed a basic Elastic stack on Elastic Cloud in the same Google region as my Autopilot cluster. I used a fresh cluster deployed on Elastic’s managed service (ESS), but the process is the same if you are using an Elastic Cloud subscription purchased through the Google marketplace.</p>
<h2 id="addingelasticobservabilitytogkeautopilot">Adding Elastic Observability to GKE Autopilot</h2>
<p>Because this is a brand new deployment, Elastic suggests adding integrations to it. Let’s add the Kubernetes integration into the new deployment:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt26e6f085f595e0c3/6a85ca80501a855e3dfbb312/blog-welcome-to-elastic.png" alt="elastic agent GKE autopilot welcome" /></p>
<p>Elastic offers hundreds of integrations; filter the list by typing “kub” into the search bar (1) and then click the Kubernetes integration (2).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0c060386e86965f9/6a85ca859829265d865838dc/blog-elastic-kubernetes-integration.png" alt="elastic agent GKE autopilot kubernetes integration" /></p>
<p>The Kubernetes integration page gives you an overview of the integration and lets you manage the Kubernetes clusters you want to observe. We haven’t added a cluster yet, so I clicked “Add Kubernetes” to add the first integration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc66c7d320ecbdd60/6a85ca88f61d6e6c539c2b05/blog-elastic-add-kubernetes.png" alt="elastic agent GKE autopilot add kubernetes" /></p>
<p>I changed the integration name to reflect the Kubernetes offering type and then clicked “Save and continue” to accept the integration defaults.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt91271c09731fd95b/6a85ca8c8c2944d9b0b8903f/blog-elastic-add-kubernetes-integration.png" alt="elastic agent GKE autopilot add kubernetes integration" /></p>
<p>At this point, an Agent policy has been created. Now it’s time to install the agent. I clicked on the “Kubernetes” integration.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta51b4de42b224a86/6a85ca9080984c2503668fd2/blog-elastic-agent-policy-1.png" alt="elastic agent GKE autopilot agent policy" /></p>
<p>Then I selected the “integration policies” tab (1) and clicked “Add agent” (2).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltc217bb87df0a00dd/6a85ca95982926f3e15838e0/blog-elastic-add-agent.png" alt="elastic agent GKE autopilot add agent" /></p>
<p>Finally, I downloaded the full manifest for a standard GKE environment.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd95c9d2d38f1d2c0/6a85ca9bba7accf30999213e/blog-elastic-download-manifest.png" alt="elastic agent GKE autopilot download manifest" /></p>
<p>We won’t be using this manifest directly, but it contains many of the values that we will need to deploy the agent on Autopilot in the next section.</p>
<p>The Elastic stack is ready and waiting for the Autopilot logs, metrics, and events. It’s time to connect Autopilot to this deployment using the Elastic Agent for GKE.</p>
<h2 id="connectautopilottoelastic">Connect Autopilot to Elastic</h2>
<p>From the Google cloud terminal, I downloaded and edited the Elastic Agent manifest for GKE Autopilot.</p>
<pre><code>$ curl -o elastic-agent-managed-gke-autopilot.yaml \
https://github.com/elastic/elastic-agent/blob/autopilotdocumentaton/docs/manifests/elastic-agent-managed-gke-autopilot.yaml
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf84444c5b0c8efe4/6a85ca9f93ffb917c9b91431/blog-elastic-cloud-shell-editor.png" alt="elastic agent GKE autopilot cloud shell editor" /></p>
<p>I used the cloud shell editor to configure the manifest for my Autopilot and Elastic clusters. For example, I updated the following:</p>
<pre><code>containers:
  - name: elastic-agent
    image: docker.elastic.co/beats/elastic-agent:8.19.13
</code></pre>
<p>I also changed the agent to the version of Elastic that I installed (8.6.0).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt2d877effdd568e7e/6a85caa49d2b719c7bf93977/blog-elastic-google-cloud.png" alt="elastic agent GKE autopilot google cloud" /></p>
<p>From the Integration manifest I downloaded earlier, I copied the values for FLEET_URL and FLEET_ENROLLMENT_TOKEN into this YAML file.</p>
<p>Now it’s time to apply the updated manifest to the Autopilot instance.</p>
<p>Before I commit, I always like to see what’s going to be created (and check for syntax errors) with a dry run.</p>
<pre><code>$ clear
$ kubectl apply --dry-run="client" -f elastic-agent-managed-gke-autopilot.yaml
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9f58f4750aa9227a/6a85caa8f5f1a0024a2ec8ef/blog-elastic-dry-run.png" alt="elastic agent GKE autopilot dry run" /></p>
<p>Everything looks good, so I’ll do it for real this time.</p>
<pre><code>$ clear
$ kubectl apply -f elastic-agent-managed-gke-autopilot.yaml
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9b3944ad1a6bbec3/6a85caab6826669bd71eabf1/blog-elastic-autopilot-cluster.png" alt="elastic agent GKE autopilot cluster" /></p>
<p>After several minutes, metrics will start flowing from the Autopilot cluster directly into the Elastic deployment.</p>
<h2 id="addingaworkloadtotheautopilotcluster">Adding a workload to the Autopilot cluster</h2>
<p>Observing an Autopilot cluster without a workload is boring, so I deployed a modified version of Google’s <a href="https://github.com/bshetti/opentelemetry-microservices-demo">Hipster Shop</a> (which includes OpenTelemetry reporting):</p>
<pre><code>$ git clone https://github.com/bshetti/opentelemetry-microservices-demo
$ cd opentelemetry-microservices-demo
$ nano ./deploy-with-collector-k8s/otelcollector.yaml
</code></pre>
<p>To get the application’s telemetry talking to our Elastic stack, I replaced all instances of the exporter type from HTTP (otlphttp/elastic) to gRPC (otlp/elastic). I then replaced OTEL_EXPORTER_OTLP_ENDPOINT with my APM endpoint and I replaced OTEL_EXPORTER_OTLP_HEADERS with my APM OTEL Bearer and Token.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt50d29e192859e232/6a85caaf43c0b73bf42f060a/blog-elastic-terminal-telemetry.png" alt="elastic agent GKE autopilot terminal telemetry" /></p>
<p>Then I deployed the Hipster Shop.</p>
<pre><code>$ kubectl create -f ./deploy-with-collector-k8s/adservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/redis.yaml
$ kubectl create -f ./deploy-with-collector-k8s/cartservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/checkoutservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/currencyservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/emailservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/frontend.yaml
$ kubectl create -f ./deploy-with-collector-k8s/paymentservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/productcatalogservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/recommendationservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/shippingservice.yaml
$ kubectl create -f ./deploy-with-collector-k8s/loadgenerator.yaml
</code></pre>
<p>Once all of the shop’s pods were running, I deployed the OpenTelemetry collector.</p>
<pre><code>$ kubectl create -f ./deploy-with-collector-k8s/otelcollector.yaml
</code></pre>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9e1ed7b460b68149/6a85cab2f5f1a033e02ec8f7/blog-elastic-deployed-opentelemetry-collector.png" alt="elastic agent GKE autopilot deployed opentelemetry collector" /></p>
<h2 id="observeandvisualizeautopilotsmetrics">Observe and visualize Autopilot’s metrics</h2>
<p>Now that we have added the Elastic Agent to our Autopilot cluster and added a workload, let's take a look at some of the Kubernetes visualizations the integration provides out of the box.</p>
<p>The “[Metrics Kubernetes] Overview” is a great place to start. It provides a high-level view of the resources used by the cluster and allows me to drill into more specific dashboards that I find interesting:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt109d07366e7595d6/6a85cab8342d69fd9421b0e3/blog-elastic-create-visualization.png" alt="elastic agent GKE autopilot create visualization" /></p>
<p>For example, the “[Metrics Kubernetes] Pods” gives me a high-level view of the pods deployed in the cluster:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf2dbb5d3043b2758/6a85cabd501a85304bfbb31c/blog-elastic-pod.png" alt="elastic agent GKE autopilot pod" /></p>
<p>The “[Metrics Kubernetes] Volumes” gives me an in-depth view to how storage is allocated and used in the Autopilot cluster:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltb841c39379c9aee2/6a85cac043c0b745062f060e/blog-elastic-filesystem-information.png" alt="elastic agent GKE autopilot filesystem information" /></p>
<h2 id="creatinganalert">Creating an alert</h2>
<p>From here, I can easily discover patterns in my cluster’s behavior and even create Alerts. Here is an example of an alert to notify me if the the main storage volume (called “volume”) exceeds 80% of its allocated space:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt31d75eb5816f6a0c/6a85cac4501a85096ffbb320/blog-elastic-create-rule-elasticsearch-query.png" alt="elastic agent GKE autopilot create rule" /></p>
<p>With a little work, I created this view from the standard dashboard:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48e17cb04ac74819/6a85cac79a32f1162da7dfde/blog-elastic-kubernetes-dashboard.png" alt="elastic agent GKE autopilot kubernetes dashboard" /></p>
<h2 id="conclusion">Conclusion</h2>
<p>Today I have shown how easy it is to monitor, observe, and generate alerts on a GKE Autopilot cluster. To get more information on what is possible, see the official Elastic documentation for <a href="https://github.com/elastic/elastic-agent/blob/autopilotdocumentaton/docs/elastic-agent-gke-autopilot.md">Autopilot observability with Elastic Agent</a>.</p>
<h2 id="nextsteps">Next steps</h2>
<p>If you don’t have Elastic yet, you can get started for free with an <a href="https://www.elastic.co/cloud/elasticsearch-service/signup">Elastic Trial</a> today. Get more from Elastic and Google together with a <a href="https://console.cloud.google.com/marketplace/browse?q=Elastic&amp;utm_source=Elastic&amp;utm_medium=qwiklabs&amp;utm_campaign=Qwiklabs+to+Marketplace">Marketplace subscription</a>. Elastic does more than just integrate with GKE — check out the almost <a href="https://www.elastic.co/integrations">300 integrations</a> that Elastic provides.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/observe-gke-autopilot-clusters</link>
    <guid isPermaLink="false">observe-gke-autopilot-clusters</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Eric Lowry]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt48e17cb04ac74819/6a85cac79a32f1162da7dfde/blog-elastic-kubernetes-dashboard.png" length="0" type="image/png"/>
    <pubDate>Wed, 15 Mar 2023 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Independence with OpenTelemetry on Elastic]]></title>
    <description><![CDATA[OpenTelemetry has become a key component for observability given its open standards and developer-friendly tools. See how easily Elastic Observability integrates with OTel to provide a platform that minimizes vendor lock-in and maximizes flexibility.]]></description>
    <content:encoded><![CDATA[<p>The drive for faster, more scalable services is on the rise. Our day-to-day lives depend on apps, from a food delivery app to have your favorite meal delivered, to your banking app to manage your accounts, to even apps to schedule doctor’s appointments. These apps need to be able to grow from not only a features standpoint but also in terms of user capacity. The scale and need for global reach drives increasing complexity for these high-demand cloud applications.</p>
<p>In order to keep pace with demand, most of these online apps and services (for example, mobile applications, web pages, SaaS) are moving to a distributed microservice-based architecture and Kubernetes. Once you’ve migrated your app to the cloud, how do you manage and monitor production, scale, and availability of the service? <a href="https://opentelemetry.io/">OpenTelemetry</a> is quickly becoming the de facto standard for instrumentation and collecting application telemetry data for Kubernetes applications.</p>
<p><a href="https://www.elastic.co/what-is/opentelemetry">OpenTelemetry (OTel)</a> is an open source project providing a collection of tools, APIs, and SDKs that can be used to generate, collect, and export telemetry data (metrics, logs, and traces) to understand software performance and behavior. OpenTelemetry recently became a CNCF incubating project and has a significant amount of growing community and vendor support.</p>
<p>While OTel provides a standard way to instrument applications with a standard telemetry format, it doesn’t provide any backend or analytics components. Hence using OTel libraries in applications, infrastructure, and user experience monitoring provides flexibility in choosing the appropriate <a href="https://www.elastic.co/observability">observability tool</a> of choice. There is no longer any vendor lock-in for application performance monitoring (APM).</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ac0a045fdf76a37/6a7f193205b7b51e0118bd21/blog-elastic-otel-1.png" alt="" /></p>
<p>Elastic Observability natively supports OpenTelemetry and its OpenTelemetry protocol (OTLP) to ingest traces, metrics, and logs. All of Elastic Observability’s APM capabilities are available with OTel data. Hence the following capabilities (and more) are available for OTel data:</p>
<ul>
<li>Service maps</li>
<li>Service details (latency, throughput, failed transactions)</li>
<li>Dependencies between services</li>
<li>Transactions (traces)</li>
<li>ML correlations (specifically for latency)</li>
<li>Service logs</li>
</ul>
<p>In addition to Elastic’s APM and unified view of the telemetry data, you will now be able to use Elastic’s powerful machine learning capabilities to reduce the analysis, and alerting to help reduce MTTR.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta01b4eb3f8dbfb6f/6a7f1935e02fac237a5d698d/blog-elastic-otel-2.png" alt="" /></p>
<p>Given its open source heritage, Elastic also supports other CNCF based projects, such as Prometheus, Fluentd, Fluent Bit, Istio, Kubernetes (K8S), and many more.</p>
<p>This blog will show:</p>
<ul>
<li>How to get a popular OTel instrumented demo app (Hipster Shop) configured to ingest into <a href="http://cloud.elastic.co">Elastic Cloud</a> through a few easy steps</li>
<li>Highlight some of the Elastic APM capabilities and features around OTel data and what you can do with this data once it’s in Elastic</li>
</ul>
<p>In follow-up blogs, we will detail how to use Elastic’s machine learning with OTel telemetry data, how to instrument OTel application metrics for specific languages, how we can support Prometheus ingest through the OTel collector, and more. Stay tuned!</p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up the configuration:</p>
<ul>
<li>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>).</li>
<li>We used the OpenTelemetry Demo. Directions for using Elastic with OpenTelemetry Demo are <a href="https://github.com/elastic/opentelemetry-demo">here</a>.</li>
<li>Make sure you have <a href="https://kubernetes.io/docs/reference/kubectl/">kubectl</a> and <a href="https://helm.sh/">helm</a> also installed locally.</li>
<li>Additionally, we are using an OTel manually instrumented version of the application. No OTel automatic instrumentation was used in this blog configuration.</li>
<li>Location of our clusters. While we used Google Kubernetes Engine (GKE), you can use any Kubernetes platform of your choice.</li>
<li>While Elastic can ingest telemetry directly from OTel instrumented services, we will focus on the more traditional deployment, which uses the OpenTelemetry Collector.</li>
<li>Prometheus and FluentD/Fluent Bit — traditionally used to pull all Kubernetes data — is not being used here versus Kubernetes Agents. Follow-up blogs will showcase this.</li>
</ul>
<p>Here is the configuration we will get set up in this blog:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt757221af75648dce/6a7f193896b5a66c5387b867/blog-elastic-otel-3.png" alt="Configuration to ingest OpenTelemetry data used in this blog" /></p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Over the next few steps, I’ll walk through an <a href="https://www.elastic.co/observability/opentelemetry">Opentelemetry visualization</a>:</p>
<ul>
<li>Getting an account on Elastic Cloud</li>
<li>Bringing up a GKE cluster</li>
<li>Bringing up the application</li>
<li>Configuring Kubernetes OTel Collector configmap to point to Elastic Cloud</li>
<li>Using Elastic Observability APM with OTel data for improved visibility</li>
</ul>
<h3 id="step0createanaccountonelasticcloud">Step 0: Create an account on Elastic Cloud</h3>
<p>Follow the instructions to <a href="https://cloud.elastic.co/registration?fromURI=/home">get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt588fbb3e515933fa/6a7f193aea068d34baf0a2b1/blog-elastic-otel-4.png" alt="" /></p>
<h3 id="step1bringupak8scluster">Step 1: Bring up a K8S cluster</h3>
<p>We used Google Kubernetes Engine (GKE), but you can use any Kubernetes platform of your choice.</p>
<p>There are no special requirements for Elastic to collect OpenTelemetry data from a Kubernetes cluster. Any normal Kubernetes cluster on GKE, EKS, AKS, or Kubernetes compliant cluster (self-deployed and managed) works.</p>
<h3 id="step2loadtheopentelemetrydemoapplicationonthecluster">Step 2: Load the OpenTelemetry demo application on the cluster</h3>
<p>Get your application on a Kubernetes cluster in your cloud service of choice or local Kubernetes platform. The application I am using is available <a href="https://github.com/bshetti/opentelemetry-microservices-demo/tree/main/deploy-with-collector-k8s">here</a>.</p>
<p>First clone the directory locally:</p>
<pre><code>git clone https://github.com/elastic/opentelemetry-demo.git
</code></pre>
<p>(Make sure you have <a href="https://kubernetes.io/docs/reference/kubectl/">kubectl</a> and <a href="https://helm.sh/">helm</a> also installed locally.)</p>
<p>The instructions utilize a specific opentelemetry-collector configuration for Elastic. Essentially, the Elastic <a href="https://github.com/elastic/opentelemetry-demo/blob/main/kubernetes/elastic-helm/values.yaml">values.yaml</a> file specified in the elastic/opentelemetry-demo configure the opentelemetry-collector to point to the Elastic APM Server using two main values:</p>
<p>OTEL_EXPORTER_OTLP_ENDPOINT is Elastic’s APM Server<br />
OTEL_EXPORTER_OTLP_HEADERS Elastic Authorization</p>
<p>These two values can be found in the OpenTelemetry setup instructions under the APM integration instructions (Integrations-&gt;APM) in your Elastic cloud.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blte776fb3258454d4d/6a7f193d42a117a8cd95c2df/blog-elastic-apm-agents.png" alt="elastic apm agents" /></p>
<p>Once you obtain this, the first step is to create a secret key on the cluster with your Elastic APM server endpoint, and your APM Secret Token with the following instruction:</p>
<pre><code>kubectl create secret generic elastic-secret \
  --from-literal=elastic_apm_endpoint='YOUR_APM_ENDPOINT_WITHOUT_HTTPS_PREFIX' \
  --from-literal=elastic_apm_secret_token='YOUR_APM_SECRET_TOKEN'
</code></pre>
<p>Don't forget to replace:</p>
<ul>
<li>YOUR_APM_ENDPOINT_WITHOUT_HTTPS_PREFIX: your Elastic APM endpoint ( <strong>without https:// prefix</strong> ) with OTEL_EXPORTER_OTLP_ENDPOINT</li>
<li>YOUR_APM_SECRET_TOKEN: your Elastic APM secret token OTEL_EXPORTER_OTLP_HEADERS</li>
</ul>
<p>Now execute the following commands:</p>
<pre><code># switch to the kubernetes/elastic-helm directory
cd kubernetes/elastic-helm

# add the open-telemetry Helm repostiroy
helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts

# deploy the demo through helm install
helm install -f values.yaml my-otel-demo open-telemetry/opentelemetry-demo
</code></pre>
<p>Once your application is up on Kubernetes, you will have the following pods (or some variant) running on the <strong>default</strong> namespace.</p>
<pre><code>kubectl get pods -n default
</code></pre>
<p>Output should be similar to the following:</p>
<pre><code>NAME                                                  READY   STATUS    RESTARTS      AGE
my-otel-demo-accountingservice-5c77754b4f-vwph6       1/1     Running   0             5d4h
my-otel-demo-adservice-6b8b7c7dc5-mb7j5               1/1     Running   0             5d4h
my-otel-demo-cartservice-76d94b7dcd-2g4lf             1/1     Running   0             5d4h
my-otel-demo-checkoutservice-988bbdb88-hmkrp          1/1     Running   0             5d4h
my-otel-demo-currencyservice-6cf4b5f9f6-vz9t2         1/1     Running   0             5d4h
my-otel-demo-emailservice-868c98fd4b-lpr7n            1/1     Running   6 (18h ago)   5d4h
my-otel-demo-featureflagservice-8446ff9c94-lzd4w      1/1     Running   0             5d4h
my-otel-demo-ffspostgres-867945d9cf-zzwd7             1/1     Running   0             5d4h
my-otel-demo-frauddetectionservice-5c97c589b9-z8fhz   1/1     Running   0             5d4h
my-otel-demo-frontend-d85ccf677-zg9fp                 1/1     Running   0             5d4h
my-otel-demo-frontendproxy-6c5c4fccf6-qmldp           1/1     Running   0             5d4h
my-otel-demo-kafka-68bcc66794-dsbr6                   1/1     Running   0             5d4h
my-otel-demo-loadgenerator-64c545b974-xfccq           1/1     Running   1 (36h ago)   5d4h
my-otel-demo-otelcol-fdfd9c7cf-6lr2w                  1/1     Running   0             5d4h
my-otel-demo-paymentservice-7955c68859-ff7zg          1/1     Running   0             5d4h
my-otel-demo-productcatalogservice-67c879657b-wn2wj   1/1     Running   0             5d4h
my-otel-demo-quoteservice-748d754ffc-qcwm4            1/1     Running   0             5d4h
my-otel-demo-recommendationservice-df78894c7-lwm5v    1/1     Running   0             5d4h
my-otel-demo-redis-7d48567546-h4p4t                   1/1     Running   0             5d4h
my-otel-demo-shippingservice-f6fc76ddd-2v7qv          1/1     Running   0             5d4h
</code></pre>
<h3 id="step3openkibanaandusetheapmservicemaptoviewyourotelinstrumentedservices">Step 3: Open Kibana and use the APM Service Map to view your OTel instrumented Services</h3>
<p>In the Elastic Observability UI under APM, select servicemap to see your services.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt5ec18e0b8fe27ba9/6a7f194033fa8a5adb202b64/blog-elastic-observability-APM.png" alt="elastic observability APM" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt1d2c8e5136dc6f53/6a7f19426693f8101666435d/blog-elastic-observability-OTEL-service-map.png" alt="elastic observability OTEL service map" /></p>
<p>If you are seeing this, then the OpenTelemetry Collector is sending data into Elastic:</p>
<p><em>Congratulations,</em> <em>you've instrumented the OpenTelemetry demo application using and successfully ingested the telemetry data into the Elastic!</em></p>
<h3 id="step4whatcanelasticshowme">Step 4: What can Elastic show me?</h3>
<p>Now that the OpenTelemetry data is ingested into Elastic, what can you do?</p>
<p>First, you can view the APM service map (as shown in the previous step) — this will give you a full view of all the services and the transaction flows between services.</p>
<p>Next, you can now check out individual services and the transactions being collected.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltd116b5c740f25566/6a7f19456693f83ea1664361/blog-elastic-observability-frontend-overview.png" alt="elastic observability frontend overview" /></p>
<p>As you can see, the frontend details are listed. Everything from:</p>
<ul>
<li>Average service latency</li>
<li>Throughput</li>
<li>Main transactions</li>
<li>Failed traction rate</li>
<li>Errors</li>
<li>Dependencies</li>
</ul>
<p>Let’s get to the trace. In the Transactions tab, you can review all the types of transactions related to the frontend service:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf1669d81336b9a3e/6a7f194873d9bdc7e029df3b/blog-elastic-observability-frontend-transactions.png" alt="elastic observability frontend transactions" /></p>
<p>Selecting the HTTP POST transaction, we can see the full trace with all the spans:</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt7b4a0689cfa8ad76/6a7f194b33fa8a0360202b68/blog-elastic-observability-frontend-HTTP-POST.png" alt="Average latency for this transaction, throughput, any failures, and of course the trace!" /></p>
<p>Not only can you review the trace but you can also analyze what is related to higher than normal latency for HTTP POST .</p>
<p>Elastic uses machine learning to help identify any potential latency issues across the services from the trace. It’s as simple as selecting the Latency Correlations tab and running the correlation.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta53b6a97f8d8cacf/6a7f194ee88c653c1c00bae0/blog-elastic-latency-correlations.png" alt="elastic observability latency correlations" /></p>
<p>This shows that the high latency transactions are occurring in checkout service with a medium correlation.</p>
<p>You can then drill down into logs directly from the trace view and review the logs associated with the trace to help identify and pinpoint potential issues.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt8ad97b9fd8b1ea36/6a7f19505967e50ea75dd69d/blog-elastic-latency-distribution.png" alt="elastic observability latency distribution" /></p>
<h3 id="analyzeyourdatawithelasticmachinelearningml">Analyze your data with Elastic machine learning (ML)</h3>
<p>Once OpenTelemetry metrics are in Elastic, start analyzing your data through Elastic’s ML capabilities.</p>
<p>A great review of these features can be found here: <a href="https://www.elastic.co/blog/apm-correlations-elastic-observability-root-cause-transactions">Correlating APM telemetry to determine root causes in transactions</a>. And there are many more videos and blogs on <a href="https://www.elastic.co/blog/">Elastic’s Blog</a>. We’ll follow up with additional blogs on leveraging Elastic’s machine learning capabilities for OpenTelemetry data.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you ingest and analyze OpenTelemetry data with Elastic’s APM capabilities.</p>
<p>A quick recap of lessons and more specifically learned:</p>
<ul>
<li>How to get a popular OTel instrumented demo app (Hipster Shop) configured to ingest into <a href="http://cloud.elastic.co">Elastic Cloud</a>, through a few easy steps</li>
<li>Highlight some of the Elastic APM capabilities and features around OTel data and what you can do with this once it’s in Elastic</li>
</ul>
<p>Ready to get started? Sign up <a href="https://cloud.elastic.co/registration">for Elastic Cloud</a> and try out the features and capabilities I’ve outlined above to get the most value and visibility out of your OpenTelemetry data.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/opentelemetry-observability</link>
    <guid isPermaLink="false">opentelemetry-observability</guid>
    <category><![CDATA[OpenTelemetry]]></category>
    <category><![CDATA[APM]]></category>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt785e1bd8fa6dd28d/6a7f19532f00b2a466efef13/illustration-scalability-gear-1680x980_(1).jpg" length="0" type="image/jpeg"/>
    <pubDate>Tue, 15 Nov 2022 00:00:00 GMT</pubDate>
  </item>
  <item>
    <title><![CDATA[Managing your Kubernetes cluster with Elastic Observability]]></title>
    <description><![CDATA[Unify all of your Kubernetes metrics, log, and trace data on a single platform and dashboard, Elastic. From the infrastructure to the application layer Elastic Observability makes it easier for you to understand how your cluster is performing.]]></description>
    <content:encoded><![CDATA[<p>As an operations engineer (SRE, IT manager, DevOps), you’re always struggling with how to manage technology and data sprawl. Kubernetes is becoming increasingly pervasive and a majority of these deployments will be in Amazon Elastic Kubernetes Service (EKS), Google Kubernetes Engine (GKE), or Azure Kubernetes Service (AKS). Some of you may be on a single cloud while others will have the added burden of managing clusters on multiple Kubernetes cloud services. In addition to cloud provider complexity, you also have to manage hundreds of deployed services generating more and more observability and telemetry data.</p>
<p>The day-to-day operations of understanding the status and health of your Kubernetes clusters and applications running on them, through the logs, metrics, and traces they generate, will likely be your biggest challenge. But as an operations engineer you will need all of that important data to help prevent, predict, and remediate issues. And you certainly don’t need that volume of metrics, logs and traces spread across multiple tools when you need to visualize and analyze Kubernetes telemetry data for troubleshooting and support.</p>
<p>Elastic Observability helps manage the sprawl of Kubernetes metrics and logs by providing extensive and centralized observability capabilities beyond just the logging that we are known for. Elastic Observability provides you with granular insights and context into the behavior of your Kubernetes clusters along with the applications running on them by unifying all of your metrics, log, and trace data through OpenTelemetry and APM agents.</p>
<p>Regardless of the cluster location (EKS, GKE, AKS, self-managed) or application, <a href="https://www.elastic.co/what-is/kubernetes-monitoring">Kubernetes monitoring</a> is made simple with Elastic Observability. All of the node, pod, container, application, and infrastructure (AWS, GCP, Azure) metrics, infrastructure and application logs, along with application traces are available in Elastic Observability.</p>
<p>In this blog we will show:</p>
<ul>
<li>How <a href="http://cloud.elastic.co">Elastic Cloud</a> can aggregate and ingest metrics and log data through the Elastic Agent (easily deployed on your cluster as a DaemonSet) to retrieve logs and metrics from the host (system metrics, container stats) along with logs from all services running on top of Kubernetes.</li>
<li>How Elastic Observability can bring a unified telemetry experience (logs, metrics,traces) across all your Kubernetes cluster components (pods, nodes, services, namespaces, and more).</li>
</ul>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4dd4583086a7cbbc/6a7f0ba36c6eac5e44f1407f/ManagingKubernetes-ElasticAgentIntegration-1.png" alt="Elastic Agent with Kubernetes Integration" /></p>
<h2 id="prerequisitesandconfig">Prerequisites and config</h2>
<p>If you plan on following this blog, here are some of the components and details we used to set up this demonstration:</p>
<ul>
<li>Ensure you have an account on <a href="http://cloud.elastic.co">Elastic Cloud</a> and a deployed stack (<a href="https://www.elastic.co/guide/en/elastic-stack/current/installing-elastic-stack.html">see instructions here</a>).</li>
<li>While we used GKE, you can use any location for your Kubernetes cluster.</li>
<li>We used a variant of the ever so popular <a href="https://github.com/GoogleCloudPlatform/microservices-demo">HipsterShop</a> demo application. It was originally written by Google to showcase Kubernetes across a multitude of variants available such as the <a href="https://github.com/open-telemetry/opentelemetry-demo">OpenTelemetry Demo App</a>. To use the app, please go <a href="https://github.com/bshetti/opentelemetry-microservices-demo/tree/main/deploy-with-collector-k8s">here</a> and follow the instructions to deploy. You don’t need to deploy otelcollector for Kubernetes metrics to flow — we will cover this below.</li>
<li>Elastic supports native ingest from Prometheus and FluentD, but in this blog, we are showing a direct ingest from Kubernetes cluster via Elastic Agent. There will be a follow-up blog showing how Elastic can also pull in telemetry from Prometheus or FluentD/bit.</li>
</ul>
<h2 id="whatcanyouobserveandanalyzewithelastic">What can you observe and analyze with Elastic?</h2>
<p>Before we walk through the steps on getting Elastic set up to ingest and visualize Kubernetes cluster metrics and logs, let’s take a sneak peek at Elastic’s helpful dashboards.</p>
<p>As we noted, we ran a variant of HipsterShop on GKE and deployed Elastic Agents with Kubernetes integration as a DaemonSet on the GKE cluster. Upon deployment of the agents, Elastic starts ingesting metrics from the Kubernetes cluster (specifically from kube-state-metrics) and additionally Elastic will pull all log information from the cluster.</p>
<h3 id="visualizingkubernetesmetricsonelasticobservability">Visualizing Kubernetes metrics on Elastic Observability</h3>
<p>Here are a few Kubernetes dashboards that will be available out of the box (OOTB) on Elastic Observability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6296af433d4603be/6a7f0ba6e88c65225900b608/ManagingKubernetes-HipsterShopMetrics-2.png" alt="HipsterShop cluster metrics on Elastic Kubernetes overview dashboard " /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb67b0d9f1fb2b31/6a7f0ba9bd21989e18758039/ManagingKubernetes-HipsterShopDashboard-3.png" alt="HipsterShop default namespace pod dashboard on Elastic Observability" /></p>
<p>In addition to the cluster overview dashboard and pod dashboard, Elastic has several useful OOTB dashboards:</p>
<ul>
<li>Kubernetes overview dashboard (see above)</li>
<li>Kubernetes pod dashboard (see above)</li>
<li>Kubernetes nodes dashboard</li>
<li>Kubernetes deployments dashboard</li>
<li>Kubernetes DaemonSets dashboard</li>
<li>Kubernetes StatefulSets dashboards</li>
<li>Kubernetes CronJob &amp; Jobs dashboards</li>
<li>Kubernetes services dashboards</li>
<li>More being added regularly</li>
</ul>
<p>Additionally, you can either customize these dashboards or build out your own.</p>
<h3 id="workingwithlogsonelasticobservability">Working with logs on Elastic Observability</h3>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6000b4853f85ac77/6a7f0bac1967ea5a663306c1/ManagingKubernetes-Logging-4.png" alt="Kubernetes container logs and Elastic Agent logs" /></p>
<p>As you can see from the screens above, not only can I get Kubernetes cluster metrics, but also all the Kubernetes logs simply by using the Elastic Agent in my Kubernetes cluster.</p>
<h3 id="preventpredictandremediateissues">Prevent, predict, and remediate issues</h3>
<p>In addition to helping manage metrics and logs, Elastic can help you detect and predict anomalies across your cluster telemetry. Simply turn on Machine Learning in Elastic against your data and watch it help you enhance your analysis work. As you can see below, Elastic is not only a unified observability location for your Kubernetes cluster logs and metrics, but it also provides extensive true machine learning capabilities to enhance your analysis and management.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltf8e368fc11775c13/6a7f0baffc63aba1ef64cbb7/ManagingKubernetes-AnomalyDetection-5.png" alt="Anomaly detection across logs on Elastic Observability" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt0547380eaee99b10/6a7f0bb2ead8ec024cbaa7f9/ManagingKubernetes-PodIssues-6.png" alt="Analyzing issues on a Kubernetes pod with Elastic Observability " /></p>
<p>In the top graph, you see anomaly detection across logs and it shows something potentially wrong in the September 21 to 23 time period. Dig into the details on the bottom chart by analyzing a single kubernetes.pod.cpu.usage.node metric showing cpu issues early in September and again, later on in the month. You can do more complicated analyses on your cluster telemetry with Machine Learning using multi-metric analysis (versus the single metric issue I am showing above) along with population analysis.</p>
<p>Elastic gives you better machine learning capabilities to enhance your analysis of Kubernetes cluster telemetry. In the next section, let’s walk through how easy it is to get your telemetry data into Elastic.</p>
<h2 id="settingitallup">Setting it all up</h2>
<p>Let’s walk through the details of how to get metrics, logs, and traces into Elastic from a HipsterShop application deployed on GKE.</p>
<p>First, pick your favorite version of Hipstershop — as we noted above, we used a variant of the <a href="https://github.com/open-telemetry/opentelemetry-demo">OpenTelemetry-Demo</a> because it already has OTel. We slimmed it down for this blog, however (fewer services with some varied languages).</p>
<h3 id="step0getanaccountonelasticcloud">Step 0: Get an account on Elastic Cloud</h3>
<p>Follow the instructions to <a href="https://cloud.elastic.co/registration?fromURI=/home">get started on Elastic Cloud</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt277a59dd6558e518/6a7f0bb5de2315150bfd7ba5/ManagingKubernetes-FreeElasticCloud-7.png" alt="" /></p>
<h3 id="step1getakubernetesclusterandloadyourkubernetesappintoyourcluster">Step 1: Get a Kubernetes cluster and load your Kubernetes app into your cluster</h3>
<p>Get your app on a Kubernetes cluster in your Cloud service of choice or local Kubernetes platform. Once your app is up on Kubernetes, you should have the following pods (or some variant) running on the default namespace.</p>
<pre><code>NAME                                    READY   STATUS    RESTARTS   AGE
adservice-8694798b7b-jbfxt              1/1     Running   0          4d3h
cartservice-67b598697c-hfsxv            1/1     Running   0          4d3h
checkoutservice-994ddc4c4-p9p2s         1/1     Running   0          4d3h
currencyservice-574f65d7f8-zc4bn        1/1     Running   0          4d3h
emailservice-6db78645b5-ppmdk           1/1     Running   0          4d3h
frontend-5778bfc56d-jjfxg               1/1     Running   0          4d3h
jaeger-686c775fbd-7d45d                 1/1     Running   0          4d3h
loadgenerator-c8f76d8db-gvrp7           1/1     Running   0          4d3h
otelcollector-5b87f4f484-4wbwn          1/1     Running   0          4d3h
paymentservice-6888bb469c-nblqj         1/1     Running   0          4d3h
productcatalogservice-66478c4b4-ff5qm   1/1     Running   0          4d3h
recommendationservice-648978746-8bzxc   1/1     Running   0          4d3h
redis-cart-96d48485f-gpgxd              1/1     Running   0          4d3h
shippingservice-67fddb767f-cq97d        1/1     Running   0          4d3h
</code></pre>
<h3 id="step2turnonahrefhttpsgithubcomkuberneteskubestatemetricstarget_selfkubestatemetricsa">Step 2: Turn on <a href="https://github.com/kubernetes/kube-state-metrics">kube-state-metrics</a></h3>
<p>Next you will need to turn on <a href="https://github.com/kubernetes/kube-state-metrics">kube-state-metrics</a>.</p>
<p>First:</p>
<pre><code>git clone https://github.com/kubernetes/kube-state-metrics.git
</code></pre>
<p>Next, in the kube-state-metrics directory under the examples directory, just apply the standard config.</p>
<pre><code>kubectl apply -f ./standard
</code></pre>
<p>This will turn on kube-state-metrics, and you should see a pod similar to this running in kube-system namespace.</p>
<pre><code>kube-state-metrics-5f9dc77c66-qjprz                    1/1     Running   0          4d4h
</code></pre>
<h3 id="step3installtheelasticagentwithkubernetesintegration">Step 3: Install the Elastic Agent with Kubernetes integration</h3>
<p><strong>Add Kubernetes Integration:</strong></p>
<ol>
<li><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdd9b396ea440ab15/6a840f08c8ced91cb80528d0/Addk8sButton-8.jpg" alt="" /></li>
<li>In Elastic, go to integrations and select the Kubernetes Integration, and select to Add Kubernetes.</li>
<li>Select a name for the Kubernetes integration.</li>
<li>Turn on kube-state-metrics in the configuration screen.</li>
<li>Give the configuration a name in the new-agent-policy-name text box.</li>
<li>Save the configuration. The integration with a policy is now created.</li>
</ol>
<p>You can read up on the agent policies and how they are used on the Elastic Agent <a href="https://www.elastic.co/guide/en/fleet/current/agent-policy.html">here</a>.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltde684a9e67536da0/6a7f0bb79090b0f30084e95b/ManagingKubernetes-K8sIntegration-9.png" alt="" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt9413c6c449235522/6a7f0bbaea068da442f09de7/ManagingKubernetes-FleetManagement-10.png" alt="" /></p>
<ol>
<li>Add Kubernetes integration.</li>
<li>Select the policy you just created in the second.</li>
<li>In the third step of Add Agent instructions, copy and paste or download the manifest.</li>
<li>Add manifest to the shell where you have kubectl running, save it as elastic-agent-managed-kubernetes.yaml, and run the following command.</li>
</ol>
<pre><code>kubectl apply -f elastic-agent-managed-kubernetes.yaml
</code></pre>
<p>You should see a number of agents come up as part of a DaemonSet in kube-system namespace.</p>
<pre><code>NAME                                                   READY   STATUS    RESTARTS   AGE
elastic-agent-qr6hj                                    1/1     Running   0          4d7h
elastic-agent-sctmz                                    1/1     Running   0          4d7h
elastic-agent-x6zkw                                    1/1     Running   0          4d7h
elastic-agent-zc64h                                    1/1     Running   0          4d7h
</code></pre>
<p>In my cluster, I have four nodes and four elastic-agents started as part of the DaemonSet.</p>
<h3 id="step4lookatelasticoutoftheboxdashboardsootbforkubernetesmetricsandstartdiscoveringkuberneteslogs">Step 4: Look at Elastic out of the box dashboards (OOTB) for Kubernetes metrics and start discovering Kubernetes logs</h3>
<p>That is it. You should see metrics flowing into all the dashboards. To view logs for specific pods, simply go into Discover in Kibana and search for a specific pod name.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt6296af433d4603be/6a7f0ba6e88c65225900b608/ManagingKubernetes-HipsterShopMetrics-2.png" alt="HipsterShop cluster metrics on Elastic Kubernetes overview dashboard" /></p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/bltdb67b0d9f1fb2b31/6a7f0ba9bd21989e18758039/ManagingKubernetes-HipsterShopDashboard-3.png" alt="Hipstershop default namespace pod dashboard on Elastic Observability" /></p>
<p>Additionally, you can browse all the pod logs directly in Elastic.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blta4498dff3b7ea4d0/6a7f0bbe63e959788f73dd60/ManagingKurbenetes-PodLogs-11.png" alt="frontendService and cartService logs" /></p>
<p>In the above example, I searched for frontendService and cartService logs.</p>
<h3 id="step5bonus">Step 5: Bonus!</h3>
<p>Because we were using an OTel based application, Elastic can even pull in the application traces. But that is a discussion for another blog.</p>
<p>Here is a quick peek at what Hipster Shop’s traces for a front end transaction look like in Elastic Observability.</p>
<p><img src="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt242e6a369d416a3b/6a7f0bc105b7b5347418b8ce/ManagingKubernetes-CheckOutTransaction-12.png" alt="Trace for Checkout transaction for HipsterShop" /></p>
<h2 id="conclusionelasticobservabilityrocksforkubernetesmonitoring">Conclusion: Elastic Observability rocks for Kubernetes monitoring</h2>
<p>I hope you’ve gotten an appreciation for how Elastic Observability can help you manage Kubernetes clusters along with the complexity of the metrics, log, and trace data it generates for even a simple deployment.</p>
<p>A quick recap of lessons and more specifically learned:</p>
<ul>
<li>How <a href="http://cloud.elastic.co">Elastic Cloud</a> can aggregate and ingest telemetry data through the Elastic Agent, which is easily deployed on your cluster as a DaemonSet and retrieves metrics from the host, such as system metrics, container stats, and metrics from all services running on top of Kubernetes</li>
<li>Show what Elastic brings from a unified telemetry experience (Kubernenetes logs, metrics, traces) across all your Kubernetes cluster components (pods, nodes, services, any namespace, and more).</li>
<li>Interest in exploring Elastic’s ML capabilities which will reduce your <strong>MTTHH</strong> (mean time to happy hour)</li>
</ul>
<p>Ready to get started? <a href="https://cloud.elastic.co/registration">Register</a> and try out the features and capabilities I’ve outlined above.</p>]]></content:encoded>
    <link>https://www.elastic.co/observability-labs/blog/kubernetes-cluster-metrics-logs-monitoring</link>
    <guid isPermaLink="false">kubernetes-cluster-metrics-logs-monitoring</guid>
    <category><![CDATA[Kubernetes]]></category>
    <category><![CDATA[Metrics]]></category>
    <category><![CDATA[Infrastructure Monitoring]]></category>
    <dc:creator><![CDATA[Bahubali Shetti]]></dc:creator>
    <enclosure url="https://static-www.elastic.co/v3/assets/bltefdd0b53724fa2ce/blt4dd4583086a7cbbc/6a7f0ba36c6eac5e44f1407f/ManagingKubernetes-ElasticAgentIntegration-1.png" length="0" type="image/png"/>
    <pubDate>Mon, 24 Oct 2022 00:00:00 GMT</pubDate>
  </item>
  </channel>
</rss>